Core Java Tutorial -- Thread Safety and Java Synchronization
2018-03-30 22:42
417 查看
Java 中的线程安全是一个重要的主题。Java 提供多线程环境支持 Java 线程,我们知道多线程由同一个 Object 创建共享对象变量,并且当线程用于读取和更新共享数据,可能会导致数据不一致。
让我们来用一个简单的程序来看这个问题,其中多个线程正在更新共享数据。
上面的循环程序中,count 增加了 4 倍,并且由于我们由两个线程,所以线程完成执行后值应该是 8。但是当你多次运行程序时,你会发现计数值在 6 7 8 之间变化。发生这种情况是因为即使 count ++ 看起来是一个原子操作,其实并不是并且会导致数据损坏。
Synchronization 是 Java 线程安全最简单并且最广泛使用的工具。
使用
使用
使用线程安全集合类,查看 ConcurrentHashMap 文章。
volatle 关键字与变量一起使用,使每个线程都从内存中读取数据,而不是从线程缓存中读取数据。
Java 同步对资源的锁定和解锁起作用,在任何线程进入同步代码之前,必须获取对象的锁定,并且在代码执行结束时,解锁可被其他线程锁定的资源。同时其他线程处于等待状态以锁定同步资源。
我们可以通过两种方式使用 synchronized 关键字,一种是使完整的方法同步,另一种使创建同步块。
当一个方法是 synchronized,它锁定 Object,如果方法是静态的,它会锁定 Class,所以最好使用 synchronized 块来锁定需要同步的方法的唯一部分。
当创建同步块时,我们需要提供获取锁的资源,它可以是 XYZ.class 或类的任何对象字段。
你应该使用最低级别的锁定,栗如,如果某个类中由多个同步块,并且其中一个锁定了对象,则其他同步块也将不可供其他线程执行。当我们锁定一个对象时,它会获取该对象所有字段的锁定。
Java 同步损失性能来提供数据完整性,因此只有在绝对必要时才应使用它。
Java 同步只能在同一个 JVM 中工作,所以如果你需要在多个JVM环境中锁定一些资源,它将不起作用,你可能不得不照看一些全局锁定机制。
Java 同步可能导致死锁,请查看 (Java 死锁以及如何避免)[]https://www.journaldev.com/1058/deadlock-in-java-example]
Java synchronized 关键字不能被用于构造函数和变量。
最好创建一个用于同步块的虚拟私有对象,以便它的引用不能被任何其他代码修改。栗如:如果你有一个你正在同步的 Object 的 setter 方法,那么它的引用可以被一些其他的代码导致并行执行 synchronized 块。
我们不应该使用在常量池中维护的任何对象,栗如,字符串不应该用于同步,因为如果任何其他代码也锁定在同一个字符串上,它将尝试从字符串池获取相同引用对象的锁定,并且即使两个代码无关,他们也会互相锁定。
下面是我们在上面的程序中需要做的代码更改,以使它线程安全。
让我们看看一些同步的例子,我们可以从中学到什么。
注意,Hackers code 试图锁定 myObject 实例,一旦它获得锁定,它就不会释放它,从而导致doSomething() 方法在等待锁定时阻塞,这会导致系统进入死锁并导致 Denial of Service(DoS)。
注意 lock 对象是公共的,通过改变它的引用,我们可以在多个线程中执行并行块同步。类似的情况,如果你有私有对象,但有 setter 方法来改变它的引用。
请注意,hackers code 正在获取类监视器上的锁定而不释放它,这将导致系统中的死锁和 DoS 。
这里是另一个例子,其中多个线程在相同的字符串数组上工作,并且一旦处理完毕,将线程名称附加到数组值。
输出:
字符串数组值因为共享数据而没有同步而被损坏。 以下是我们如何更改 addThreadName() 方法以使我们的程序线程安全。
改变后的输出:
Thread Safety
数据不一致的原因是因为更新任何字段的值不是一个原子操作,它需要三个步骤:首先获取当前值、其次要做必要的操作以获取更新的值,第三步将更新的值分配给字段引用。让我们来用一个简单的程序来看这个问题,其中多个线程正在更新共享数据。
package Thread; public class ThreadSafety { public static void main(String[] args) { ProcessingThread processingThread = new ProcessingThread(); Thread t1 = new Thread(processingThread); t1.start(); Thread t2 = new Thread(processingThread); t2.start(); // wait for threads to finish processing try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Processing count = " + processingThread.getCount()); } } class ProcessingThread implements Runnable { private int count; @Override public void run() { for (int i = 0; i < 5; i++) { processSomething(i); count++; } } public int getCount() { return count; } private void processSomething(int i) { // processing some job try { Thread.sleep(i * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
上面的循环程序中,count 增加了 4 倍,并且由于我们由两个线程,所以线程完成执行后值应该是 8。但是当你多次运行程序时,你会发现计数值在 6 7 8 之间变化。发生这种情况是因为即使 count ++ 看起来是一个原子操作,其实并不是并且会导致数据损坏。
Thread Safety in Java
Java 中的线程安全,是让我们的程序在多线程环境中安全使用的过程,我们可以通过不同的方式使我们的程序线程安全。Synchronization 是 Java 线程安全最简单并且最广泛使用的工具。
使用
java.util.concurrent.atomic包的原子包装类。栗如
AtomicInteger。
使用
java.util.concurrent.locks包中的锁。
使用线程安全集合类,查看 ConcurrentHashMap 文章。
volatle 关键字与变量一起使用,使每个线程都从内存中读取数据,而不是从线程缓存中读取数据。
Java synchronized
同步是我们可以实现线程安全的工具,JVM 保证同步代码一次只能由一个线程执行。Java 关键字 synchonized 用于创建同步代码,并在内部使用 Object 或 Class 上的锁来确保只有一个线程正在执行同步代码。Java 同步对资源的锁定和解锁起作用,在任何线程进入同步代码之前,必须获取对象的锁定,并且在代码执行结束时,解锁可被其他线程锁定的资源。同时其他线程处于等待状态以锁定同步资源。
我们可以通过两种方式使用 synchronized 关键字,一种是使完整的方法同步,另一种使创建同步块。
当一个方法是 synchronized,它锁定 Object,如果方法是静态的,它会锁定 Class,所以最好使用 synchronized 块来锁定需要同步的方法的唯一部分。
当创建同步块时,我们需要提供获取锁的资源,它可以是 XYZ.class 或类的任何对象字段。
synchronized(this)将在进入同步块之前锁定对象。
你应该使用最低级别的锁定,栗如,如果某个类中由多个同步块,并且其中一个锁定了对象,则其他同步块也将不可供其他线程执行。当我们锁定一个对象时,它会获取该对象所有字段的锁定。
Java 同步损失性能来提供数据完整性,因此只有在绝对必要时才应使用它。
Java 同步只能在同一个 JVM 中工作,所以如果你需要在多个JVM环境中锁定一些资源,它将不起作用,你可能不得不照看一些全局锁定机制。
Java 同步可能导致死锁,请查看 (Java 死锁以及如何避免)[]https://www.journaldev.com/1058/deadlock-in-java-example]
Java synchronized 关键字不能被用于构造函数和变量。
最好创建一个用于同步块的虚拟私有对象,以便它的引用不能被任何其他代码修改。栗如:如果你有一个你正在同步的 Object 的 setter 方法,那么它的引用可以被一些其他的代码导致并行执行 synchronized 块。
我们不应该使用在常量池中维护的任何对象,栗如,字符串不应该用于同步,因为如果任何其他代码也锁定在同一个字符串上,它将尝试从字符串池获取相同引用对象的锁定,并且即使两个代码无关,他们也会互相锁定。
下面是我们在上面的程序中需要做的代码更改,以使它线程安全。
//dummy object variable for synchronization private Object mutex=new Object(); ... //using synchronized block to read, increment and update count value synchronously synchronized (mutex) { count++; }
让我们看看一些同步的例子,我们可以从中学到什么。
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); } }
注意,Hackers code 试图锁定 myObject 实例,一旦它获得锁定,它就不会释放它,从而导致doSomething() 方法在等待锁定时阻塞,这会导致系统进入死锁并导致 Denial of Service(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();
注意 lock 对象是公共的,通过改变它的引用,我们可以在多个线程中执行并行块同步。类似的情况,如果你有私有对象,但有 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 } }
请注意,hackers code 正在获取类监视器上的锁定而不释放它,这将导致系统中的死锁和 DoS 。
这里是另一个例子,其中多个线程在相同的字符串数组上工作,并且一旦处理完毕,将线程名称附加到数组值。
package Thread; import java.util.Arrays; public class SynchronizedMethod { 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(); } } }
输出:
[1:t1, 2:t1:t2:t3, 3:t1:t2:t3, 4:t2:t3, 5:t1:t3, 6:t2:t1:t3]
字符串数组值因为共享数据而没有同步而被损坏。 以下是我们如何更改 addThreadName() 方法以使我们的程序线程安全。
private Object lock = new Object(); private void addThreadName(int i, String name) { synchronized(lock){ strArr[i] = strArr[i] +":"+name; } }
改变后的输出:
[1:t1:t2:t3, 2:t1:t3:t2, 3:t1:t3:t2, 4:t2:t3:t1, 5:t2:t3:t1, 6:t1:t2:t3]
相关文章推荐
- Core Java Tutorial -- Thread Safety in Singleton Class
- 【Java多线程】之六:Synchronization and Thread Safety
- Core Java Tutorial -- Thread wait, notify and notifyAll
- Core Java Tutorial -- Daemon Thread
- JAVA Final KeyWord And Thread Safety
- Core Java Tutorial -- Thread Join
- ThreadLocal in Java - Example Program and Tutorial
- Boost Thread and Synchronization Tutorial
- Difference between start and run method in Thread – Java Tutorial
- Java并发编程(Java Concurrency)(9)- 线程安全与共享资源(Thread Safety and Shared Resources)
- Core Java Tutorial -- Thread.sleep()
- ACE线程安全与同步(Thread Safety and Synchronization)
- Java并发编程(Java Concurrency)(10)- 线程安全与不变性(Thread Safety and Immutability)
- Core Java Tutorial -- Thread Life Cycle
- ACE线程安全与同步(Thread Safety and Synchronization)
- Java Program to print Prime numbers in Java - Example Tutorial and Code
- Java theory and practice: Thread pools and work queues
- The Java EE 6 Tutorial Filtering Requests and Responses
- java8-concurrency-tutorial-thread-executor-examples
- Java 8 Date Time API Example Tutorial – LocalDate, Instant, LocalDateTime, Parse and Format