day31上
线程安全 – 加锁
注意:要想多个线程互斥住,就必须使用同一把锁(对象)!!!
加锁方式
synchronized
Lock
synchronized
继day30的售票需求案例学习
学习思路:
1.使用线程类、任务类方式不同
2.加锁方式不同
3.对于加锁中同步代码块、同步方法不同
同步代码块:
synchronized(锁对象){//自动上锁
…想要互斥的代码…
}//自动解锁
见day30
同步方法
同步方法 – 成员同步方法:
注意:锁对象 -> this(原因:成员方法属于对象)
public synchronized void method(){//自动上锁
…想要互斥的代码…
}//自动解锁
同步方法 – 静态同步方法:
注意:锁对象 -> 类.class(原因:静态方法属于类)
public static synchronized void method(){//自动上锁
…想要互斥的代码…
}//自动解锁
Lock
补充: Doug Lea(道格·利)编写的util.concurrent包 ;个人开发线程安全
//锁对象
Lock lock = new ReentrantLock();
lock.lock();//手动上锁
…想要互斥的代码…
lock.unlock();//手动解锁
需求
铁道部发布了一个售票任务,要求销售1000张票,要求有3个窗口来进行销售,请编写多线程程序来模拟这个效果(该题涉及到线程安全,https://www.jb51.net/article/221008.htm)
i. 窗口001正在销售第1张票
ii. 窗口001正在销售第2张票
iii. 窗口002正在销售第3张票
iv. 。。。
v. 窗口002正在销售第1000张票
涉及到线程安全,要加锁
使用线程类解决需求
synchronized方式
使用同步代码块
继day30
使用同步方法
public class MyThread extends Thread{private static int allTicket = 1000;private static int curTicket = 0;public MyThread(String name) {super(name);}@Overridepublic void run() {while(curTicket < allTicket){method();}}//锁对象:MyThread.classpublic static synchronized void method(){if(curTicket < allTicket){curTicket++;System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + curTicket + "张票");}if(curTicket >= allTicket){System.out.println("窗口" + Thread.currentThread().getName() + "票已经售完");}}}public class Test01 {public static void main(String[] args) {MyThread t1 = new MyThread("001");MyThread t2 = new MyThread("002");MyThread t3 = new MyThread("003");t1.start();t2.start();t3.start();}
}
方法外面加锁,还是锁不住
原因:锁对象 -> this(原因:成员方法属于对象),new了三个对象,就有三把锁(对象)锁不住
解决:再把同步方法设置成静态变成一个锁(对象),就能锁锁上
Lock方式
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class MyThread extends Thread{private static int allTicket = 1000;private static int curTicket = 0;private static Lock lock = new ReentrantLock();public MyThread(String name) {super(name);}@Overridepublic void run() {while(curTicket < allTicket){lock.lock();//手动上锁try {if(curTicket < allTicket){curTicket++;System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + curTicket + "张票");}if(curTicket >= allTicket){System.out.println("窗口" + Thread.currentThread().getName() + "票已经售完");}} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();//手动解锁}}}
}
run()中new了锁对象,添加了手动上锁和手动解锁,还是锁不住
原因:线程new了三个对象,都调用run方法就有三把锁(对象)锁不住
解决:把new的锁放在run方法外面,再把new的锁对象设置成静态变成一个锁(对象),就能锁锁上
注意
考虑到想要互斥的代码有可能出现异常,如果出现异常就解不了锁,锁就用不了,用trycatch处理(鼠标右键Surround With)保证能解锁,下一次线程使用
使用任务类解决需求
synchronized方式
使用同步代码块
public class Task implements Runnable{private int allTicket = 1000;private int curTicket = 0;@Overridepublic void run() {while(curTicket < allTicket){synchronized (this) {if(curTicket < allTicket){curTicket++;System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + curTicket + "张票");}}}System.out.println("窗口" + Thread.currentThread().getName() + "票已售完");}}public class Test01 {public static void main(String[] args) {Task task = new Task();Thread t1 = new Thread(task, "001");Thread t2 = new Thread(task, "002");Thread t3 = new Thread(task, "003");t1.start();t2.start();t3.start();}
}
代码实现出现的问题
与day30中的不太一样,day30要求是不会出现脏数据,而现在是要保证锁对象用得好
问题一:
三个窗口各卖1000张票,一共卖了3000张
出现原因:三个线程抢到CPU资源后都会调用run方法,curTicket和allTicket都是run方法里的局部变量,所以会调用3次
解决思路:curTicket和allTicket设置为成员属性,三个线程共用同一个任务
注意:
设置成静态属性和成员属性的区别:设置成成员属性,对于”铁道部发布了一个售票任务“设置成静态没有问题,但考虑到多个售票任务,静态对象就不好,售票任务之间是不同的
问题二:
有些票没有卖,有些票卖了重票
出现原因:当前线程抢到CPU资源后做了票的自增,但是还没来得及输出,时间片到了就退出CPU资源,然后其他线程抢到CPU资源了
解决方案:当前线程抢到CPU资源后,票的自增和输出执行完毕才能切换到其他线程运行 – 加锁
注意:
对于锁对象,问题1提到静态的是不好的,所以使用非静态的
通常用this,原因是this表当前任务的对象,对于外部有多个任务时,就会针对不同任务相应互斥内容进行互斥
问题三:
多卖了票
出现原因:curTicket到了临界点(999),三个线程都可以进判断,然后上锁
解决方案:在锁中再次判断
注意:
1.对于两个判断为什么不写出if-else的原因:如果写出if-else,if正常执行不会再执行else,就会有一个售完票的窗口输出不了“窗口xxx票已售完”
2.对于也不写成双if判断,把售完的判断不加锁的原因:以前的代码是存在问题的,有可能正好最后一个票卖完解锁,下一次curTicket不小于allTicket就进不去了,也没办法执行判断卖完的输出
使用同步方法
使用成员属性的原因同上
public class Task implements Runnable{private int allTicket = 1000;private int curTicket = 0;@Overridepublic void run() {while(curTicket < allTicket){method();}System.out.println("窗口" + Thread.currentThread().getName() + "票已售完");}public synchronized void method(){if(curTicket < allTicket){curTicket++;System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + curTicket + "张票");}}}
Lock方式
加锁参考使用线程类解决需求的lock方式,lock注意trycatch
小结:
线程类方式和任务类方式的区别:线程类方式不灵活,任务类是新建的任务交给线程更灵活,后续普遍使用任务类方式
设置成静态属性和成员属性的区别:设置成成员属性,对于”铁道部发布了一个售票任务“设置成静态没有问题,但考虑到多个售票任务,静态对象就不好,售票任务之间是不同的
总结:
1.线程安全 — 买票案例
synchronized代码块
synchronized方法(成员同步方法、静态同步方法)
Lock锁
注意:
1.加锁的方式
2.锁对象(多个线程去操作同一把锁才能互斥住)