在单线程程序中,后面的线程只能到前面的线程执行完毕才能执行,这不会出现线程抢占问题。但是在多任务的操作系统中,不同优先级的线程抢占CPU资源,这会造成线程共享资源出现资源冲突的问题
我们来引入一个场景:秒杀我们都经常见到过。假如有2个人抢一台手机,势必会造成有一个能抢到,有一个抢不到。假如A运气比较好,抢到了,那么手机数量就会-1,进而变成0,这时候B再来秒杀手机时,程序询问手机数量发现为0,进而反馈:运气真不好,手机被抢走了。
但是如果没有线程同步,当A购买了手机,此时应该手机要减一,但是线程A并没有修改。B也打算下单,程序询问手机数量发现手机还是1(按理来说应该是0),也下单成功。此时就会造成,明明只有一台手机,但是有两个人下单成功的尴尬局面。
因此线程同步是个非常重要的问题:场景也有很多(火车票售卖、淘宝库存、排号系统等等)
这种都必须要求,只有一个线程能访问线程共享资源(剩余票数,库存数,资源剩余),等他执行完毕,才允许其他线程访问
我们来通过买票问题来说明问题
一开始没有线程同步时
public class TreadSafe implements Runnable{ int num=10; @Override public void run() { while (true){ if(num>0){ try { Thread.sleep(100); }catch (Exception e){ e.printStackTrace(); } System.out.println("tickets:"+num--); } } } public static void main(String [] args){ TreadSafe tread =new TreadSafe(); Thread t1=new Thread(tread); Thread t2=new Thread(tread); Thread t3=new Thread(tread); Thread t4=new Thread(tread); t1.start(); t2.start(); t3.start(); t4.start(); } }
到最后你会发现tickets
这就是各个线程没有同步的后果,线程1234都有各自的tickets数值,但互相没有同步,会恶意修改num值
那就会出现明明线程1拿走最后一张票了,线程2访问时发现自己的tickets并不是0,继续访问if{}
线程锁
既然问题出现了,那么就有解决问题的方法,那就是给共享资源(票数)上一道锁,当一个线程访问共享资源时,上一道锁,其他线程只能等待前面的线程执行完毕才可以访问。
synchronized关键字可以实现,是基于悲观锁设计的,被synchronized标注的代码块只允许一个线程能访问资源,其他线程等待前面的线程执行完才能进入。
synchronized有两种表示方法
一种是synchronized代码块;一种是synchronized方法
先看第一种
public class TreadSafe implements Runnable{ int num=10; @Override public void run() { while (true){ synchronized (""){ if(num>0){ try { Thread.sleep(100); }catch (Exception e){ e.printStackTrace(); } System.out.println("tickets:"+num--); } } } } public static void main(String [] args){ TreadSafe tread =new TreadSafe(); Thread t1=new Thread(tread); Thread t2=new Thread(tread); Thread t3=new Thread(tread); Thread t4=new Thread(tread); t1.start(); t2.start(); t3.start(); t4.start(); } }
我们在之前的例子 中while加入synchronized代码块
synchronized(object){ //... }
其实现原理就是,Object为一个任意对象,在每个对象里都有一个标志位(o or 1)。当一个线程访问该代码块时,会先访问对象的标志位,如果标志位为0,说明有其他线程在执行,就自己进入就绪状态直到标志位为1,才能进入该代码块执行,此时标志位又会从1变成0;该代码区又会称为临界区。
这样就能解决问题
还有一种就是synchronized方法,在前面加上synchronized修饰的方法
synchronized void f(){}
在之前的案例中创建一个类文件,在该类里定义同步方法
public synchronized void doit(){ if(num>0){ try { Thread.sleep(100); }catch (Exception e){ e.printStackTrace(); } System.out.println("tickets:"+num--); } } public void run(){ while(ture){ doit(); } }
这样执行的结果与代码块无异。