Java多线程通信、同步卖票实例--线程安全、详细注释
来源:互联网 发布:未来日记 知乎 编辑:程序博客网 时间:2024/05/17 23:37
实现线程的有继承 thread类和实现runnable接口两种方式,一般没人会说实现callable接口这个方式,所以,这就暂不考虑这个方法。
下面分别以这2种方式实现线程安全的卖票的例子。
1,继承thread类来实现多线程卖票。
先是票的代码
package com.lxk.threadTest.ticket.extend;/** * Created by lxk on 2017/6/25 */public class Ticket extends Thread { //private int ticket = 100;//创建一个对象就有100张票。错误一:几个线程都打印一次100-1。不合适。所以,如下操作,换成静态变量。 private static int ticket = 100;//静态变量是所有对象都共享,100张票。几个线程,卖的都是一个票。但是,一般都不这么干,静态变量,生命周期太长。 /** * 实现自定义线程的名称 */ public Ticket(String name) { super(name); } /** * 这地方就是需要注意的地方,如果不加[synchronized],就会发生线程安全问题。 * 奇怪了, * 怎么还是线程不安全,还是会执行出0,-1,-2。的结果出来。 * 错误原因的分析: * 可以看到添加的锁的对象是this,但是在main方法中有4个对象,每个对象都对自己加锁,锁不同,所以,还是不安全的。 * 比如:换成都对Ticket.class(内存中就有一份字节码文件存在)加锁,那就安全啦。 */ @Override //public synchronized void run() {//【①】 错误二:即使添加了同步方法(此处注释的代码),锁的是this,是线程不安全的。 public void run() { while (true) { //synchronized (this) {//【①】 错误二:即使添加了同步代码块,锁的也是this,是线程不安全的。 //synchronized (Ticket.class) {//【①】正确同步方式: 必须所有实例化的对象都锁相同的一个家伙,那就安全啦。 if (ticket > 0) { //睡一下,好实现线程不安全的现象,前提是这个方法,没有添加synchronized,同步函数。 try { Thread.sleep(10); } catch (Exception ignored) { } //错误三:不添加同步(即注释掉标记:①的所有代码),多线程操作则会打印出0,-1,-2 //分析: //假设1线程运行到下行代码处,还未执行,此时ticket的值仍然为1,那么其他线程继续执行还是都会进到这个判断 //假设其他几个线程都恰好停到此处,那么依次执行完之后,四个线程的结果就是,1,0,-1,-2. System.out.println(this.getName() + " sale:" + ticket--); } //} //【①】 } }}
下面是main方法
package com.lxk.threadTest.ticket.extend;/** * 卖票例子(继承Thread类,实现多线程) * <p> * Created by lxk on 2017/6/25 */public class Main { public static void main(String[] args) { Ticket ticket1 = new Ticket("ticket1"); Ticket ticket2 = new Ticket("ticket2"); Ticket ticket3 = new Ticket("ticket3"); Ticket ticket4 = new Ticket("ticket4"); ticket1.start(); ticket2.start(); ticket3.start(); ticket4.start(); }}
然后就是这个代码的执行结果。
先不要惊慌:我都说了线程安全了,怎么还是输出了0,-1,-2,这些个不合法的票呢。
其实,这是上面ticket类里面的代码把那些同步的代码都给注释了,就是要示范一下,怎么个线程不安全。
也就是带有这个的(//【①】)代码,打开的话(标记过的是错误的就表打开啦 ),就不会看到卖出0,-1,-2的票啦
上面几种情况,我都把详细的注释,写在代码里了。跟着代码走,应该可以很好的理解这个多线程是怎么运行的吧。
这有个前提:就是你知道cpu是轮询执行程序的。这个是最基本的概念啦,每个线程,他是说停就停的,知道这个就好说啦。
2,再是实现runnable接口来实现多线程卖票的实例。
还是先看票的代码
package com.lxk.threadTest.ticket.implement;/** * Created by lxk on 2017/6/25 */public class Ticket implements Runnable { private int tick = 100; boolean flag = true; //Object object = new Object(); public void run() { if (flag) { while (true) { //synchronized (object) {//这个同步代码块使用的锁是object,而下面的同步函数使用的是锁是this,所以,这么干就线程不安全。 synchronized (this) {//换成this就变得安全啦。说明下面同步函数使用的锁是this if (tick > 0) { try { Thread.sleep(10); } catch (Exception ignore) { } System.out.println(Thread.currentThread().getName() + "....sale...代码块 : " + tick--); } } } } else { while (true) { show(); } } } private synchronized void show() {//使用的锁就是this if (tick > 0) { try { Thread.sleep(10); } catch (Exception ignore) { } System.out.println(Thread.currentThread().getName() + "....sale...函数 : " + tick--); } }}可以看到上面,因为第一个继承的例子中,使用了同步代码块,或者同步函数,来解决同步问题。那么在下面这个例子中,直接把2个给弄到一起,看效果。
线程1走同步代码块,线程2走同步方法。都可以实现多线程同步的效果。
然后就是看main的代码
package com.lxk.threadTest.ticket.implement;/** * 卖票例子(实现Runnable接口,实现多线程) * <p> * Created by lxk on 2017/6/25 */public class Main { public static void main(String[] args) { //test(); testStatic(); } /** * 测试普通的同步代码块和同步函数的同步效果。 * 结论:同步函数和同步代码块使用的锁都是this */ private static void test() { Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); //现在修改为只有2个线程,1使用同步代码块,2使用同步函数。 //测试发现:两者使用的锁是不同的,因为使用的不是同一个锁,所以,线程还是不安全的。(下面分析为什么这要sleep()) t1.start(); //这时候,在没有添加下面的sleep的时候,代码一运行,所有执行结果是:全走的是同步函数, //因为线程1启动完之后,瞬间,主线程已经把flag置成false啦,所以,2个线程都走的是false结果。 //所以,要在这1线程启动完之后,主线程休息一下,就剩1线程在跑,才能看到2个线程分别的效果。 try { Thread.sleep(10); } catch (Exception ignore) { } t.flag = false; t2.start(); //运行结果:打印出0的错票。不安全。(此时,同步代码块使用的锁,是自己new的一个obj) //对错误代码进行分析如下: //两个前提。1,两个或以上的线程;2,用的是否是同一个锁。 // 后面修改同步代码块中的同步对象由object变成this,然后就安全啦。 // //这个修改完之后,就可以看到,没有输出0啦,而且2个线程,确实都执行了不同的同步实现。一个同步代码块,一个同步函数。 //Thread t3 = new Thread(t); //Thread t4 = new Thread(t); //t3.start(); //t4.start(); } private static void testStatic() { TicketStatic t = new TicketStatic(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); t1.start(); //这时候,没有添加下面的sleep的时候,代码一运行,所有执行结果全走的是同步函数, //因为线程1启动完之后,瞬间,主线程已经把flag置成false啦,所以,都走的是false结果。 //所以,要在这1线程启动完之后,主线程休息一下才能看到2个线程分别的效果。 try { Thread.sleep(10); } catch (Exception ignore) { } t.flag = false; t2.start(); //运行结果:打印出0的错票。不安全。(这个时候,同步代码块使用的锁,是自己this) //静态方法使用的锁和同步代码块使用的锁不一样。静态同步函数使用的锁是类.class //对错误代码进行分析如下: //两个前提。1,两个或以上的线程;2,用的是否是同一个锁。 // 后面修改同步函数中的同步对象由this变成.class,然后就安全啦。 // //这个修改完之后,就可以看到,没有输出0啦,而且2个线程,确实都执行了不同的同步实现。一个同步代码块,一个同步函数。 }}
在main方法里面看到了2个测试方法。
那么再看看这另一个票的代码实现。
package com.lxk.threadTest.ticket.implement;/** * 测试:静态同步函数和非静态的差别 * <p> * Created by lxk on 2017/6/25 */public class TicketStatic implements Runnable { private static int tick = 100; boolean flag = true; public void run() { if (flag) { while (true) { //synchronized (this) {//静态同步函数使用的是类对象。 synchronized (TicketStatic.class) {//内存中没有本类对象,但是一定有该类对应的字节码文件对象。类名.class if (tick > 0) { try { Thread.sleep(10); } catch (Exception ignore) { } System.out.println(Thread.currentThread().getName() + "....sale...代码块 : " + tick--); } } } } else { while (true) { show(); } } } private static synchronized void show() {//静态方法的时候,使用的锁就不是this,经测试,可发现使用的是类.class if (tick > 0) { try { Thread.sleep(10); } catch (Exception ignore) { } System.out.println(Thread.currentThread().getName() + "....sale...函数 : " + tick--); } }}
这2个一起的原因,就是测试一下一般的同步函数和静态的同步函数,他们加锁的对象是谁。
经过测试,一般的普通同步函数,可以对this加锁,因为从代码可以看出来,这2个线程操作的都是同一个对象。
在上面最开始的以继承的方式实现多线程的第一个例子里面,是四个线程都各自操作各自的对象。
但是,那个票,是静态的,那么,静态的东西是属于类的,所以,虽然有四个对象,但是他们都操作的是一个共同的数据,那就得考虑线程安全问题,那就得考虑如何同步。
在这个实现runnable接口来实现多线程卖票的2个例子。
差别在这个地方。
一个是静态的,一个是非静态的,静态方法只能操作静态变量,所以,在第二个类里面,票变量被声明为静态的。第一个则不用。
而且,对于实现runnable接口的第一个票的例子,
发现2个线程,加锁的对象要相同,才能实现线程安全。
对于实现runnable接口的第二个票的例子,
发现,对于静态的方法而言,加锁的对象,就应该是类.class,这样才能线程安全。
有兴趣的小伙伴 可以把代码直接拿出来,自己测试一下,看看具体执行效果。更好的加深下理解。
- Java多线程通信、同步卖票实例--线程安全、详细注释
- 线程同步问题,线程上锁---多线程“卖票”实例
- JAVA线程实例-----卖票
- java 线程同步--卖票问题
- Java 线程同步 卖票问题
- Java多线程之线程安全与同步实例
- Java多线程实例之卖票
- JAVA 多线程--线程同步安全
- java-多线程 | 线程安全和线程同步
- Java线程同步卖票问题解决方法
- 多线程同步卖票demo
- java多线程之线程间同步通信
- 线程同步,卖票
- 用线程同步卖票
- 多线程之多窗口卖票&线程之间的通信
- 线程示例:多线程卖票
- java多线程(二)之线程安全和线程同步
- Java多线程之并发安全经典案例-卖票
- UE4 打包设置全屏模式方法--GameUserSettings的使用
- 字典
- Java-IO框架-文件输入输出流
- 转载:SCI 投稿Cover letter模板大全
- 页面分成左右部分
- Java多线程通信、同步卖票实例--线程安全、详细注释
- javascript面向对象
- Linux中的重定向和管道
- 关于ASP.net TextBox控件的失去焦点后触发其它事件
- [组合数学]BZOJ 1008——[HNOI2008]越狱
- UDP协议以及与TCP区别
- MFC中tab控件的应用
- HTML Purifier --非常好用的XSS过滤器
- C++与C的文件读入写入