前言:
现在看官们对“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的线程安全是什么意思