线程同步机制(重点)
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方法,等价于: