首页 > 技术文章 > JavaSE:线程同步机制

JasperZhao 2021-06-17 09:53 原文

线程同步机制(重点)

1.  基本概念

    <1>  当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题,

          此时就需要对线程之间进行通信和协调,该机制就叫做线程的同步机制

    <2>  多个线程并发读写同一个临界资源时,会发生线程并发安全问题

    <3>  异步操作:多线程并发的操作,各自独立运行

    <4>  同步操作:多线程串行的操作,先后执行的顺序

 

2.  案例代码 (银行的存款取款)

<1>AccountRunnableTest.java

 

 

 

 

 

 会出现问题的情况(两个线程同时,对同一个账户,进行取款操作):

 

 

原因: 线程一没来得及执行setBalance(),线程二就已经执行了

因此,需要线程的同步机制。

 

3.   案例分析 (银行的存款取款)

    <1>  当两个线程同时对同一个账户进行取款时,可能会导致最终的账户余额不合理

    <2>  引发原因:线程一执行取款时,还没来得及将取款后的余额写入后台,线程二就已经开始取款

    <3>  解决方案:线程一完成取款操作后,再让线程二执行即可,将线程的并发操作改为串行操作

    <4>  经验: 在开发中,尽量减少串行操作的范围,从而提高效率  

 

4.  解决方法

方法一:

 

 不使用方法一,因为这样做,会使得多线程 (线程t1、线程t2同时运行) 失效。

 

方法二:使用 同步锁  / 监视器, 如下

 

5.  线程同步的实现方式 (synchronized关键字)

    使用synchronized关键字,实现同步 / 对象锁机制,从而保证线程执行的原子性,具体方式如下:

 

       <1>使用同步代码块的方式,实现部分代码的锁定,格式如下:

            synchronized (类类型的引用){

              编写所有需要锁定的代码;

            }

 

       <2>使用同步方法的方式,实现所有代码的锁定

          直接使用synchronized关键字来修饰整个方法即可

          该方法等价于:

            synchronized(this) {整个方法体的代码}

 

6.  synchronized 的使用 (使用同步代码块的方式)

 

 

 

 

 

 注意:

 

 

7.  另外,在AccountThreadTest.java中,(使用同步代码块的方式),设置同步锁需要注意:

 

 

 1 package com.lagou.task18;
 2 
 3 public class AccountThreadTest extends Thread {
 4     private int balance; // 用于描述账户的余额
 5     private static Demo dm = new Demo(); // 隶属于类层级,所有对象共享同一个
 6 
 7     public AccountThreadTest() {
 8     }
 9 
10     public AccountThreadTest(int balance) {
11         this.balance = balance;
12     }
13 
14     public int getBalance() {
15         return balance;
16     }
17 
18     public void setBalance(int balance) {
19         this.balance = balance;
20     }
21 
22     @Override
23     public /*static*/ /*synchronized*/ void run() {
24         /*System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
25         //synchronized (dm) { // ok
26             //synchronized (new Demo()) { // 锁不住  要求必须是同一个对象
27             // 1.模拟从后台查询账户余额的过程
28             int temp = getBalance(); // temp = 1000  temp = 1000
29             // 2.模拟取款200元的过程
30             if (temp >= 200) {
31                 System.out.println("正在出钞,请稍后...");
32                 temp -= 200;  // temp = 800   temp = 800
33                 try {
34                     Thread.sleep(5000);
35                 } catch (InterruptedException e) {
36                     e.printStackTrace();
37                 }
38                 System.out.println("请取走您的钞票!");
39             } else {
40                 System.out.println("余额不足,请核对您的账户余额!");
41             }
42             // 3.模拟将最新的账户余额写入到后台
43             setBalance(temp); // balance = 800  balance = 800
44         //}*/
45         test();
46     }
47 
48     public /*synchronized*/ static void test() {
49         synchronized (AccountThreadTest.class) { // 该类型对应的Class对象,由于类型是固定的,因此Class对象也是唯一的,因此可以实现同步
50             System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
51             //synchronized (dm) { // ok
52             //synchronized (new Demo()) { // 锁不住  要求必须是同一个对象
53             // 1.模拟从后台查询账户余额的过程
54             int temp = 1000; //getBalance(); // temp = 1000  temp = 1000
55             // 2.模拟取款200元的过程
56             if (temp >= 200) {
57                 System.out.println("正在出钞,请稍后...");
58                 temp -= 200;  // temp = 800   temp = 800
59                 try {
60                     Thread.sleep(5000);
61                 } catch (InterruptedException e) {
62                     e.printStackTrace();
63                 }
64                 System.out.println("请取走您的钞票!");
65             } else {
66                 System.out.println("余额不足,请核对您的账户余额!");
67             }
68             // 3.模拟将最新的账户余额写入到后台
69             //setBalance(temp); // balance = 800  balance = 800
70         }
71     }
72 
73     public static void main(String[] args) {
74 
75         AccountThreadTest att1 = new AccountThreadTest(1000);
76         att1.start();
77 
78         AccountThreadTest att2 = new AccountThreadTest(1000);
79         att2.start();
80 
81         System.out.println("主线程开始等待...");
82         try {
83             att1.join();
84             //t2.start(); // 也就是等待线程一取款操作结束后再启动线程二
85             att2.join();
86         } catch (InterruptedException e) {
87             e.printStackTrace();
88         }
89         System.out.println("最终的账户余额为:" + att1.getBalance()); // 800
90 
91     }
92 
93     }

 

8. 使用同步方法的方式,实现所有代码的锁定 (AccountRunnableTest.java)

 

 

 

9. 使用同步方法的方式,实现所有代码的锁定 (AccountThreadTest.java)

 

 用synchronized修饰staic方法,等价于:

 

 

 

推荐阅读