JavaRush /Java Blog /Random-ID /Konkurensi di Jawa. Tutorial - Konstruksi thread-safe.
0xFF
Level 9
Донецк

Konkurensi di Jawa. Tutorial - Konstruksi thread-safe.

Dipublikasikan di grup Random-ID
Setelah membahas risiko utama menjalankan program paralel (seperti atomisitas atau visibilitas ), kita akan melihat beberapa desain kelas yang akan membantu kita mencegah kesalahan yang disebutkan di atas. Beberapa konstruksi ini membuat objek thread-safe, memungkinkan kita membaginya dengan aman antar thread. Sebagai contoh, kita akan melihat objek yang tidak dapat diubah dan tidak memiliki kewarganegaraan. Tampilan lain akan mencegah thread berbeda mengubah data, seperti variabel lokal thread. Anda dapat melihat semua kode sumber di Github . 1. Objek yang Tidak Dapat Diubah Objek yang tidak dapat diubah memiliki status (memiliki data yang merepresentasikan status objek), namun diatur pada waktu pembuatan di konstruktor, setelah instance objek dibuat dan status tidak dapat diubah. Meskipun thread dapat berganti-ganti, suatu objek masih mempunyai satu kemungkinan keadaan. Karena semua bidang bersifat read-only, tidak ada thread yang dapat mengubah data objek. Oleh karena itu, aliran yang tidak dapat diubah pada dasarnya aman untuk thread. Kelas Produk mendemonstrasikan kelas yang tidak dapat diubah. Itu mengisi semua bidangnya di konstruktor dan tidak ada satupun yang berubah: public final class Product { private final String id; private final String name; private final double price; public Product(String id, String name, double price) { this.id = id; this.name = name; this.price = price; } public String getId() { return this.id; } public String getName() { return this.name; } public double getPrice() { return this.price; } public String toString() { return new StringBuilder(this.id).append("-").append(this.name) .append(" (").append(this.price).append(")").toString(); } public boolean equals(Object x) { if (this == x) return true; if (x == null) return false; if (this.getClass() != x.getClass()) return false; Product that = (Product) x; if (!this.id.equals(that.id)) return false; if (!this.name.equals(that.name)) return false; if (this.price != that.price) return false; return true; } public int hashCode() { int hash = 17; hash = 31 * hash + this.getId().hashCode(); hash = 31 * hash + this.getName().hashCode(); hash = 31 * hash + ((Double) this.getPrice()).hashCode(); return hash; } } Dalam beberapa kasus, menjadikan bidang tersebut final tidak akan cukup. Misalnya, kelas MutableProduct tidak dapat diubah, meskipun semua bidang bersifat final: Mengapa kelas di atas tidak dapat diubah? Alasannya adalah kami mengizinkan referensi diambil dari suatu kelas. Bidang ' kategori ' adalah referensi yang bisa diubah, jadi setelah diterima, klien dapat memodifikasinya. Untuk mengilustrasikan hal ini, pertimbangkan program berikut: Dan keluaran konsol: Karena bidang 'kategori' dapat diubah dan diperoleh dari suatu objek, klien telah memodifikasi daftar ini. Sebuah objek yang seharusnya tidak dapat diubah telah diubah, menghasilkan keadaan baru. Jika Anda ingin merepresentasikan isi sebuah daftar, Anda dapat menggunakan representasi daftar yang tidak dapat diubah: 2. Objek Tanpa Status Objek tanpa status mirip dengan objek yang tidak dapat diubah, namun dalam hal ini objek tersebut tidak memiliki status, bahkan satu pun. Jika suatu objek adalah objek tanpa kewarganegaraan, maka objek tersebut tidak harus menyimpan data apa pun di antara panggilan. Karena tidak ada keadaan, tidak ada thread yang dapat mempengaruhi hasil thread lain dengan memanggil metode objek. Karena alasan ini, objek tanpa kewarganegaraan pada dasarnya aman untuk thread. Kelas ProductHandler adalah contoh objek jenis ini. Ini berisi beberapa operasi pada objek Produk, dan tidak menyimpan data apa pun di antara panggilan. Hasil operasi tidak bergantung pada panggilan sebelumnya atau data apa pun yang disimpan: Dalam metode sumCart, ProductHandler mengonversi daftar Produk menjadi array untuk digunakan dalam perulangan for-each untuk melakukan iterasi melalui semua elemen. Daftar iterator tidak aman untuk thread dan mungkin memunculkan ConcurrentModificationException jika ada perubahan selama iterasi. Tergantung pada kebutuhan Anda, Anda dapat memilih strategi yang berbeda . 3. Variabel Lokal Thread Variabel lokal thread adalah variabel-variabel yang didefinisikan dalam sebuah thread. Tidak ada thread lain yang melihatnya dan tidak akan mengubahnya. Tipe pertama adalah variabel lokal. Pada contoh di bawah, variabel total disimpan di tumpukan thread: public final class MutableProduct { private final String id; private final String name; private final double price; private final List categories = new ArrayList<>(); public MutableProduct(String id, String name, double price) { this.id = id; this.name = name; this.price = price; this.categories.add("A"); this.categories.add("B"); this.categories.add("C"); } public String getId() { return this.id; } public String getName() { return this.name; } public double getPrice() { return this.price; } public List getCategories() { return this.categories; } public List getCategoriesUnmodifiable() { return Collections.unmodifiableList(categories); } public String toString() { return new StringBuilder(this.id).append("-").append(this.name) .append(" (").append(this.price).append(")").toString(); } } public static void main(String[] args) { MutableProduct p = new MutableProduct("1", "a product", 43.00); System.out.println("Product categories"); for (String c : p.getCategories()) System.out.println(c); p.getCategories().remove(0); System.out.println("\nModified Product categories"); for (String c : p.getCategories()) System.out.println(c); } Product categories A B C Modified Product categories B C public List getCategoriesUnmodifiable() { return Collections.unmodifiableList(categories); } public class ProductHandler { private static final int DISCOUNT = 90; public Product applyDiscount(Product p) { double finalPrice = p.getPrice() * DISCOUNT / 100; return new Product(p.getId(), p.getName(), finalPrice); } public double sumCart(List cart) { double total = 0.0; for (Product p : cart.toArray(new Product[0])) total += p.getPrice(); return total; } } public double sumCart(List cart) { double total = 0.0; for (Product p : cart.toArray(new Product[0])) total += p.getPrice(); return total; } Ingatlah bahwa jika Anda mendefinisikan referensi alih-alih variabel primitif dan mengembalikannya, ia akan meninggalkan batasannya. Anda mungkin tidak tahu di mana tautan yang dikembalikan itu dibuat. Kode yang memanggil metode sumCart dapat menyimpannya dalam bidang statis dan memungkinkannya diakses oleh thread yang berbeda. Tipe kedua adalah kelas ThreadLocal . Kelas ini menyediakan penyimpanan independen untuk setiap thread. Nilai yang disimpan di ThreadLocal tersedia untuk kode apa pun di thread yang sama. Kelas ClientRequestId menunjukkan contoh penggunaan kelas ThreadLocale: Kelas ProductHandlerThreadLocal menggunakan ClientRequestId untuk mengembalikan ID yang dihasilkan sama pada thread yang sama: Saat menjalankan metode utama, output konsol akan menampilkan ID yang berbeda untuk setiap thread. Misalnya: Jika Anda akan menggunakan ThreadLocale, Anda harus memperhatikan beberapa risiko penggunaan saat menggabungkan thread (seperti dalam aplikasi server). Anda mungkin mendapatkan kebocoran memori atau kebocoran informasi di antara permintaan. Saya tidak akan membahasnya terlalu detail karena... Artikel “ Cara menembak diri sendiri dengan ThreadLocale ” menunjukkan dengan baik bagaimana hal ini bisa terjadi. 4. Menggunakan sinkronisasi Cara lain untuk menyediakan akses thread-safe ke objek adalah melalui sinkronisasi. Jika kita menyinkronkan semua akses ke suatu referensi, maka hanya satu objek thread yang akan mengaksesnya pada waktu tertentu. Hal ini akan kita bahas pada postingan selanjutnya. 5. Kesimpulan Kita melihat beberapa metode yang memungkinkan Anda membuat objek sederhana yang dapat diakses oleh banyak thread. Jauh lebih sulit untuk mencegah kesalahan multi-utas jika suatu objek dapat memiliki banyak status. Di sisi lain, jika suatu objek hanya dapat memiliki satu keadaan atau tidak sama sekali, kita tidak perlu khawatir tentang beberapa thread yang mengakses objek tersebut pada saat yang bersamaan. Yang asli ada di sini . public class ClientRequestId { private static final ThreadLocal id = new ThreadLocal () { @Override protected String initialValue() { return UUID.randomUUID().toString(); } }; public static String get() { return id.get(); } } public class ProductHandlerThreadLocal { //Same methods as in ProductHandler class public String generateOrderId() { return ClientRequestId.get(); } } T1 - 23dccaa2-8f34-43ec-bbfa-01cec5df3258 T2 - 936d0d9d-b507-46c0-a264-4b51ac3f527d T2 - 936d0d9d-b507-46c0-a264-4b51ac3f527d T3 - 126b8359-3bcc-46b9-859a-d305aff22c7e ...
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION