天天看點

java多線程 線程安全_Java中的線程安全

java多線程 線程安全

Thread Safety in Java is a very important topic. Java provides multi-threaded environment support using Java Threads, we know that multiple threads created from same Object share object variables and this can lead to data inconsistency when the threads are used to read and update the shared data.

Java中的線程安全是一個非常重要的主題。 Java使用Java線程提供了多線程環境支援,我們知道從同一個對象建立的多個線程共享對象變量,當這些線程用于讀取和更新共享資料時,這可能導緻資料不一緻 。

線程安全 (Thread Safety)

java多線程 線程安全_Java中的線程安全

The reason for data inconsistency is because updating any field value is not an atomic process, it requires three steps; first to read the current value, second to do the necessary operations to get the updated value and third to assign the updated value to the field reference.

資料不一緻的原因是因為更新任何字段值都不是原子過程,它需要三個步驟。 首先讀取目前值,其次進行必要的操作以擷取更新的值,第三次将更新的值配置設定給字段引用。

Let’s check this with a simple program where multiple threads are updating the shared data.

讓我們用一個簡單的程式檢查一下,其中多個線程正在更新共享資料。

package com.journaldev.threads;

public class ThreadSafety {

    public static void main(String[] args) throws InterruptedException {
    
        ProcessingThread pt = new ProcessingThread();
        Thread t1 = new Thread(pt, "t1");
        t1.start();
        Thread t2 = new Thread(pt, "t2");
        t2.start();
        //wait for threads to finish processing
        t1.join();
        t2.join();
        System.out.println("Processing count="+pt.getCount());
    }

}

class ProcessingThread implements Runnable{
    private int count;
    
    @Override
    public void run() {
        for(int i=1; i < 5; i++){
            processSomething(i);
        	count++;
        }
    }

    public int getCount() {
        return this.count;
    }

    private void processSomething(int i) {
        // processing some job
        try {
            Thread.sleep(i*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
}
           

In the above program for loop, count is incremented by 1 four times and since we have two threads, its value should be 8 after both the threads finished executing. But when you will run the above program multiple times, you will notice that count value is varying between 6,7,8. This is happening because even if count++ seems to be an atomic operation, its NOT and causing data corruption.

在上面的for循環程式中, count增加1到四倍,并且由于我們有兩個線程,是以兩個線程執行完後其值應為8。 但是當您多次運作上述程式時,您會注意到計數值在6,7,8之間變化。 發生這種情況是因為即使count ++似乎是一個原子操作,它的NOT也不會導緻資料損壞。

Java中的線程安全 (Thread Safety in Java)

Thread safety in java is the process to make our program safe to use in multithreaded environment, there are different ways through which we can make our program thread safe.

Java中的線程安全是使我們的程式在多線程環境中可以安全使用的過程,可以通過多種方法使程式線程安全。

  • Synchronization is the easiest and most widely used tool for thread safety in java.

    同步是Java中線程安全最簡單,使用最廣泛的工具。

  • Use of Atomic Wrapper classes from java.util.concurrent.atomic package. For example AtomicInteger

    從java.util.concurrent.atomic包使用Atomic Wrapper類。 例如AtomicInteger

  • Use of locks from java.util.concurrent.locks package.

    使用java.util.concurrent.locks包中的鎖。

  • Using thread safe collection classes, check this post for usage of ConcurrentHashMap for thread safety.

    使用線程安全收集類,請檢查此文章以了解ConcurrentHashMap的線程安全用法。

  • Using volatile keyword with variables to make every thread read the data from memory, not read from thread cache.

    将volatile關鍵字與變量一起使用,可使每個線程從記憶體中讀取資料,而不是從線程緩存中讀取資料。

Java同步 (Java synchronized)

Synchronization is the tool using which we can achieve thread-safety, JVM guarantees that synchronized code will be executed by only one thread at a time. java keyword synchronized is used to create synchronized code and internally it uses locks on Object or Class to make sure only one thread is executing the synchronized code.

同步是我們可以用來實作線程安全性的工具,JVM保證同步的代碼一次隻能由一個線程執行。 java關鍵字sync用于建立同步代碼,并且在内部使用Object或Class上的鎖來確定隻有一個線程在執行同步代碼。

  • Java synchronization works on locking and unlocking of the resource before any thread enters into synchronized code, it has to acquire the lock on the Object and when code execution ends, it unlocks the resource that can be locked by other threads. In the meantime, other threads are in wait state to lock the synchronized resource.

    Java同步可在任何線程進入同步代碼之前對資源進行鎖定和解鎖,它必須獲得對Object的鎖定,并且在代碼執行結束時,它會解鎖可以被其他線程鎖定的資源。 同時,其他線程處于等待狀态以鎖定同步資源。

  • We can use synchronized keyword in two ways, one is to make a complete method synchronized and another way is to create synchronized block.

    我們可以通過兩種方式使用synced關鍵字,一種是使完整的方法同步,另一種方法是建立同步塊。

  • When a method is synchronized, it locks the Object, if method is static it locks the Class, so it’s always best practice to use synchronized block to lock the only sections of method that needs synchronization.

    同步方法時,它會鎖定Object ;如果方法是靜态的,則它會鎖定Class ,是以,最佳做法始終是使用同步塊來鎖定方法中僅需要同步的部分。

  • While creating a synchronized block, we need to provide the resource on which lock will be acquired, it can be XYZ.class or any Object field of the class.

    建立同步塊時,我們需要提供擷取鎖定的資源,它可以是XYZ.class或該類的任何Object字段。

  • synchronized(this)

    will lock the Object before entering into the synchronized block.

    synchronized(this)

    将在進入同步塊之前鎖定對象。
  • You should use the lowest level of locking, for example, if there are multiple synchronized block in a class and one of them is locking the Object, then other synchronized blocks will also be not available for execution by other threads. When we lock an Object, it acquires a lock on all the fields of the Object.

    您應該使用最低級别的鎖定 ,例如,如果一個類中有多個同步塊,而其中一個正在鎖定Object,則其他同步塊也将不可用于其他線程執行。 當我們鎖定一個對象時,它獲得了對該對象所有字段的鎖定。

  • Java Synchronization provides data integrity on the cost of performance, so it should be used only when it’s absolutely necessary.

    Java同步以性能為代價提供資料完整性,是以僅在絕對必要時才應使用它。

  • Java Synchronization works only in the same JVM, so if you need to lock some resource in multiple JVM environment, it will not work and you might have to look after some global locking mechanism.

    Java同步僅在同一個JVM中起作用,是以,如果您需要在多個JVM環境中鎖定某些資源,則它将無法正常工作,是以您可能需要照顧一些全局鎖定機制。

  • Java Synchronization could result in deadlocks, check this post about deadlock in java and how to avoid them.

    Java同步可能會導緻死鎖,請檢視有關Java死鎖以及如何避免死鎖的文章。

  • Java synchronized keyword cannot be used for constructors and variables.

    Java同步關鍵字不能用于構造函數和變量。

  • It is preferable to create a dummy private Object to use for the synchronized block so that it’s reference can’t be changed by any other code. For example, if you have a setter method for Object on which you are synchronizing, it’s reference can be changed by some other code leads to the parallel execution of the synchronized block.

    最好建立一個虛拟私有對象用于同步塊,以使它的引用不能被任何其他代碼更改。 例如,如果您有一個要在其上同步的Object的setter方法,則可以通過其他一些代碼來更改其引用,進而導緻并行執行同步塊。

  • We should not use any object that is maintained in a constant pool, for example String should not be used for synchronization because if any other code is also locking on same String, it will try to acquire lock on the same reference object from String pool and even though both the codes are unrelated, they will lock each other.

    我們不應該使用在常量池中維護的任何對象,例如,不應将String用于同步,因為如果任何其他代碼也鎖定在同一String上,它将嘗試從String池擷取對同一引用對象的鎖定,并且即使兩個代碼無關,它們也會互相鎖定。

Here are the code changes we need to do in the above program to make it thread-safe.

這是我們在上述程式中需要執行的代碼更改,以使其具有線程安全性。

//dummy object variable for synchronization
    private Object mutex=new Object();
    ...
    //using synchronized block to read, increment and update count value synchronously
    synchronized (mutex) {
            count++;
    }
           

Let’s see some synchronization examples and what can we learn from them.

讓我們看看一些同步示例,以及我們可以從中學到什麼。

public class MyObject {
 
  // Locks on the object's monitor
  public synchronized void doSomething() { 
    // ...
  }
}
 
// Hackers code
MyObject myObject = new MyObject();
synchronized (myObject) {
  while (true) {
    // Indefinitely delay myObject
    Thread.sleep(Integer.MAX_VALUE); 
  }
}
           

Notice that hacker’s code is trying to lock the myObject instance and once it gets the lock, it’s never releasing it causing doSomething() method to block on waiting for the lock, this will cause the system to go on deadlock and cause Denial of Service (DoS).

請注意,黑客的代碼正在嘗試鎖定myObject執行個體,并且一旦獲得了鎖定,就永遠不會釋放它,進而導緻doSomething()方法在等待鎖定時阻塞,這将導緻系統進入死鎖并導緻拒絕服務( DoS)。

public class MyObject {
  public Object lock = new Object();
 
  public void doSomething() {
    synchronized (lock) {
      // ...
    }
  }
}

//untrusted code

MyObject myObject = new MyObject();
//change the lock Object reference
myObject.lock = new Object();
           

Notice that lock Object is public and by changing its reference, we can execute synchronized block parallel in multiple threads. A similar case is true if you have private Object but have a setter method to change its reference.

注意,鎖對象是公共的,并且通過更改其引用,我們可以在多個線程中并行執行同步塊。 如果您有私有Object但有一個setter方法來更改其引用,則情況類似。

public class MyObject {
  //locks on the class object's monitor
  public static synchronized void doSomething() { 
    // ...
  }
}
 
// hackers code
synchronized (MyObject.class) {
  while (true) {
    Thread.sleep(Integer.MAX_VALUE); // Indefinitely delay MyObject
  }
}
           

Notice that hacker code is getting a lock on the class monitor and not releasing it, it will cause deadlock and DoS in the system.

請注意,黑客代碼已在類螢幕上獲得鎖定,而沒有釋放它,這将導緻系統中的死鎖和DoS。

Here is another example where multiple threads are working on the same array of Strings and once processed, appending thread name to the array value.

這是另一個示例,其中多個線程正在相同的String數組上工作,并且一旦被處理,就将線程名附加到數組值中。

package com.journaldev.threads;

import java.util.Arrays;

public class SyncronizedMethod {

    public static void main(String[] args) throws InterruptedException {
        String[] arr = {"1","2","3","4","5","6"};
        HashMapProcessor hmp = new HashMapProcessor(arr);
        Thread t1=new Thread(hmp, "t1");
        Thread t2=new Thread(hmp, "t2");
        Thread t3=new Thread(hmp, "t3");
        long start = System.currentTimeMillis();
        //start all the threads
        t1.start();t2.start();t3.start();
        //wait for threads to finish
        t1.join();t2.join();t3.join();
        System.out.println("Time taken= "+(System.currentTimeMillis()-start));
        //check the shared variable value now
        System.out.println(Arrays.asList(hmp.getMap()));
    }

}

class HashMapProcessor implements Runnable{
    
    private String[] strArr = null;
    
    public HashMapProcessor(String[] m){
        this.strArr=m;
    }
    
    public String[] getMap() {
        return strArr;
    }

    @Override
    public void run() {
        processArr(Thread.currentThread().getName());
    }

    private void processArr(String name) {
        for(int i=0; i < strArr.length; i++){
            //process data and append thread name
            processSomething(i);
            addThreadName(i, name);
        }
    }
    
    private void addThreadName(int i, String name) {
        strArr[i] = strArr[i] +":"+name;
    }

    private void processSomething(int index) {
        // processing some job
        try {
            Thread.sleep(index*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
}
           

Here is the output when I run the above program.

這是我運作上述程式時的輸出。

Time taken= 15005
[1:t2:t3, 2:t1, 3:t3, 4:t1:t3, 5:t2:t1, 6:t3]
           

The String array values are corrupted because of shared data and no synchronization. Here is how we can change addThreadName() method to make our program thread-safe.

由于共享資料且沒有同步,是以String數組值已損壞。 這是我們如何更改addThreadName()方法以使程式具有線程安全性的方法。

private Object lock = new Object();
    private void addThreadName(int i, String name) {
        synchronized(lock){
        strArr[i] = strArr[i] +":"+name;
        }
    }
           

After this change, our program works fine and here is the correct output of the program.

進行此更改後,我們的程式可以正常工作,這是程式的正确輸出。

Time taken= 15004
[1:t1:t2:t3, 2:t2:t1:t3, 3:t2:t3:t1, 4:t3:t2:t1, 5:t2:t1:t3, 6:t2:t1:t3]
           

That’s all for thread safety in java, I hope you learned about thread-safe programming and using synchronized keyword.

這就是Java中線程安全的全部内容,希望您了解線程安全程式設計以及如何使用synced關鍵字。

翻譯自: https://www.journaldev.com/1061/thread-safety-in-java

java多線程 線程安全