基本概念
- 当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题,此时就需要对线程之间进行通信和协调,该机制就叫做线程的同步机制。
- 多个线程并发读写同一个临界资源时会发生线程并发安全问题。
- 异步操作:多线程并发的操作,各自独立运行。
- 同步操作:多线程串行的操作,先后执行的顺序。 ```java package com.lagou.task18;
/**
- @author lijing
- @date 2020/10/15 15:05
@description */ public class AccountRunnableTest implements Runnable{ private int balance; public AccountRunnableTest() { }
public AccountRunnableTest(int balance) {
this.balance = balance;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "已启动");///1.模拟从后台查询账户余额的过程int temp=getBalance();//2.模拟取款200元的过程if(temp>=200){System.out.println("正在出钞中,请骚等..");temp -=200;try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("请取走你的钞票");}else{System.out.println("余额不足,请核对的账户余额");}//3.模拟将最新的账户余额写入到后台setBalance(temp);
}
public static void main(String[] args){
AccountRunnableTest account=new AccountRunnableTest(1000);Thread t1=new Thread(account);Thread t2=new Thread(account);t1.start();t2.start();System.out.println("主线程开始等待...");try {t1.join();
// t2.start();//也就是等待线程一取款操作结束后再启动线程二—串行 没有多线程
t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("最终的账户余额为"+account.getBalance());
} }
<a name="E9JcA"></a># 解决方案由程序结果可知:当两个线程同时对同一个账户进行取款时,导致最终的账户余额不合理。<br />引发原因:线程一执行取款时还没来得及将取款后的余额写入后台,线程二就已经开始取款。<br />解决方案:让线程一执行完毕取款操作后,再让线程二执行即可,将线程的并发操作改为串行操作。<br />经验分享:在以后的开发尽量减少串行操作的范围,从而提高效率。<a name="WenYy"></a># 实现方式在Java语言中使用synchronized关键字来实现同步/对象锁机制从而保证线程执行的原子性,具体方式如下:<br />使用同步代码块的方式实现部分代码的锁定,格式如下:<br />synchronized(类类型的引用) {<br />编写所有需要锁定的代码;<br />}<br />使用同步方法的方式实现所有代码的锁定。直接使用synchronized关键字来修饰整个方法即可<br />该方式等价于:<br />synchronized(this) { 整个方法体的代码 }```javapackage com.lagou.task18;/*** @author lijing* @date 2020/10/15 15:05* @description*/public class AccountRunnableTest implements Runnable{private int balance;private Demo dm=new Demo();public AccountRunnableTest() {}public AccountRunnableTest(int balance) {this.balance = balance;}public int getBalance() {return balance;}public void setBalance(int balance) {this.balance = balance;}@Overridepublic void run() {System.out.println("线程" + Thread.currentThread().getName() + "已启动");synchronized (dm) {//synchronized(new Demo()){ 锁不住 需要同一个锁///1.模拟从后台查询账户余额的过程int temp=getBalance();//2.模拟取款200元的过程if(temp>=200){System.out.println("正在出钞中,请骚等..");temp -=200;try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("请取走你的钞票");}else{System.out.println("余额不足,请核对的账户余额");}//3.模拟将最新的账户余额写入到后台setBalance(temp);}}public static void main(String[] args){AccountRunnableTest account=new AccountRunnableTest(1000);Thread t1=new Thread(account);Thread t2=new Thread(account);t1.start();t2.start();System.out.println("主线程开始等待...");try {t1.join();// t2.start();//也就是等待线程一取款操作结束后再启动线程二--串行 没有多线程t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("最终的账户余额为"+account.getBalance());}}class Demo{}
静态方法的锁定
- 当我们对一个静态方法加锁,如:
- public synchronized static void xxx(){….}
- 那么该方法锁的对象是类对象。每个类都有唯一的一个类对象。获取类对象的方式:类名.class。
- 静态方法与非静态方法同时使用了synchronized后它们之间是非互斥关系的。
- 原因在于:静态方法锁的是类对象而非静态方法锁的是当前方法所属对象。 ```java package com.lagou.task18;
/**
- @author lijing
- @date 2020/10/15 15:26
@description */ public class AccountThreadTest extends Thread{ private int balance; //类属于类层级 private static Demo dm=new Demo();
public AccountThreadTest() { }
public AccountThreadTest(int balance) {
this.balance = balance;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override public /synchronized/ void run() {
System.out.println("线程" + Thread.currentThread().getName() + "已启动");synchronized (dm) { //每一个对象都有一个成员变量dm 所以锁不住 如果要锁住需要static关键字修饰dm///1.模拟从后台查询账户余额的过程int temp=getBalance();//2.模拟取款200元的过程if(temp>=200){System.out.println("正在出钞中,请骚等..");temp -=200;try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("请取走你的钞票");}else{System.out.println("余额不足,请核对的账户余额");}//3.模拟将最新的账户余额写入到后台setBalance(temp);}
}
public static void main(String[] args) {
AccountThreadTest att1=new AccountThreadTest(1000);att1.start();AccountThreadTest att2=new AccountThreadTest(1000);att2.start();System.out.println("主线程开始等待...");try {att1.join();
// t2.start();//也就是等待线程一取款操作结束后再启动线程二—串行 没有多线程
att2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("最终的账户余额为"+att1.getBalance());
} }
<a name="g4gbx"></a># 注意事项- 使用synchronized保证线程同步应当注意:- 多个需要同步的线程在访问同步块时,看到的应该是同一个锁对象引用。- 在使用同步块时应当尽量减少同步范围以提高并发的执行效率。<a name="a0oS8"></a># 线程安全类和不安全类- StringBuffer类是线程安全的类,但StringBuilder类不是线程安全的类。- Vector类和 Hashtable类是线程安全的类,但ArrayList类和HashMap类不是线程安全的类。- Collections.synchronizedList() 和 Collections.synchronizedMap()等方法实现安全。<a name="IMOTQ"></a># 死锁的概念线程一执行的代码:<br />public void run(){<br />synchronized(a){ //持有对象锁a,等待对象锁b<br />synchronized(b){<br />编写锁定的代码;<br />}<br />}<br />}<br />线程二执行的代码:<br />public void run(){<br />synchronized(b){ //持有对象锁b,等待对象锁a<br />synchronized(a){<br />编写锁定的代码;<br />}<br />}<br />}<br />注意:<br />在以后的开发中尽量减少同步的资源,减少同步代码块的嵌套结构的使用!<a name="58TVb"></a># 使用Lock(锁)实现线程同步<a name="IoLdy"></a>## 基本概念从Java5开始提供了更强大的线程同步机制—使用显式定义的同步锁对象来实现。<br />java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。<br />该接口的主要实现类是ReentrantLock类,该类拥有与synchronized相同的并发性,在以后的线程安全控制中,经常使用ReentrantLock类显式加锁和释放锁。<a name="AezvV"></a>## 常用的方法| ReentrantLock() | 使用无参方式构造对象 || --- | --- || void lock() | 获取锁 || void unlock() | 释放锁 |<a name="UvkJ0"></a>## 与synchronized方式的比较- Lock是显式锁,需要手动实现开启和关闭操作,而synchronized是隐式锁,执行锁定代码后自动释放。- Lock只有同步代码块方式的锁,而synchronized有同步代码块方式和同步方法两种锁。- 使用Lock锁方式时,Java虚拟机将花费较少的时间来调度线程,因此性能更好。<a name="1lL0a"></a>## Object类常用的方法| void wait() | 用于使得线程进入等待状态,直到其它线程调用notify()或notifyAll()方法 || --- | --- || void wait(long timeout) | 用于进入等待状态,直到其它线程调用方法或参数指定的毫秒数已经过去为止 || void notify() | 用于唤醒等待的单个线程 || void notifyAll() | 用于唤醒等待的所有线程 |```javapackage com.lagou.task18;/*** @author lijing* @date 2020/10/15 15:55* @description*/public class ThreadCommunicationTest implements Runnable{private int cnt=1;@Overridepublic void run() {while (true){synchronized (this) {//每当有一个线程进来后先大喊一声,把其他喊起来,调用notify()notify();if(cnt<=100){System.out.println("线程:" + Thread.currentThread().getName() + "中,cnt=" + cnt);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}cnt++;//当前线程打印完毕一个整数后,为了防止继续打印下一个数据,则调用wait方法try {wait();//当前线程进入阻塞状态,自动释放对象锁,必须在锁定的代码中调用} catch (InterruptedException e) {e.printStackTrace();}}else{break;}}}}public static void main(String[] args) {ThreadCommunicationTest tct=new ThreadCommunicationTest();Thread t1=new Thread(tct);t1.start();Thread t2=new Thread(tct);t2.start();}}
生产者消费者模型的实现
package com.lagou.task18;/*** @author lijing* @date 2020/10/15 16:20* @description*/public class StoreHouse {private int cnt=0;//用于记录产品的数量public synchronized void produceProduct() {notify();if(cnt <10){System.out.println("线程"+Thread.currentThread().getName()+"正在生产第"+(cnt+1)+"个产品...");cnt++;}else{try {wait();} catch (InterruptedException e) {e.printStackTrace();}}}public synchronized void consumerProduct() {notify();if(cnt>0){System.out.println("线程" + Thread.currentThread().getName() + "正在消费第" + cnt + "个商品..");cnt--;}else{try {wait();} catch (InterruptedException e) {e.printStackTrace();}}}}
package com.lagou.task18;/*** @author lijing* @date 2020/10/15 16:21* @description*/public class ProduceThread extends Thread{//声明一个仓库类型的引用作为成员变量,是为了能调用仓库类中的生产方法//合成复用原则private StoreHouse storeHouse;public ProduceThread(StoreHouse storeHouse){this.storeHouse=storeHouse;}@Overridepublic void run() {while(true){storeHouse.produceProduct();try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}
package com.lagou.task18;/*** @author lijing* @date 2020/10/15 16:25* @description*/public class ConsumerThread extends Thread{//声明一个仓库类型的引用作为成员变量,是为了能调用仓库类中的生产方法//合成复用原则private StoreHouse storeHouse;public ConsumerThread(StoreHouse storeHouse){this.storeHouse=storeHouse;}@Overridepublic void run() {while(true){storeHouse.consumerProduct();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}
package com.lagou.task18;/*** @author lijing* @date 2020/10/15 16:26* @description*/public class StoreHouseTest {public static void main(String[] args) {//创建仓库类的对象StoreHouse storeHouse=new StoreHouse();//创建线程类对象并启动ProduceThread t1=new ProduceThread(storeHouse);ConsumerThread t2=new ConsumerThread(storeHouse);t1.start();t2.start();}}
