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


posted @   垄山小站  阅读(54)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)
点击右上角即可分享
微信分享提示