Rumah  >  Artikel  >  Java  >  Pelaksanaan bersama objek dalam pengaturcaraan serentak Java

Pelaksanaan bersama objek dalam pengaturcaraan serentak Java

WBOY
WBOYke hadapan
2023-04-23 17:25:071556semak imbas

1. Keterlihatan

Secara amnya, kami tidak dapat menjamin bahawa utas yang menjalankan operasi baca boleh melihat nilai yang ditulis oleh utas lain, kerana setiap utas mempunyai mekanisme caching sendiri. Untuk memastikan keterlihatan operasi tulis memori antara berbilang benang, mekanisme penyegerakan mesti digunakan.

public class NoVisibility {
    private static boolean ready;
    private static int number;

    private static class ReaderThread extends Thread {
        public void run() {
            while (!ready)
                Thread.yield();
            System.out.println(number);
        }
    }

    public static void main(String[] args) {
        new ReaderThread().start();
        number = 42;
        ready = true;
    }
}

Kod di atas nampaknya mengeluarkan 42, tetapi sebenarnya ia mungkin tidak ditamatkan sama sekali, kerana benang bacaan tidak pernah dapat melihat nilai siap; ia berkemungkinan besar untuk mengeluarkan 0, kerana membaca benang melihat menulis Masukkan nilai sedia, tetapi tidak melihat nilai nombor yang ditulis kemudian. Sekiranya tiada penyegerakan, pengkompil, pemproses, masa jalan, dsb. mungkin membuat beberapa pelarasan yang tidak dijangka pada susunan pelaksanaan operasi.

Jadi, apabila data dikongsi antara berbilang urutan, penyegerakan yang betul harus digunakan.

1.1 Data basi

Melainkan penyegerakan digunakan, ia berkemungkinan besar memperoleh nilai basi pembolehubah. Nilai tidak sah mungkin tidak muncul pada masa yang sama, dan benang boleh memperoleh nilai terkini satu pembolehubah dan nilai tidak sah bagi pembolehubah lain. Data tidak sah juga boleh menyebabkan beberapa kegagalan yang mengelirukan, seperti pengecualian yang tidak dijangka, struktur data yang rosak, pengiraan tidak tepat, gelung tak terhingga, dsb.

1.2 Operasi 64-bit bukan atom

Untuk pembolehubah panjang dan berganda yang tidak meruap, JVM membenarkan operasi baca atau tulis 64-bit diuraikan kepada dua operasi 32-bit. Oleh itu, kemungkinan besar 32 bit atas nilai terkini dan 32 bit bawah nilai tidak sah akan dibaca, menyebabkan nilai rawak dibaca. Melainkan ia diisytiharkan dengan kata kunci yang tidak menentu atau dilindungi dengan kunci.

1.3 Penguncian dan Keterlihatan

Apabila benang melaksanakan blok kod segerak yang dilindungi oleh kunci, ia boleh melihat hasil semua operasi sebelumnya bagi urutan lain dalam blok kod disegerakkan yang sama. Tanpa penyegerakan, jaminan di atas tidak dapat dicapai. Maksud mengunci tidak terhad kepada tingkah laku pengecualian bersama, tetapi juga termasuk keterlihatan. Untuk memastikan semua utas melihat nilai terkini pembolehubah yang dikongsi, semua utas yang menjalankan operasi baca atau tulis mesti disegerakkan pada kunci yang sama.

1.4 Pembolehubah meruap

Apabila pembolehubah diisytiharkan sebagai jenis meruap, pengkompil mahupun masa jalan tidak akan menyusun semula operasi pada pembolehubah bersama-sama dengan operasi memori yang lain. Pembolehubah yang tidak menentu tidak dicache dalam daftar atau tempat lain yang tidak dapat dilihat oleh pemproses, jadi membaca pembolehubah yang tidak menentu sentiasa mengembalikan nilai bertulis yang paling terkini. Mekanisme penguncian boleh memastikan kedua-dua keterlihatan dan atomicity, manakala pembolehubah yang tidak menentu hanya boleh memastikan keterlihatan.

Pembolehubah meruap harus digunakan jika dan hanya jika semua syarat berikut dipenuhi:

  • Operasi menulis kepada pembolehubah tidak bergantung pada nilai semasa bagi pembolehubah, atau boleh memastikan Hanya gunakan satu utas untuk mengemas kini nilai pembolehubah.

  • Pembolehubah ini tidak termasuk dalam keadaan invarian bersama dengan pembolehubah keadaan lain.

  • Tidak perlu mengunci apabila mengakses pembolehubah.

2. Menerbitkan dan membocorkan

Menerbitkan objek bermakna objek itu boleh digunakan dalam kod di luar skop semasa. Kaedah penerbitan objek termasuk: rujukan kepada pembolehubah bukan peribadi, rujukan yang dikembalikan melalui panggilan kaedah, menerbitkan objek kelas dalam yang membayangkan rujukan kepada kelas luaran, dsb. Apabila objek dilepaskan yang tidak sepatutnya dilepaskan, ia dipanggil kebocoran.

public class ThisEscape {
   private int status;
   public ThisEscape(EventSource source) {
      source.registerListener(new EventListener() {
         public void onEvent(Event e) {
            doSomething(e);
         }
      });
      status = 1;
   }

   void doSomething(Event e) {
      status = e.getStatus();
   }

   interface EventSource {
      void registerListener(EventListener e);
   }

   interface EventListener {
      void onEvent(Event e);
   }

   interface Event {
      int getStatus();
   }
}

Oleh kerana contoh kelas dalam mengandungi rujukan tersirat kepada kejadian kelas luar, apabila ThisEscape menerbitkan EventListener, ia juga secara tersirat menerbitkan tika ThisEscape itu sendiri. Tetapi pada masa ini, status pembolehubah belum dimulakan, menyebabkan rujukan ini dibocorkan dalam pembina. Anda boleh menggunakan pembina persendirian dan kaedah kilang awam untuk mengelakkan proses pembinaan yang salah:

public class SafeListener {
    private int status;
    private final EventListener listener;
    private SafeListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        };
        status = 1;
    }
    public static SafeListener newInstance(EventSource source) {
        SafeListener safe = new SafeListener();
        source.registerListener(safe.listener);
        return safe;
    }

    void doSomething(Event e) {
        status = e.getStatus();
    }

    interface EventSource {
        void registerListener(EventListener e);
    }

    interface EventListener {
        void onEvent(Event e);
    }

    interface Event {
        int getStatus();
    }
}

3 Penutupan benang

Satu cara untuk mengelakkan penggunaan penyegerakan adalah dengan tidak berkongsi . Jika data diakses hanya dalam satu utas, tiada penyegerakan diperlukan, yang dipanggil penutupan benang. Pembendungan benang adalah pertimbangan pengaturcaraan dan mesti dilaksanakan dalam program. Java juga menyediakan beberapa mekanisme untuk membantu mengekalkan penutupan benang, seperti pembolehubah tempatan dan ThreadLocal.

3.1 Penutupan utas ad-hoc

Penutupan utas ad-hoc bermakna tanggungjawab untuk mengekalkan penutupan utas ditanggung sepenuhnya oleh pelaksanaan program. Menggunakan pembolehubah tidak menentu ialah satu cara untuk mencapai penutupan utas Ad-hoc selagi dijamin bahawa hanya satu utas menjalankan operasi tulis pada pembolehubah meruap yang dikongsi, maka adalah selamat untuk melaksanakan operasi "baca-ubah suai-tulis" pada pembolehubah ini. . , keterlihatan pembolehubah yang tidak menentu memastikan bahawa urutan lain dapat melihat nilai terkini.

Penutupan utas ad-hoc sangat rapuh, jadi gunakannya sesedikit mungkin dalam program anda. Jika boleh, gunakan teknik pembendungan benang lain seperti pembendungan tindanan dan ThreadLocal.

3.2 Penutupan tindanan

Dalam penutupan tindanan, objek hanya boleh diakses melalui pembolehubah setempat. Ia terletak pada timbunan utas pelaksanaan dan tidak boleh diakses oleh utas lain. Walaupun objek ini tidak selamat untuk benang, ia masih selamat untuk benang. Walau bagaimanapun, perlu diingat bahawa hanya orang yang menulis kod itu mengetahui objek mana yang disertakan dengan timbunan. Tanpa arahan yang jelas, penyelenggara seterusnya boleh dengan mudah membocorkan objek ini secara tidak sengaja.

3.3 ThreadLocal类

使用ThreadLocal是一种更规范的线程封闭方式,它能是线程中的某个值与保存值的对象关联起来。如下代码,通过将JDBC的连接保存到ThreadLocal对象中,每个线程都会拥有属于自己的连接:

public class ConnectionDispenser {
    static String DB_URL = "jdbc:mysql://localhost/mydatabase";

    private ThreadLocal<Connection> connectionHolder
        = new ThreadLocal<Connection>() {
            public Connection initialValue() {
                try {
                    return DriverManager.getConnection(DB_URL);
                } catch (SQLException e) {
                    throw new RuntimeException("Unable to acquire Connection, e");
                }
        };
    };

    public Connection getConnection() {
        return connectionHolder.get();
    }
}

从概念上看,你可以将ThreadLocal8742468051c85b06f0a0af9e3e506b5c视为包含了Mapdd13f7ef6939263b34f16ddd764e4ff9对象,其中保存了特定于改线程的值,但ThreadLocal的实现并非如此。这些特定于线程的值保存在Thread对象中,当线程终止后,这些值会作为垃圾被回收。

4. 不变性

如果某个对象在被创建后其状态就不能被修改,那么这个对象就被称为不可变对象。满足同步需求的另一种方法就是使用不可变对象。不可变对象一定是线程安全的。当满足以下条件时,对象才是不可变的:

  • 对象创建以后其状态就不能改变

  • 对象的所有域都是final类型

  • 对象是正确创建的,在对象创建期间,this引用没有泄露

public final class ThreeStooges {
    private final Set<String> stooges = new HashSet<String>();

    public ThreeStooges() {
        stooges.add("Moe");
        stooges.add("Larry");
        stooges.add("Curly");
    }

    public boolean isStooge(String name) {
        return stooges.contains(name);
    }
}

上述代码中,尽管stooges对象是可变的,但在它构造完成后无法对其修改。stooges是一个final类型的引用变量,因此所有的对象状态都通过一个final域访问。在构造函数中,this引用不能被除了构造函数之外的代码访问到。

4.1 final域

final类型的域是不能修改的,但如果final域所引用的对象是可变的,那么这些被引用的对象是可以修改的。final域的对象在构造函数中不会被重排序,所以final域也能保证初始化过程的安全性。和“除非需要更高的可见性,否则应将所有的域都声明为私用域”一样,“除非需要某个域是可变的,否则应将其声明为final域”也是一个良好的编程习惯。

4.2 使用volatile类型来发布不可变对象

因式分解Sevlet将执行两个原子操作:

  • 更新缓存

  • 通过判断缓存中的数值是否等于请求的数值来决定是否直接读取缓存中的结果

每当需要一组相关数据以原子方式执行某个操作时,就可以考虑创建一个不可变的类来包含这些数据:

public class OneValueCache {
    private final BigInteger lastNumber;
    private final BigInteger[] lastFactors;

    public OneValueCache(BigInteger i, BigInteger[] factors) {
        lastNumber = i;
        lastFactors = Arrays.copyOf(factors, factors.length);
    }

    public BigInteger[] getFactors(BigInteger i) {
        if (lastNumber == null || !lastNumber.equals(i))
            return null;
        else
            return Arrays.copyOf(lastFactors, lastFactors.length);
    }
}

当线程获取了不可变对象的引用后,不必担心另一个线程会修改对象的状态。如果要更新这些变量,可以创建一个新的容器对象,但其他使用原有对象的线程仍然会看到对象处于一致的状态。当一个线程将volatile类型的cache设置为引用一个新的OneValueCache时,其他线程就会立即看到新缓存的数据:

public class VolatileCachedFactorizer implements Servlet {
    private volatile OneValueCache cache = new OneValueCache(null, null);

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = cache.getFactors(i);
        if (factors == null) {
            factors = factor(i);
            cache = new OneValueCache(i, factors);
        }
        encodeIntoResponse(resp, factors);
    }
}

5 安全发布

5.1 不正确的发布

像这样将对象引用保存到公有域中就是不安全的:

public Holder holder;
public void initialize(){
    holder = new Holder(42);
}

由于存在可见性问题,其他线程看到的Holder对象将处于不一致的状态。除了发布对象的线程外,其他线程可以看到Holder域是一个失效值,因此将看到一个空引用或者之前的旧值。

public class Holder {
    private int n;

    public Holder(int n) {
        this.n = n;
    }

    public void assertSanity() {
        if (n != n)
            throw new AssertionError("This statement is false.");
    }
}

上述代码,即使Holder对象被正确的发布,assertSanity也有可能抛出AssertionError。因为线程看到Holder引用的值是最新的,但由于重排序Holder状态的值却是时效的。

5.2 不可变对象与初始化安全性

即使在发布不可变对象的引用时没有使用同步,也仍然可以安全地访问该对象。任何线程都可以在不需要额外同步的情况下安全地访问不可变对象,即使在发布这些对象时没有使用同步。在没有额外同步的情况下,也可以安全地访问final类型的域。然而,如果final类型的域所指向的是可变对象,那么在访问这些域所指向的对象的状态时仍然需要同步。

5.3 安全发布的常用模式

要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全发布:

  • 在静态初始化函数里初始化一个对象引用。

  • 将对象的引用保存到volatile类型的域或者AtomicReference对象中。

  • 将对象的引用保存到某个正确构造对象的final类型域中。

  • 将对象的引用保存到一个由锁保护的域中。

线程安全库中的容器类提供了以下的安全发布保证:

  • 通过将一个键或者值放入Hashtable、synchronizedMap或者ConcurrentMap中,可以安全地将它发布给任何从这些容器中访问它的线程。

  • 通过将某个对象放入Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或者synchronizedSet中,可以将该对象安全地发布到任何从这些容器中访问该对象的线程。

  • Dengan meletakkan objek ke dalam BlockingQueue atau ConcurrentLinkedQueue, anda boleh menerbitkan objek dengan selamat ke mana-mana urutan yang mengakses objek daripada baris gilir ini.

5.4 Objek Tidak Boleh Berubah Fakta

Jika objek boleh berubah secara teknikal, tetapi keadaannya tidak akan berubah selepas diterbitkan, maka objek ini Dipanggil objek tidak berubah de facto. Objek tidak boleh ubah de facto yang diterbitkan dengan selamat boleh digunakan dengan selamat oleh mana-mana urutan tanpa penyegerakan tambahan. Contohnya, kekalkan objek Peta yang menyimpan masa log masuk terkini setiap pengguna:

Peta awam7c36a245a97922f78d3224cbec5818e1 lastLogin =
Collections.synchronizedMap(new HashMapJika nilai objek Date tidak akan berubah selepas dimasukkan ke dalam Map, maka mekanisme penyegerakan dalam synchronizedMap sudah cukup untuk membolehkan nilai Date ​​disiarkan dengan selamat, dan tiada penyegerakan tambahan diperlukan apabila mengakses nilai Date ini .

5.5 Objek Boleh Berubah

Untuk objek boleh ubah, penyegerakan bukan sahaja diperlukan semasa menerbitkan objek, tetapi juga perlu digunakan setiap kali objek diakses untuk memastikan keterlihatan operasi pengubahsuaian seterusnya. Keperluan penerbitan objek bergantung pada kebolehubahannya:

  • Objek tidak berubah boleh diterbitkan melalui sebarang mekanisme.

  • Fakta bahawa objek tidak berubah mesti diterbitkan dengan cara yang selamat.

  • Objek boleh ubah mesti dilepaskan dengan cara yang selamat dan mesti selamat untuk benang atau dilindungi dengan kunci.

5.6 Objek kongsi selamat

Apabila menggunakan dan berkongsi objek dalam program serentak, anda boleh menggunakan beberapa strategi praktikal, termasuk:

  • Benang ditutup. Objek tertutup benang hanya boleh dimiliki oleh satu utas, objek itu disertakan dalam utas itu dan hanya boleh diubah suai oleh utas ini.

  • Perkongsian baca sahaja. Tanpa penyegerakan tambahan, objek baca sahaja yang dikongsi boleh diakses secara serentak oleh berbilang benang, tetapi tiada benang boleh mengubah suainya. Objek baca sahaja yang dikongsi termasuk objek tidak berubah dan objek tidak berubah de facto.

  • Perkongsian selamat benang. Objek selamat benang disegerakkan secara dalaman supaya berbilang benang boleh mengaksesnya melalui antara muka awam objek tanpa penyegerakan selanjutnya.

  • Objek yang dilindungi . Objek yang dilindungi hanya boleh diakses dengan memegang kunci tertentu. Objek yang dilindungi termasuk objek yang dikapsulkan dalam objek selamat benang lain, serta objek yang dilepaskan dan dilindungi oleh kunci tertentu.

Atas ialah kandungan terperinci Pelaksanaan bersama objek dalam pengaturcaraan serentak Java. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam