java多线程编程(一)——synchronized关键字
学习java多线程的小伙伴对synchronized关键字一定不陌生,那么我们什么时候需要使用synchronized?synchronized解决了哪些问题?synchronized存在哪些不足?我们带着今天这些问题开始synchronized的学习
1、为什么要用synchronized?
场景1: 2个卖票员共同来卖10张票,代码如下
package cn.iocoder.demo0424.thread;
/**
* @author :leichunhui
* @date :Created in 2020/5/24 10:17
* @description:售票系统
* @modified By:
* @version: 0.1$
*/
public class SellTicket {
//票的总数
private int ticketNum=10;
public void sellTicket(){
System.out.println(Thread.currentThread().getName() +"——————开始执行————————");
try {
while (ticketNum > 0) {
System.out.println("卖票员" + Thread.currentThread().getName() + ", 卖了第" + ticketNum + "张票");
ticketNum--;
Thread.sleep(100);
}
System.out.println("票卖完了");
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +"——————执行结束————————");
}
}
package cn.iocoder.demo0424.thread;
/**
* @author :leichunhui
* @date :Created in 2020/5/24 10:21
* @description:卖票员A
* @modified By:
* @version: 0.1$
*/
public class ThreadA extends Thread{
SellTicket sellTicket=new SellTicket();
public ThreadA(SellTicket sellTicket){
super();
this.sellTicket=sellTicket;
}
@Override
public void run(){
sellTicket.sellTicket();
}
public static void main(String[] args){
SellTicket sellTicket=new SellTicket();
ThreadA threadA1=new ThreadA(sellTicket);
ThreadA threadA2=new ThreadA(sellTicket);
threadA1.start();
threadA2.start();
}
}
运行结果如下
Thread-0——————开始执行————————
卖票员Thread-0, 卖了第10张票
Thread-1——————开始执行————————
卖票员Thread-1, 卖了第9张票
卖票员Thread-1, 卖了第8张票
卖票员Thread-0, 卖了第8张票
卖票员Thread-0, 卖了第6张票
卖票员Thread-1, 卖了第5张票
卖票员Thread-1, 卖了第4张票
卖票员Thread-0, 卖了第3张票
卖票员Thread-1, 卖了第2张票
卖票员Thread-0, 卖了第2张票
票卖完了
票卖完了
Thread-0——————执行结束————————
Thread-1——————执行结束————————
可以看出有些票是被两个售票员同时卖出的,这个事情如果在12306系统中发生,那估计就要“杀个程序员祭天了”。当然这种事情在真实卖票系统中几乎不可能出现。那么这个问题如何能够通过synchronized解决呢?
将 SellTicket添加锁,代码如下
package cn.iocoder.demo0424.thread;
/**
* @author :leichunhui
* @date :Created in 2020/5/24 10:17
* @description:售票系统
* @modified By:
* @version: 0.1$
*/
public class SellTicket {
//票的总数
private int ticketNum=10;
synchronized public void sellTicket(){
System.out.println(Thread.currentThread().getName() +"——————开始执行————————");
try {
while (ticketNum > 0) {
System.out.println("卖票员" + Thread.currentThread().getName() + ", 卖了第" + ticketNum + "张票");
ticketNum--;
Thread.sleep(100);
}
System.out.println("票卖完了");
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +"——————执行结束————————");
}
}
当然了,synchronized关键字除了添加在方法上,也可以添加在类上和代码块上,在这里就不再展示了。重新运行,结果如下
Thread-1——————开始执行————————
卖票员Thread-1, 卖了第10张票
卖票员Thread-1, 卖了第9张票
卖票员Thread-1, 卖了第8张票
卖票员Thread-1, 卖了第7张票
卖票员Thread-1, 卖了第6张票
卖票员Thread-1, 卖了第5张票
卖票员Thread-1, 卖了第4张票
卖票员Thread-1, 卖了第3张票
卖票员Thread-1, 卖了第2张票
卖票员Thread-1, 卖了第1张票
票卖完了
Thread-1——————执行结束————————
Thread-0——————开始执行————————
票卖完了
Thread-0——————执行结束————————
可以看出了,卖票工作全由“Thread-1”做了,为什么会这样呢?
2、synchronized实现原理
非线程安全问题主要原因有两方面:
1)存在共享数据;
2)多个线程共同操作共享数据;
而通过关键字synchronized保证同一时刻只有一个线程可以获取对象锁【不管synchronized加在类、方法还是代码块上】,确保了线程互斥的访问同步代码,从而实现了线程安全。
代码示例中线程Thread-1调用SellTicket对象加synchronized关键字的sellTicket方法时,线程Thread-1就获得了sellTicket方法锁,其实就是获得了SellTicket对象锁,所以线程Thread-0必须等线程Thread-1执行完毕才可以调用sellTicket方法。
3、synchronized缺点
有小伙伴会问了,synchronized既然这么好用是不是所有的多线程不安全线程都可以通过synchronized关键字来解决?当然不是了,就像人一样,人人无完人,synchronized也有其自身缺点
1)效率低:
- 锁的释放情况少,只在程序正常执行完成和抛出异常时释放锁;
- 试图获得锁是不能设置超时;
- 不能中断一个正在试图获得锁的线程;
2)无法知道线程是否成功获取到锁;
当然了,多线程的知识还有很多,这里只开了一个简单的头,后面我们继续一块去探索多线程的奥秘!