标签:
看到一篇讲线程的故事性文章,觉得很有意思,很佩服作者能这么生动地讲述出来,点击可跳转阅读此文章:《我是一个线程》
继续我的笔记中总结 - -
下面是书上看到的卖票例子:模拟3个窗口同时在售10张票。
上篇博文笔记总结了多线程创建的两种方式,那我们就分别以这两种实现多线程的方式来解决这个场景。
上Demo:
class SaleTicket extends Thread { int num = 10; // 票数 public SaleTicket(String name) { super(name); } @Override public void run() { while (true) { if (num > 0) { System.out.println(Thread.currentThread().getName() + "窗口售出了第" + num + "号票" ); try { sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } num--; } else { System.out.println("售罄了..."); break; } } } } public class Demo1 { public static void main(String[] args) { //创建三个线程对象 SaleTicket th1 = new SaleTicket("A"); SaleTicket th2 = new SaleTicket("B"); SaleTicket th3 = new SaleTicket("C"); //开启线程 th1.start(); th2.start(); th3.start(); } }
Dmeo1执行结果图为:
从实验结果得到,本来10张的门票却卖出了30张,Why?
static int num = 10;//票数 非静态的成员变量,非静态的成员变量数据是在每个对象中都会维护一份数据的。
改完后再运行之,结果如下:
这时候发现确实输出的是10条记录(除去头三条可能出错的三条),说明static起作用,达到了共享的目的。
但细细一看,新的问题随之而来,A、B、C窗口居然会出现售出同一张票的情况!Why?
这便搭上了今天的主题了,线程安全问题。
分析如下:
假设首先A线程抢夺了cpu的执行权,开始执行,当执行到
System.out.println(Thread.currentThread().getName() + "窗口售出了第" + num + "号票" );
后(还未执行num--),num为10,此时B线程抢占了cpu执行权开始执行,同样执行到
System.out.println(Thread.currentThread().getName() + "窗口售出了第" + num + "号票" );
后,num为50,C线程抢夺了cpu执行权,C同样执行到AB执行的相同的代码处,此时ABC三者num值都为10;因而才会出现以上现象。
那么就得考虑,如何保证当一个线程在执行整块的代码时,不受其他线程的干扰?
同步机制分为同步代码块和同步方法
先来看同步代码块的格式:
synchronized(锁对象){ //需要被同步的代码 ... }
当一个线程要使用火车票这个资源时,我们就交给它一把锁,等它把事情做完后再把锁给另一个需要用这个资源的线程.
synchronized (SaleTicket.class) { if (num > 0) { System.out.println(Thread.currentThread().getName() + "窗口售出了第" + num + "号票" ); try { sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } num--; } else { System.out.println("售罄了..."); break; } }
再次运行之:
由此得到了我们想要的正确结果。
同步代码块要注意的事项:
public void run() { while (true) { if (num > 0) { try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.sale(); } else { break; } } }
// 同步方法 public synchronized void sale() { if(num > 0) { System.out.println(Thread.currentThread().getName() + "窗口售出了第" + (num--) + "号票" ); } }
同步函数要注意的事项 :
那么,对于这两种实现同步机制的方式,推荐使用的是:同步代码块
原因也很简单:
Demo2:
class SaleTicket2 implements Runnable { int num = 10; @Override public void run() { while (true) { synchronized (SaleTicket2.class) { if (num > 0) { System.out.println(Thread.currentThread().getName() + "窗口售出了第" + num + "号票"); num--; } else { break; } } } } }
public class Demo2 { public static void main(String[] args) { //创建了一个Runnable实现类的对象 SaleTicket2 saleTicket = new SaleTicket2(); //创建三个线程对象模拟三个窗口 Thread th1 = new Thread(saleTicket, "A"); Thread th2 = new Thread(saleTicket, "B"); Thread th3 = new Thread(saleTicket, "C"); //开启线程售票 th1.start(); th2.start(); th3.start(); } }
运行之,结果如下:
同样也得到了正确的结果。
引发线程安全问题的根本原因是什么?
1. 存在两个或两个以上的线程对象,并且线程之间共享同一个资源。
2. 有多条语句操作了共享资源。
解决方案:
使用同步机制来解决,即通过同步代码块(推荐)或同步方法将可能引发线程安全问题的代码段“锁”住。
标签:
原文地址:http://www.cnblogs.com/crayon-v/p/5398655.html