Làm cách nào để đồng bộ hóa một biến tĩnh giữa các luồng chạy các phiên bản khác nhau của một lớp trong Java?


120

Tôi biết rằng việc sử dụng synchronizetừ khóa trước một phương thức mang lại sự đồng bộ hóa cho đối tượng đó. Tức là 2 luồng chạy cùng một thể hiện của đối tượng sẽ được đồng bộ hóa.

Tuy nhiên, vì đồng bộ hóa ở cấp độ đối tượng, 2 luồng chạy các phiên bản khác nhau của đối tượng sẽ không được đồng bộ hóa. Nếu chúng ta có một biến static trong một lớp Java được gọi bởi phương thức này, chúng ta muốn nó được đồng bộ hóa giữa các thể hiện của lớp. Hai phiên bản đang chạy trong 2 luồng khác nhau.

Chúng ta có thể đạt được sự đồng bộ theo cách sau không?

public class Test  
{  
   private static int count = 0;  
   private static final Object lock= new Object();    
   public synchronized void foo() 
  {  
      synchronized(lock)
     {  
         count++;  
     }  
  }  
}

Có đúng là vì chúng ta đã xác định một đối tượng locklà static và chúng ta đang sử dụng từ khóa synchronizedcho khóa đó, biến static counthiện được đồng bộ hóa giữa các thể hiện của lớp Testkhông?


4
tất cả các câu trả lời này đều KHÔNG DÙNG được trừ khi đối tượng khóa được khai báo là CUỐI CÙNG!

Ngoài ra, hãy xem java.util.concurrent.atomic.AtomicInteger
RoundSparrow hilltx

Câu trả lời:


193

Có một số cách để đồng bộ hóa quyền truy cập vào một biến tĩnh.

  1. Sử dụng một phương pháp tĩnh được đồng bộ hóa. Điều này đồng bộ hóa trên đối tượng lớp.

    public class Test {
        private static int count = 0;
    
        public static synchronized void incrementCount() {
            count++;
        }
    } 
  2. Đồng bộ hóa rõ ràng trên đối tượng lớp.

    public class Test {
        private static int count = 0;
    
        public void incrementCount() {
            synchronized (Test.class) {
                count++;
            }
        }
    } 
  3. Đồng bộ hóa trên một số đối tượng tĩnh khác.

    public class Test {
        private static int count = 0;
        private static final Object countLock = new Object();
    
        public void incrementCount() {
            synchronized (countLock) {
                count++;
            }
        }
    } 

Phương pháp 3 là tốt nhất trong nhiều trường hợp vì đối tượng khóa không bị lộ ra bên ngoài lớp của bạn.


1
1. cái đầu tiên thậm chí không cần một đối tượng khóa, nó không phải là tốt nhất?
Baiyan Huang

4
2. khai báo số đếm như biến cũng sẽ hoạt động, vì biến đảm bảo biến được đồng bộ hóa.
Baiyan Huang

9
Lý do số 3 là tốt nhất là bất kỳ bit mã ngẫu nhiên nào cũng có thể đồng bộ hóa Test.classvà có khả năng làm hỏng ngày của bạn. Ngoài ra, quá trình khởi tạo lớp chạy với một khóa trên lớp được tổ chức, vì vậy nếu bạn có những bộ khởi tạo lớp điên rồ, bạn có thể tự làm mình đau đầu. volatilekhông giúp ích gì count++vì đó là một chuỗi đọc / sửa đổi / ghi. Như đã lưu ý trong một câu trả lời khác, java.util.concurrent.atomic.AtomicIntegercó thể là lựa chọn đúng ở đây.
fadden

4
Đừng quên đồng bộ hóa thao tác đọc trên số đếm nếu bạn muốn đọc giá trị chính xác như được đặt bởi các luồng khác. Khai báo nó dễ bay hơi (ngoài việc ghi đồng bộ) cũng sẽ giúp ích cho việc này.
n0rm1e

1
@Ferrybig không, bạn đang khóa Test.class. thissẽ là khóa cho đồng bộ không tĩnh phương pháp
user3237736

64

Nếu bạn chỉ đơn giản là chia sẻ bộ đếm, hãy cân nhắc sử dụng AtomicInteger hoặc một lớp phù hợp khác từ gói java.util.concurrent.atomic:

public class Test {

    private final static AtomicInteger count = new AtomicInteger(0); 

    public void foo() {  
        count.incrementAndGet();
    }  
}

3
Nó có sẵn trong java 1.5, không có trong 1.6.
Pawel

4

Vâng, nó là sự thật.

Nếu bạn tạo hai phiên bản của lớp

Test t1 = new Test();
Test t2 = new Test();

Sau đó, t1.foo và t2.foo đều đồng bộ hóa trên cùng một đối tượng tĩnh và do đó chặn lẫn nhau.


một cái sẽ chặn nhau, không phải nhau cùng một lúc nếu được quan tâm.
Jafar Ali

0

Bạn có thể đồng bộ hóa mã của mình qua lớp học. Đó sẽ là đơn giản nhất.

   public class Test  
    {  
       private static int count = 0;  
       private static final Object lock= new Object();    
       public synchronized void foo() 
      {  
          synchronized(Test.class)
         {  
             count++;  
         }  
      }  
    }

Hy vọng bạn thấy câu trả lời này hữu ích.


2
Điều này sẽ hoạt động, nhưng như đã đề cập ở nơi khác của @Fadden, hãy cẩn thận rằng bất kỳ luồng nào khác cũng có thể đồng bộ hóa Test.classvà ảnh hưởng đến hành vi. Đây là lý do tại sao đồng bộ hóa trên lockcó thể được ưu tiên.
sbk

Những gì bạn đang nói là chính xác. Đó là lý do tại sao tôi đề cập rõ ràng rằng ở trên là cách tiếp cận đơn giản nhất.
Jafar Ali

0

Chúng ta cũng có thể sử dụng ReentrantLock để đạt được sự đồng bộ hóa cho các biến tĩnh.

public class Test {

    private static int count = 0;
    private static final ReentrantLock reentrantLock = new ReentrantLock(); 
    public void foo() {  
        reentrantLock.lock();
        count = count + 1;
        reentrantLock.unlock();
    }  
}
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.