Java学习之线程安全详解(卖票案例)

来源:互联网 发布:二叉树的结点算法公式 编辑:程序博客网 时间:2024/06/05 11:28

一、要求,有100张票,要求同时在三个窗口出售

首先创建卖票的类:

package com.edu_03;public class ShellTicket implements Runnable{int tickets = 100;@Overridepublic void run() {while (true) {//让窗口一直处于打开的状态if (tickets>0) {try {Thread.sleep(100);/* * 在加入休眠之后出现了重复票和0张-1张票,出现了线程安全问题thread1进来,休眠thread2进来,休眠thread3进来,休眠*/} catch (InterruptedException e) {e.printStackTrace();}//thread1醒来,输出1,然后tickets = 0;//thread2醒来,输出0,然后tickets = -1;//thread3醒来,输出-1,然后tickets = -2;System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets--+"票");/* * 1、打印这个变量的值 * 2、减一 * 3、把减一后的数据赋值给变量 */}}}}
这是主程序:

package com.edu_03;public class Test {public static void main(String[] args) {//创建对象ShellTicket st = new ShellTicket();//转换成线程对象Thread thread = new Thread(st);Thread thread2 = new Thread(st);Thread thread3 = new Thread(st);//给线程设置名字thread.setName("第一个");thread2.setName("第二个");thread3.setName("第三个");//开启线程thread.start();thread2.start();thread3.start();}}
这是结果:

第三个正在出售第100票第一个正在出售第99票第二个正在出售第99票第一个正在出售第98票第三个正在出售第97票第二个正在出售第96票第二个正在出售第95票第一个正在出售第94票第三个正在出售第93票第二个正在出售第91票第一个正在出售第90票第三个正在出售第92票第二个正在出售第89票第一个正在出售第88票第三个正在出售第87票第二个正在出售第86票第一个正在出售第85票第三个正在出售第84票第一个正在出售第83票第二个正在出售第82票第三个正在出售第81票第二个正在出售第80票第一个正在出售第79票第三个正在出售第78票第一个正在出售第77票第二个正在出售第76票第三个正在出售第75票第二个正在出售第74票第一个正在出售第73票第三个正在出售第72票第一个正在出售第71票第二个正在出售第70票第三个正在出售第69票第二个正在出售第68票第一个正在出售第67票第三个正在出售第66票第二个正在出售第65票第一个正在出售第64票第三个正在出售第63票第一个正在出售第62票第二个正在出售第61票第三个正在出售第60票第三个正在出售第59票第二个正在出售第58票第一个正在出售第57票第二个正在出售第56票第一个正在出售第55票第三个正在出售第54票第二个正在出售第53票第三个正在出售第52票第一个正在出售第51票第一个正在出售第50票第二个正在出售第49票第三个正在出售第48票第二个正在出售第47票第一个正在出售第46票第三个正在出售第45票第一个正在出售第44票第二个正在出售第43票第三个正在出售第42票第二个正在出售第41票第一个正在出售第40票第三个正在出售第39票第三个正在出售第38票第一个正在出售第36票第二个正在出售第37票第三个正在出售第35票第二个正在出售第33票第一个正在出售第34票第三个正在出售第32票第二个正在出售第31票第一个正在出售第30票第二个正在出售第29票第三个正在出售第28票第一个正在出售第27票第三个正在出售第26票第二个正在出售第25票第一个正在出售第24票第二个正在出售第23票第一个正在出售第22票第三个正在出售第21票第二个正在出售第20票第一个正在出售第19票第三个正在出售第18票第二个正在出售第17票第一个正在出售第16票第三个正在出售第15票第二个正在出售第14票第一个正在出售第13票第三个正在出售第12票第二个正在出售第11票第一个正在出售第10票第三个正在出售第9票第二个正在出售第8票第一个正在出售第7票第三个正在出售第6票第二个正在出售第5票第一个正在出售第4票第三个正在出售第3票第二个正在出售第2票第一个正在出售第1票第三个正在出售第0票第二个正在出售第-1票
在线程没有加入休眠时间也就是延迟的时候,虽然也存在线程安全问题,但是不明显,当我们为了符合实际在卖票的时候加入延迟后,出现了重复票和0张和-1 张票,出现原因上面程序中有注释,为解决这种线程安全问题,我们有三种解决方法:

第一种是同步代码块,就是将你程序中可能出现线程安全问题的代码用一个同步代码框起来

方法:synchronized(对象){

需要同步的代码

}

下面是代码:

public class ShellTicket implements Runnable{private int tickets = 100;Object obj = new Object();@Overridepublic void run() {//让窗口一直处于打开的状态while (true) {//同步代码块synchronized (obj) {//synchronized (this) {//this指代的是本类对象,this = st = new ShellTicket() ,所以也可以锁住if (tickets>0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets--+"票");}}}}}
那么同步代码块是如何保证线程安全的呢?

第一点是这三个线程共享一个对象(锁),如果每个线程的对象(锁)不同,就无法保证线程安全了,然后是3个线程抢夺cpu的执行权利,比如说当第一个线程抢到执行权利了,那么他就开始执行同步代码块中的同步代码,这时候其他线程是被阻挡在同步代码外边的,当这个线程执行完代码之后,释放锁,然后3个再重新开始抢占执行权,以此来保证线程安全。

第二种方法是同步方法(仅适用于实现Runnable接口的类):

public synchronized void sellTicket(){

同步代码

}
同步方法的锁对象是:this

下面是代码:

public class ShellTicket implements Runnable{   static int tickets = 100;int i=0;@Overridepublic void run() {//让窗口一直处于打开的状态while (true) {if (i%2==0) {synchronized (this) {if (tickets>0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets--+"票");}}}else{shellTicket();//使用同步方法}i++;}}//普通方法//public  void shellTicket(){//synchronized (obj) {//while (true) {//if (tickets>0) {//try {//Thread.sleep(100);//} catch (InterruptedException e) {//e.printStackTrace();//}//System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets--+"票");//}//}//}//}//同步方法,同步方法对应的锁是thispublic synchronized void shellTicket(){if (tickets>0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets--+"票");}}}
第三种方法是静态同步方法:

public static synchronized void sellTicket() 

需要同步的代码
}

它与同步方法不同的地方有三点:

1、静态同步方法需要在同步方法的关键字种加上静态static

2、静态同步方法所对应的锁是类的字节码文件:类名.class

3、由于方法是静态的所以即使创建不同的对象也不会出现线程安全问题
下面是代码:

package com.edu_07;public class ShellTicket implements Runnable{static int tickets = 100;int i=0;@Overridepublic void run() {//让窗口一直处于打开的状态while (true) {if (i%2==0) {synchronized (ShellTicket.class) {if (tickets>0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets--+"票");}}}else{shellTicket();//使用静态同步方法}i++;}}private static synchronized void shellTicket(){if (tickets>0) {try {//在加入休眠之后出现了重复票和0张-1张票,出现了线程安全问题Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets--+"票");}}}
这是在加入三种方法后的程序结果,由于三种结果一样,所以只个一个

第二个正在出售第100票第一个正在出售第99票第三个正在出售第98票第三个正在出售第97票第三个正在出售第96票第一个正在出售第95票第二个正在出售第94票第一个正在出售第93票第三个正在出售第92票第一个正在出售第91票第二个正在出售第90票第二个正在出售第89票第二个正在出售第88票第二个正在出售第87票第二个正在出售第86票第二个正在出售第85票第二个正在出售第84票第二个正在出售第83票第二个正在出售第82票第二个正在出售第81票第二个正在出售第80票第二个正在出售第79票第一个正在出售第78票第三个正在出售第77票第三个正在出售第76票第三个正在出售第75票第三个正在出售第74票第三个正在出售第73票第三个正在出售第72票第三个正在出售第71票第三个正在出售第70票第一个正在出售第69票第一个正在出售第68票第一个正在出售第67票第一个正在出售第66票第二个正在出售第65票第二个正在出售第64票第二个正在出售第63票第二个正在出售第62票第二个正在出售第61票第二个正在出售第60票第二个正在出售第59票第二个正在出售第58票第二个正在出售第57票第二个正在出售第56票第二个正在出售第55票第二个正在出售第54票第二个正在出售第53票第二个正在出售第52票第二个正在出售第51票第二个正在出售第50票第二个正在出售第49票第二个正在出售第48票第二个正在出售第47票第二个正在出售第46票第一个正在出售第45票第一个正在出售第44票第三个正在出售第43票第一个正在出售第42票第一个正在出售第41票第一个正在出售第40票第一个正在出售第39票第一个正在出售第38票第一个正在出售第37票第一个正在出售第36票第一个正在出售第35票第一个正在出售第34票第二个正在出售第33票第二个正在出售第32票第二个正在出售第31票第二个正在出售第30票第二个正在出售第29票第二个正在出售第28票第二个正在出售第27票第一个正在出售第26票第一个正在出售第25票第一个正在出售第24票第一个正在出售第23票第一个正在出售第22票第一个正在出售第21票第一个正在出售第20票第一个正在出售第19票第一个正在出售第18票第一个正在出售第17票第一个正在出售第16票第三个正在出售第15票第三个正在出售第14票第一个正在出售第13票第一个正在出售第12票第一个正在出售第11票第一个正在出售第10票第一个正在出售第9票第二个正在出售第8票第二个正在出售第7票第二个正在出售第6票第二个正在出售第5票第二个正在出售第4票第二个正在出售第3票第二个正在出售第2票第二个正在出售第1票