Java在多线程中使用同步锁机制时,一定要注意锁对对象,下面的例子就是没锁对对象(每个线程使用一个被锁住的对象时,得先看该对象的被锁住部分是否有人在使用)
例子:两个人操作同一个银行账户,丈夫在ATM机上操作,妻子在银行柜台操作
账户类:账户里面有100万
public class Acount { public int money=100; }
ATM机类:里面存在一个Acount对象和要取的钱数,在takeMoney方法中加了synchronized 机制
public class ATM implements Runnable{ private Acount acount; private int withdrawMoney; public ATM(Acount acount, int withdrawMoney) { this.acount = acount; this.withdrawMoney = withdrawMoney; } @Override public void run() { takeMoney(); } public synchronized void takeMoney(){ //取钱 if(acount.money<withdrawMoney){ return; } try { Thread.sleep(200); //线程1进入后休眠,线程2仍可以进来,这样可能造成赤字 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"取出"+withdrawMoney+"万元"); acount.money-=withdrawMoney; System.out.println("账户余额:"+acount.money); } }
客户类:创建一个唯一账户,两台ATM机(其中一台模拟柜台),分别给两个人使用
public class Customer { public static void main(String[] args) { Acount acount=new Acount(); ATM atm1=new ATM(acount,70); //丈夫在ATM机上操作账户,妻子在柜台操作账户 ATM atm2=new ATM(acount,80); Thread husband=new Thread(atm1,"丈夫"); Thread wife=new Thread(atm2,"妻子"); husband.start(); wife.start(); } }
运行结果:
可以看到加了synchronized 后仍然出现线程不安全。
分析:synchronized 机制一般用在被数据操作的对象中,而takeMoney方法是属于ATM机的方法,在此例子中,一共存在一个账户类,两个ATM机类,两个ATM机类去操作账户类,所以应该把账户类锁住。
修正:使用同步块机制锁住acount对象
public void takeMoney(){ //取钱 synchronized (acount){ if(acount.money<withdrawMoney){ return; } try { Thread.sleep(200); //线程1进入后休眠,线程2仍可以进来,这样可能造成赤字 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"取出"+withdrawMoney+"万元"); acount.money-=withdrawMoney; System.out.println("账户余额:"+acount.money); } }
例子2 Tickert类是线程类
public class Ticket implements Runnable{ private int ticker=100; private boolean flag=true; @Override public void run() { while(flag){ robTicket(); } } /** * 抢票 */ public void robTicket(){ if (ticker <= 0) { flag = false; return; } synchronized (this){ //多重验证机制,在这里检测这个对象的这部分代码是否被使用,如果有线程正在使用该对象的该部分代码,就等待 if (ticker <= 0) { flag = false; return; } try { //模拟网络延时 Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"-->"+ticker--); } } }
主类
public class RobTicket { public static void main(String[] args) { Runnable ticket=new Ticket(); //3个线程同时对一个ticket对象进行操作,准确来说是对ticket对象里面的ticket变量操作 Thread feizhu=new Thread(ticket,"飞猪"); Thread zhixing=new Thread(ticket,"智行"); Thread xiecheng=new Thread(ticket,"携程"); zhixing.start(); feizhu.start(); xiecheng.start(); } }
在这个例子里面运用了多重的验证机制,保证了抢票重复和抢票出现负数的情况,如果不加synchronized 里面的if判断语句,仍然会出现线程不安全,因为其他线程可能并发的排队在同步块外面等候了,此时如果还剩一张票的话,当前线程抢完这最后一张票后其他线程仍然有机会抢票,这是不合理的,当然可以为整个方法上锁,但是性能会下降。