Java基础知识(13)- Java 多线程编程(二) | 线程同步
线程的同步是保证多线程安全访问竞争资源的一种手段。
Java 多个线程同时操作一个可共享的资源变量时(如数据的增删改查),各线程操作同一资源的顺序有一定的随机性,可能会导致数据不准确,或在写操作的时候产生冲突。
常用的线程同步方法:volatile关键字、synchronized关键字、ReenreantLock(重入锁)、AtomicInteger(原子变量)
1. volatile 关键字
1) volatile 保证共享变量的内存可见性:
(1)读取一个volatile 变量时,会直接从共享内存中读,而非线程专属的存储空间中读;
(2)对 volatile 变量进行写操作时,这个变量将会被直接写入共享内存,而非线程的专属存储空间;
通过对 volatile 变量读写的限制,就能保证线程每次读到的都是最新的值,从而确保了该变量的内存可见性。
2) volatile 变量使用的场景:
(1) 对变量的写入不依赖变量当前的值, 或者能确保只有单线程更新变量的值;
(2) 该变量不会与其他状态变量一起纳入不变性条件中;
(3) 在访问变量时不需要加锁;
实例说明使用的场景:
1 class Task { 2 public volatile boolean testVolatileFlag = true; 3 4 public void testVolatile() { 5 String name = Thread.currentThread().getName(); 6 int i = 0; 7 System.out.println(name + ": (" + i + ")"); 8 try { 9 while (testVolatileFlag) { 10 i++; 11 //Thread.sleep(1); // 如果testVolatileFlag不是volatile变量,用sleep()退出循环,
// 可能是sleep()期间,JVM同步了线程缓存和共享内存里的数据 12 } 13 14 Thread.sleep(1); // 本行没有意义,代码编译需要 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 System.out.println(name + ": (" + i + "), terminated"); 19 System.out.println("Exit " + name); 20 } 21 } 22 23 public class App { 24 public static void main( String[] args ) { 25 26 final Task task = new Task(); 27 28 Thread threadVolatile = new Thread(new Runnable() { 29 public void run() { 30 task.testVolatile(); 31 } 32 }, "Thread volatile" ); 33 threadVolatile.start(); 34 35 // 36 try { 37 38 Thread.sleep(100); 39 task.testVolatileFlag = false; 40 41 } catch (InterruptedException e) { 42 e.printStackTrace(); 43 } 44 } 45 }
以上代码中,如果 testVolatileFlag 不是 volatile 变量,testVolatile()方法里的 while 循环不会结束。
3) volatile 变量只能确保 long、double 读写的"原子性"(volatile 在其他情况下是不能保证原子性的):
原子性:是指一个操作或多个操作要么全部执行,且执行的过程不会被其它因素打断,要么就都不执行。
在Java中的所有类型中,有long、double类型比较特殊,他们占据8字节(64比特),其余类型都小于64比特。在32位操作系统中,CPU一次只能读取/写入32位的数据,因此对于64位的long、double变量的读写会进行两步。在多线程中,若一条线程只写入了long型变量的前32位,紧接着另一条线程读取了这个只有“一半”的变量,从而就读到了一个错误的数据。
为了避免这种情况,需要在用 volatile 修饰 long、double 型变量。
如果一个变量加了 volatile 关键字,就会告诉编译器和JVM的内存模型:这个变量是对所有线程共享的、可见的,每次jvm都会读取最新写入的值并使其最新值在所有CPU可见。所以说的是线程可见性,没有提原子性。
2. synchronized 关键字
synchronized 关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
1) 同步方法,synchronized 关键字修饰的方法
Java对象有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
class Task {
// 同步方法
public synchronized void testSync2() {
}
}
2)同步代码块,synchronized 关键字修饰的语句块
synchronized 修饰的语句块会自动被加上内置锁。
class Task {
public void testSync1() {
synchronized(this) {
// 同步代码
}
}
}
注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用 synchronized 代码块同步关键代码即可。
3. ReenreantLock (重入锁)
在JavaSE 5.0中新增了一个java.util.concurrent 包来支持同步。
ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用 synchronized 方法和快具有相同的基本行为和语义,并且扩展了其能力。
ReenreantLock 类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁
注:ReenreantLock 类有一个创建公平锁的构造方法 ReentrantLock(boolean fair) ,由于大幅度降低程序运行效率,不推荐使用
实例说明:
1 import java.util.concurrent.locks.Lock; 2 import java.util.concurrent.locks.ReentrantLock; 3 4 class Task { 5 6 private int value = 1; 7 Lock lock = new ReentrantLock(); 8 9 public void testLock() { 10 11 String name = Thread.currentThread().getName(); 12 try { 13 for (int i = 1; i <= 5; i++) { 14 15 lock.lock(); 16 try { 17 System.out.println(name + ": (" + i + ") value = " + (value++)); 18 } finally { 19 lock.unlock(); 20 } 21 22 Thread.sleep(1000); 23 } 24 } catch (InterruptedException e) { 25 System.out.println(name + " interrupted"); 26 } 27 System.out.println("Exit " + name); 28 } 29 }
以上代码,如果两个线程同时调用 testLock(), 每个线程输出 5 条信息,信息中包含 value 值,期望的 value 应该是从 1 到 10 的顺序结构。
如果没有使用 ReenreantLock,value 值可能会没有 10,其它某个数是重复的,因为 value++ 不是原子性操作。
如果 synchronized 关键字能满足需求,就使用 synchronized,因为它能简化代码 。使用 ReentrantLock 类,要注意及时释放锁,否则会出现死锁,通常在 finally 代码释放锁。
4. AtomicInteger (原子变量)
在Java语言中,++i和i++操作并不是线程安全的,上文中使用了 ReenreantLock 类的 lock()方法来保证 value++ 的线程安全。
我们也可以使用 AtomicInteger,来实现线程安全的加减操作接口。
实例说明:
1 import java.util.concurrent.atomic.AtomicInteger; 2 3 class Task { 4 // int value2 = 1; 5 private AtomicInteger value2 = new AtomicInteger(1); 6 7 public void testAtomic() { 8 9 String name = Thread.currentThread().getName(); 10 try { 11 for (int i = 1; i <= 5; i++) { 12 System.out.println(name + ": (" + i + ") value = " + value2.getAndIncrement()); 13 Thread.sleep(1000); 14 } 15 } catch (InterruptedException e) { 16 System.out.println(name + " interrupted"); 17 } 18 System.out.println("Exit " + name); 19 } 20 }
以上代码中,如果两个线程同时调用 task.testAtomic(), 每个线程输出 5 条信息,信息中包含 value2.getAndIncrement() 值,期望的值应该是从 1 到 10。
与 ReenreantLock 的例子不同,显示的 1 到 10 的顺序不能保证是从小到大的顺序结构。
实例:
1 import java.util.concurrent.locks.Lock; 2 import java.util.concurrent.locks.ReentrantLock; 3 import java.util.concurrent.atomic.AtomicInteger; 4 5 public class App { 6 7 public static void main( String[] args ) { 8 9 final Task task = new Task(); 10 11 System.out.println("----------------- Test volatile --------------------"); 12 13 Thread threadVolatile = new Thread(new Runnable() { 14 public void run() { 15 task.testVolatile(); 16 } 17 }, "Thread volatile" ); 18 threadVolatile.start(); 19 20 // 21 try { 22 23 Thread.sleep(50); 24 task.testVolatileFlag = false; 25 26 Thread.sleep(1000); 27 28 } catch (InterruptedException e) { 29 e.printStackTrace(); 30 } 31 32 System.out.println("----------------- Test synchronized --------------------"); 33 34 Thread threadSynchronized1 = new Thread(new Runnable() { 35 public void run() { 36 task.testSync1(); 37 } 38 }, "Thread synchronized 1" ); 39 40 Thread threadSynchronized2 = new Thread(new Runnable() { 41 public void run() { 42 task.testSync2(); 43 } 44 }, "Thread synchronized 2" ); 45 46 threadSynchronized1.start();; 47 threadSynchronized2.start(); 48 49 try { 50 Thread.sleep(12000); 51 } catch (InterruptedException e) { 52 e.printStackTrace(); 53 } 54 55 System.out.println("----------------- Test ReentrantLock --------------------"); 56 57 Thread threadReentrantLock1 = new Thread(new Runnable() { 58 public void run() { 59 task.testLock(); 60 } 61 }, "Thread ReentrantLock 1" ); 62 63 Thread threadReentrantLock2 = new Thread(new Runnable() { 64 public void run() { 65 task.testLock(); 66 } 67 }, "Thread ReentrantLock 2" ); 68 threadReentrantLock1.start(); 69 threadReentrantLock2.start(); 70 71 try { 72 Thread.sleep(10000); 73 } catch (InterruptedException e) { 74 e.printStackTrace(); 75 } 76 77 System.out.println("----------------- Test AtomicInteger --------------------"); 78 79 Thread threadAtomicInteger1 = new Thread(new Runnable() { 80 public void run() { 81 task.testAtomic(); 82 } 83 }, "Thread AtomicInteger 1" ); 84 85 Thread threadAtomicInteger2 = new Thread(new Runnable() { 86 public void run() { 87 task.testAtomic(); 88 } 89 }, "Thread AtomicInteger 2" ); 90 threadAtomicInteger1.start(); 91 threadAtomicInteger2.start(); 92 93 } 94 95 } 96 97 class Task { 98 99 public volatile boolean testVolatileFlag = true; 100 private int value = 1; 101 private AtomicInteger value2 = new AtomicInteger(1); 102 Lock lock = new ReentrantLock(); 103 104 public void testVolatile() { 105 106 String name = Thread.currentThread().getName(); 107 int i = 0; 108 System.out.println(name + ": (" + i + ")"); 109 try { 110 while (testVolatileFlag) { 111 i++; 112 //Thread.sleep(1); // 如果 testVolatileFlag 不是 volatile 变量, 113 // 使用 sleep() 可以退出循环, 可能是sleep()期间,JVM同步了线程缓存和共享内存里的数据 114 } 115 116 Thread.sleep(1); // 本行没有意义,代码编译需要 117 } catch (InterruptedException e) { 118 e.printStackTrace(); 119 } 120 System.out.println(name + ": (" + i + "), terminated "); 121 System.out.println("Exit " + name); 122 } 123 124 public void testSync1() { 125 126 synchronized(this) { 127 String name = Thread.currentThread().getName(); 128 try { 129 for (int i = 1; i <=5; i++) { 130 System.out.println(name + ": " + i); 131 Thread.sleep(1000); 132 } 133 } catch (InterruptedException e) { 134 System.out.println(name + " interrupted"); 135 } 136 System.out.println("Exit " + name); 137 } 138 } 139 140 public synchronized void testSync2() { 141 142 String name = Thread.currentThread().getName(); 143 try { 144 for (int i = 1; i <=5; i++) { 145 System.out.println(name + ": " + i); 146 Thread.sleep(1000); 147 } 148 } catch (InterruptedException e) { 149 System.out.println(name + " interrupted"); 150 } 151 System.out.println("Exit " + name); 152 } 153 154 public void testLock() { 155 156 String name = Thread.currentThread().getName(); 157 try { 158 for (int i = 1; i <=5; i++) { 159 160 lock.lock(); 161 try { 162 System.out.println(name + ": (" + i + ") value = " + (value++)); 163 } finally { 164 lock.unlock(); 165 } 166 167 Thread.sleep(1000); 168 } 169 } catch (InterruptedException e) { 170 System.out.println(name + " interrupted"); 171 } 172 System.out.println("Exit " + name); 173 } 174 175 public void testAtomic() { 176 String name = Thread.currentThread().getName(); 177 try { 178 for (int i = 1; i <= 5; i++) { 179 System.out.println(name + ": (" + i + ") value = " + value2.getAndIncrement()); 180 Thread.sleep(1000); 181 } 182 } catch (InterruptedException e) { 183 System.out.println(name + " interrupted"); 184 } 185 System.out.println("Exit " + name); 186 } 187 188 }
输出:
----------------- Test volatile --------------------
Thread volatile: (0)
Thread volatile: (188740922), terminated
Exit Thread volatile
----------------- Test synchronized --------------------
Thread synchronized 1: 1
Thread synchronized 1: 2
Thread synchronized 1: 3
Thread synchronized 1: 4
Thread synchronized 1: 5
Exit Thread synchronized 1
Thread synchronized 2: 1
Thread synchronized 2: 2
Thread synchronized 2: 3
Thread synchronized 2: 4
Thread synchronized 2: 5
Exit Thread synchronized 2
----------------- Test ReentrantLock --------------------
Thread ReentrantLock 2: (1) value = 1
Thread ReentrantLock 1: (1) value = 2
Thread ReentrantLock 1: (2) value = 3
Thread ReentrantLock 2: (2) value = 4
Thread ReentrantLock 1: (3) value = 5
Thread ReentrantLock 2: (3) value = 6
Thread ReentrantLock 1: (4) value = 7
Thread ReentrantLock 2: (4) value = 8
Thread ReentrantLock 2: (5) value = 9
Thread ReentrantLock 1: (5) value = 10
Exit Thread ReentrantLock 1
Exit Thread ReentrantLock 2
----------------- Test AtomicInteger --------------------
Thread AtomicInteger 1: (1) value = 1
Thread AtomicInteger 2: (1) value = 2
Thread AtomicInteger 2: (2) value = 3
Thread AtomicInteger 1: (2) value = 4
Thread AtomicInteger 2: (3) value = 5
Thread AtomicInteger 1: (3) value = 6
Thread AtomicInteger 2: (4) value = 7
Thread AtomicInteger 1: (4) value = 8
Thread AtomicInteger 2: (5) value = 9
Thread AtomicInteger 1: (5) value = 10
Exit Thread AtomicInteger 1
Exit Thread AtomicInteger 2
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)