Java 中的线程安全

Java 使用 Java Threads 提供多线程环境支持,我们知道由相同的 Object share 对象变量创建的多个线程,这可能会导致数据不一致,当线程用于读取和更新共享数据时。

威胁安全

thread safe, thread safety, thread safety in java, thread safe 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.

 1package com.journaldev.threads;
 2
 3public class ThreadSafety {
 4
 5    public static void main(String[] args) throws InterruptedException {
 6
 7        ProcessingThread pt = new ProcessingThread();
 8        Thread t1 = new Thread(pt, "t1");
 9        t1.start();
10        Thread t2 = new Thread(pt, "t2");
11        t2.start();
12        //wait for threads to finish processing
13        t1.join();
14        t2.join();
15        System.out.println("Processing count="+pt.getCount());
16    }
17
18}
19
20class ProcessingThread implements Runnable{
21    private int count;
22
23    @Override
24    public void run() {
25        for(int i=1; i < 5; i++){
26            processSomething(i);
27        	count++;
28        }
29    }
30
31    public int getCount() {
32        return this.count;
33    }
34
35    private void processSomething(int i) {
36        // processing some job
37        try {
38            Thread.sleep(i*1000);
39        } catch (InterruptedException e) {
40            e.printStackTrace();
41        }
42    }
43
44}

在上面的循环程序中, count 增加了 1 四倍,因为我们有两个线程,它的值应该在两个线程完成执行后为 8 但是当你运行上面的程序多次时,你会注意到计数值在 6.7.8 之间变化。

Java 中的安全威胁

java 的线程安全是使我们的程序在多线程环境中安全使用的过程,有不同的方法可以使我们的程序线程安全。

  • 同步是用于 Java 的最简单和最广泛的线程安全工具.
  • 使用来自 java.util.concurrent.atomic 包的 Atomic Wrapper 类,例如 AtomicInteger
  • 使用来自 java.util.concurrent.locks 包的锁
  • 使用线程安全收集类,请检查本文使用 ConcurrentHashMap 用于线程安全。

Java 同步

同步是我们可以实现线程安全的工具,JVM保证同步代码一次只能由一个线程执行。 java keyword synchronized用于创建同步代码,并内部使用对象或类锁定,以确保只有一个线程执行同步代码。

  • 联合国 Java同步在任何线程进入同步代码前都会对资源进行锁定和解锁,它必须获得对象上的锁,当代码执行结束时,会解锁其他线程可以锁定的资源. 在此期间,其他线程处于等待状态以锁定同步资源. (_) ( )* 我们可以以两种方式使用同步关键词,一种是使方法完全同步,另一种是创建同步块.
  • 当一种方法同步时,它会锁起Object,如果方法是静态的,它会锁起Class,所以使用_同步块_锁定方法中唯一需要同步的部分总是最佳做法. (_) ( )* 在创建同步块的同时,我们需要提供将获取锁定的资源,可以是XYZ. 类或该类的任何对象字段 。
  • `同步(此)' 将锁定对象,然后输入同步块 。
  • 例如,如果一个类中有多个同步块,其中一个正在锁定对象,那么其他同步块也会无法由其他线程执行。 当我们锁定对象时,它会获取对象所有字段的锁.
  • Java同步提供了性能成本的数据完整性,因此只有在绝对必要时才应使用. () ( )* Java同步只在同一个JVM中工作,所以如果需要在多个JVM环境中锁定一些资源,则将不起作用,你可能必须关注一些全球锁定机制. () ( )* Java Syncronization 可能导致僵局, 请检查此帖子是否 [死在java 以及如何避免它们] (/ community/touristics/ deadlock- in-java-example) "Java Deadlock Example and How to analysis dialing situation"). (- ) * (中文(简体) ). Java 同步关键字不能用于构造器和变量 。 () ( )* 更可取的做法是创建一个用于同步块的假冒私用对象,这样它就无法被其他代码所改变. 例如,如果您对同步对象有设定方法,则引用可以被其它一些代码所更改,导致同步块的并行执行. () ( )* 我们不应使用任何在常数池中保持的物体,例如字符串不应用于同步,因为如果其他代码也锁定在同一字符串上,它会试图从string pool获取同一参考对象的锁定. "Java String Pool是什么?"),虽然两者代码无关,但都会互相锁上. ( (英语)

以下是我们需要在上面的程序中进行的代码更改,以使其安全。

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

让我们看看一些同步的例子,我们可以从中学到什么。

 1public class MyObject {
 2
 3  // Locks on the object's monitor
 4  public synchronized void doSomething() { 
 5    // ...
 6  }
 7}
 8
 9// Hackers code
10MyObject myObject = new MyObject();
11synchronized (myObject) {
12  while (true) {
13    // Indefinitely delay myObject
14    Thread.sleep(Integer.MAX_VALUE); 
15  }
16}

请注意,黑客的代码正在试图锁定 myObject 实例,一旦它得到锁定,它永远不会释放它,导致 doSomething() 方法阻止等待锁定,这将导致系统进入僵局并导致服务拒绝(DoS)。

 1public class MyObject {
 2  public Object lock = new Object();
 3
 4  public void doSomething() {
 5    synchronized (lock) {
 6      // ...
 7    }
 8  }
 9}
10
11//untrusted code
12
13MyObject myObject = new MyObject();
14//change the lock Object reference
15myObject.lock = new Object();

请注意,锁定对象是公共的,通过更改其引用,我们可以在多个线程中并行执行同步区块。

 1public class MyObject {
 2  //locks on the class object's monitor
 3  public static synchronized void doSomething() { 
 4    // ...
 5  }
 6}
 7
 8// hackers code
 9synchronized (MyObject.class) {
10  while (true) {
11    Thread.sleep(Integer.MAX_VALUE); // Indefinitely delay MyObject
12  }
13}

请注意,黑客代码在类监视器上被锁定,而不释放它,这将导致系统中的僵局和DoS。

 1package com.journaldev.threads;
 2
 3import java.util.Arrays;
 4
 5public class SyncronizedMethod {
 6
 7    public static void main(String[] args) throws InterruptedException {
 8        String[] arr = {"1","2","3","4","5","6"};
 9        HashMapProcessor hmp = new HashMapProcessor(arr);
10        Thread t1=new Thread(hmp, "t1");
11        Thread t2=new Thread(hmp, "t2");
12        Thread t3=new Thread(hmp, "t3");
13        long start = System.currentTimeMillis();
14        //start all the threads
15        t1.start();t2.start();t3.start();
16        //wait for threads to finish
17        t1.join();t2.join();t3.join();
18        System.out.println("Time taken= "+(System.currentTimeMillis()-start));
19        //check the shared variable value now
20        System.out.println(Arrays.asList(hmp.getMap()));
21    }
22
23}
24
25class HashMapProcessor implements Runnable{
26
27    private String[] strArr = null;
28
29    public HashMapProcessor(String[] m){
30        this.strArr=m;
31    }
32
33    public String[] getMap() {
34        return strArr;
35    }
36
37    @Override
38    public void run() {
39        processArr(Thread.currentThread().getName());
40    }
41
42    private void processArr(String name) {
43        for(int i=0; i < strArr.length; i++){
44            //process data and append thread name
45            processSomething(i);
46            addThreadName(i, name);
47        }
48    }
49
50    private void addThreadName(int i, String name) {
51        strArr[i] = strArr[i] +":"+name;
52    }
53
54    private void processSomething(int index) {
55        // processing some job
56        try {
57            Thread.sleep(index*1000);
58        } catch (InterruptedException e) {
59            e.printStackTrace();
60        }
61    }
62
63}

以下是当我运行上述程序时的输出。

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

字符串数值是损坏的,因为共享的数据和没有同步. 这里是我们如何改变 addThreadName() 方法,使我们的程序 thread-safe。

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

在这种变化之后,我们的程序运行良好,这里是程序的正确输出。

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

对于 Java 的线程安全而言,我希望您了解了线程安全编程和使用同步的关键字。

Published At
Categories with 技术
Tagged with
comments powered by Disqus