Tại sao lớp này không an toàn cho luồng?


94
class ThreadSafeClass extends Thread
{
     private static int count = 0;

     public synchronized static void increment()
     {
         count++;
     }

     public synchronized void decrement()
     {
         count--;
     }
}

Bất cứ ai có thể giải thích tại sao lớp trên không an toàn luồng?


6
Tôi không biết về Java, nhưng có vẻ như mỗi phương thức đó đều an toàn theo luồng riêng lẻ , nhưng bạn có thể có một luồng trong mỗi phương thức cùng một lúc. Có thể nếu bạn có một phương thức duy nhất sử dụng bool ( increment) thì nó sẽ an toàn. Hoặc nếu bạn đã sử dụng một số đối tượng khóa. Như tôi đã nói, tôi không biết về Java - nhận xét của tôi bắt nguồn từ kiến ​​thức C #.
Wai Ha Lee


Tôi cũng không biết rõ về Javavery, nhưng để đồng bộ hóa quyền truy cập vào một biến tĩnh, synchronizedchỉ nên sử dụng trong các phương thức tĩnh. Vì vậy, trong opionion của tôi ngay cả khi bạn loại bỏ incrementphương thức, nó vẫn không an toàn vì hai trường hợp (chỉ có quyền truy cập đồng bộ thông qua cùng một trường hợp) có thể gọi phương thức đồng thời.
Onur

4
Nó là luồng an toàn miễn là bạn không bao giờ tạo một thể hiện của lớp.
Benjamin Gruenbaum

1
Tại sao bạn nghĩ rằng nó không phải là chủ đề an toàn.
Raedwald

Câu trả lời:


134

incrementphương thức là staticnó sẽ đồng bộ hóa trên đối tượng lớp cho ThreadSafeClass. Các decrementphương pháp không phải là tĩnh và sẽ đồng bộ hóa trên các trường hợp sử dụng để gọi nó. Tức là, chúng sẽ đồng bộ hóa trên các đối tượng khác nhau và do đó hai luồng khác nhau có thể thực thi các phương thức cùng một lúc. Vì các hoạt động ++--không phải là nguyên tử nên lớp không an toàn cho luồng.

Ngoài ra, vì countstatic, sửa đổi nó từ decrementmà là một đồng bộ dụ phương pháp là không an toàn vì nó có thể được gọi vào trường hợp khác nhau và thay đổi countđồng thời như vậy.


12
Bạn có thể thêm, vì countstatic, có một phương pháp dụ decrement()là sai, thậm chí nếu không có static increment()phương pháp, như hai luồng có thể gọi decrement()về trường hợp khác nhau thay đổi bộ đếm cùng đồng thời.
Holger

1
Đó có thể là một lý do chính đáng để thích sử dụng synchronizedcác khối nói chung (thậm chí trên toàn bộ nội dung phương thức) thay vì sử dụng công cụ sửa đổi trên phương thức, tức là synchronized(this) { ... }synchronized(ThreadSafeClass.class) { ... }.
Bruno

++--không phải là nguyên tử, ngay cả trênvolatile int . Synchronizedgiải quyết vấn đề đọc / cập nhật / ghi với ++/ --, nhưng statictừ khóa là chìa khóa ở đây. Câu trả lời tốt!
Chris Cirefice

Re, việc sửa đổi [một trường tĩnh] từ ... một phương thức thể hiện được đồng bộ hóa là sai : Không có gì sai khi truy cập một biến tĩnh từ bên trong một phương thức thể hiện và cũng không có gì sai khi truy cập nó từ một synchronizedphương thức thể hiện. Chỉ cần không mong đợi "đồng bộ hóa" trên phương thức cá thể để cung cấp bảo vệ cho dữ liệu tĩnh. Vấn đề duy nhất ở đây là những gì bạn đã nói trong đoạn đầu tiên của mình: Các phương pháp sử dụng các khóa khác nhau để cố gắng bảo vệ cùng một dữ liệu và tất nhiên điều đó không cung cấp sự bảo vệ nào cả.
Solomon Slow

23

Bạn có hai phương thức được đồng bộ hóa, nhưng một trong số chúng là tĩnh và phương thức kia thì không. Khi truy cập một phương thức được đồng bộ hóa, dựa trên kiểu của nó (tĩnh hoặc không tĩnh), một đối tượng khác sẽ bị khóa. Đối với một phương thức tĩnh, một khóa sẽ được đặt trên đối tượng Class, trong khi đối với khối không tĩnh, một khóa sẽ được đặt trên thể hiện của lớp chạy phương thức. Bởi vì bạn có hai đối tượng bị khóa khác nhau, bạn có thể có hai luồng sửa đổi cùng một đối tượng đồng thời.


14

Bất cứ ai có thể giải thích tại sao lớp trên không an toàn luồng?

  • increment là tĩnh, đồng bộ hóa sẽ được thực hiện trên chính lớp đó.
  • decrementkhông phải là tĩnh, đồng bộ hóa sẽ được thực hiện trên khởi tạo đối tượng, nhưng điều đó không bảo mật bất cứ điều gì countlà tĩnh.

Tôi muốn thêm điều đó để khai báo một bộ đếm an toàn luồng, tôi tin rằng cách đơn giản nhất là sử dụng AtomicIntegerthay vì một int nguyên thủy.

Hãy để tôi chuyển hướng bạn đến java.util.concurrent.atomicthông tin gói.


7

Câu trả lời của những người khác là khá tốt giải thích lý do. Tôi chỉ thêm một cái gì đó để tóm tắt synchronized:

public class A {
    public synchronized void fun1() {}

    public synchronized void fun2() {}

    public void fun3() {}

    public static synchronized void fun4() {}

    public static void fun5() {}
}

A a1 = new A();

synchronizedtrên fun1fun2được đồng bộ hóa ở cấp đối tượng cá thể. synchronizedtrên fun4được đồng bộ hóa ở cấp độ đối tượng lớp. Nghĩa là:

  1. Khi 2 cuộc gọi a1.fun1()cùng lúc, cuộc gọi sau sẽ bị chặn.
  2. Khi cuộc gọi luồng 1 a1.fun1()và cuộc gọi luồng 2 a1.fun2()cùng một lúc, cuộc gọi sau sẽ bị chặn.
  3. Khi luồng 1 gọi a1.fun1()và luồng 2 gọi a1.fun3()cùng một lúc, không bị chặn, 2 phương thức sẽ được thực hiện cùng lúc.
  4. Khi luồng 1 cuộc gọi A.fun4(), nếu các luồng khác gọi A.fun4()hoặc A.fun5()đồng thời, các cuộc gọi sau sẽ bị chặn vì synchronizedtrên fun4là cấp độ lớp.
  5. Khi luồng 1 gọi A.fun4(), luồng 2 gọi a1.fun1()cùng lúc, không chặn thì 2 phương thức sẽ được thực hiện cùng lúc.

6
  1. decrementđang khóa một thứ khác để incrementchúng không ngăn cản nhau chạy.
  2. Gọi decrementtrên một cá thể đang khóa một thứ khác với việc gọi decrementtrên một cá thể khác, nhưng chúng đang ảnh hưởng đến cùng một thứ.

Điều đầu tiên có nghĩa là các cuộc gọi chồng chéo đến incrementdecrementcó thể dẫn đến việc hủy bỏ (đúng), tăng hoặc giảm.

Điều thứ hai có nghĩa là hai lệnh gọi chồng lên nhau decrementtrên các trường hợp khác nhau có thể dẫn đến giảm kép (đúng) hoặc giảm một.


4

Vì hai phương thức khác nhau, một là mức cá thể và một là mức lớp, vì vậy bạn cần phải khóa trên 2 đối tượng khác nhau để tạo ThreadSafe


1

Như đã giải thích trong các câu trả lời khác, mã của bạn không an toàn Chủ đề vì phương thức tĩnh increment()khóa Trình theo dõi lớp và decrement()khóa phương thức không tĩnh Trình theo dõi đối tượng.

Đối với ví dụ mã này, giải pháp tốt hơn tồn tại mà không cần synchronzedsử dụng từ khóa. Bạn phải sử dụng AtomicInteger để đạt được sự an toàn của Thread.

Chủ đề an toàn bằng cách sử dụng AtomicInteger:

import java.util.concurrent.atomic.AtomicInteger;

class ThreadSafeClass extends Thread {

    private static AtomicInteger count = new AtomicInteger(0);

    public static void increment() {
        count.incrementAndGet();
    }

    public static void decrement() {
        count.decrementAndGet();
    }

    public static int value() {
        return count.get();
    }

}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.