龙空技术网

Java 中的线程安全

农非农 108

前言:

现在看官们对“java的线程安全是什么意思”可能比较讲究,姐妹们都需要知道一些“java的线程安全是什么意思”的相关文章。那么小编在网摘上汇集了一些有关“java的线程安全是什么意思””的相关资讯,希望小伙伴们能喜欢,各位老铁们快快来学习一下吧!

Java 中的线程安全是一个非常重要的主题。Java 使用 Java 线程提供多线程环境支持,我们知道从同一对象创建的多个线程共享对象变量,当线程用于读取和更新共享数据时,这可能会导致数据不一致。

线程安全

数据不一致的原因是更新任何字段值都不是原子过程,它需要三个步骤;首先读取当前值,然后执行必要的操作以获取更新的值,第三将更新的值分配给字段引用。让我们用一个简单的程序来检查这一点,其中多个线程正在更新共享数据。

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();          }      }}

在上面的循环程序中,count 增加了 1 四倍,由于我们有两个线程,因此在两个线程都完成执行后,其值应为 8。但是,当您多次运行上述程序时,您会注意到计数值在 6,7,8 之间变化。发生这种情况是因为count++不是原子操作。

Java 中的线程安全

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

Synchronized是 Java 中最简单、使用最广泛的线程安全工具。使用 java.util.concurrent.atomic 包中的原子包装器类。例如 AtomicInteger使用 java.util.concurrent.locks 包中的锁。使用线程安全集合类,例如线程安全的 ConcurrentHashMap 等。将 volatile 关键字与变量一起使用,使每个线程从内存中读取数据,而不是从线程缓存中读取数据。Java synchronized

synchronized是我们可以用来实现线程安全的工具,JVM保证同步的代码一次只能由一个线程执行。Java 关键字synchronized 用于创建同步代码,并在内部使用 Object 或 Class 上的锁来确保只有一个线程在执行同步代码。

Java 同步在任何线程进入同步代码之前对资源进行锁定和解锁,它必须获取对象上的锁,当代码执行结束时,它会解锁可以被其他线程锁定的资源。同时,其他线程处于等待状态以锁定同步的资源。可以通过两种方式使用 synchronized 关键字,一种是使一个完整的方法同步,另一种方式是创建同步块。当一个方法被同步时,它会锁定对象,如果方法是静态的,它会锁定类,因此使用同步块来锁定方法中唯一需要同步的部分始终是最佳做法。在创建同步块时,需要提供将获取锁的资源,可以是 XYZ.class 或类的任何 Object 字段。synchronized(this)将在进入同步块之前锁定对象。您应该使用最低级别的锁定,例如,如果一个类中有多个同步块,其中一个正在锁定 Object,那么其他同步块也将无法由其他线程执行。当我们锁定一个对象时,它会锁定对象的所有字段。Java 同步以性能成本提供数据完整性,因此应仅在绝对必要时使用。Java 同步只在同一个 JVM 中工作,所以如果你需要在多个 JVM 环境中锁定一些资源,它将不起作用,此时可以使用一些全局锁定机制。Java 同步可能会导致死锁。Java 同步关键字不能用于构造函数和变量。最好创建一个虚拟私有对象来用于同步块,以便任何其他代码都无法更改它的引用。例如,如果你有一个要同步的对象 setter 方法,它的引用可以通过其他一些代码更改,从而导致同步块的并行执行。我们不应该使用在常量池中维护的任何对象,例如 String 不应该用于同步,因为如果任何其他代码也锁定在同一个 String 上,它将尝试从 String pool 中获取对同一引用对象的锁定,即使两个代码不相关,它们也会相互锁定。

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

   //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 codeMyObject myObject = new MyObject();synchronized (myObject) {    while (true) {      	// Indefinitely delay myObject        Thread.sleep(Integer.MAX_VALUE);   	}}

请注意,代码正在尝试锁定 myObject 实例,一旦它获得锁定,它永远不会释放它,导致 doSomething() 方法在等待锁定时阻塞,这将导致系统进入死锁并导致拒绝服务 (DoS)。

public class MyObject {    public Object lock = new Object();     public void doSomething() {        synchronized (lock) {            // ...        }    }}//untrusted codeMyObject myObject = new MyObject();//change the lock Object referencemyObject.lock = new Object();

请注意,lock Object 是公共的,通过更改其引用,我们可以在多个线程中并行执行同步块。如果您有私有对象,但有一个 setter 方法来更改其引用,则类似情况也是如此。

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

请注意,代码在类监视器上锁定并且不释放它,这将导致系统中的死锁和 DoS。下面是另一个示例,其中多个线程正在处理同一个字符串数组,并在处理后将线程名称附加到数组值。

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();        }    }    }

运行上述程序时的输出:

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

字符串数组值由于共享数据和未同步而损坏。以下是我们如何更改 addThreadName() 方法以使我们的程序线程安全。

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

在此更改之后,我们的程序运行良好,这是程序的正确输出。

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]

以上就是Java中的线程安全,我希望你了解线程安全编程和使用同步关键字。

标签: #java的线程安全是什么意思