JavaSE之多线程
Posted wholovewangjie
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaSE之多线程相关的知识,希望对你有一定的参考价值。
今天要讲的还是多线程,但在此之前先做个练习,用多线程模拟电影院售票,问题:某电影院目前正在上映贺岁大片(红高粱,少林寺传奇藏经阁),共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
我们知道实现线程有两种方式,我们先来看看第一种方式:继承Thread类实现。具体代码如下:
public class SellTicket extends Thread { //定义100张票 //private int tickets=100; // 为了让多个线程对象共享这100张票,我们其实应该用静态修饰 private static int tickets=100; public SellTicket() { super(); } public SellTicket(String name){ super(name); } @Override public void run() { //每个线程进来都会走这里,这样的话,每个线程对象相当于买的是自己的那100张票,这不合理,所以应该定义到外面 //int tickets=100;//定义100张票 //模拟影院一直有票在出售 while(true){ if (tickets>0) { System.out.println(getName()+"正在出售第"+(tickets--)+"张票"); } } } } /* * 方式1: 继承Thread类来实现。 */ public class SellTicketDemo { public static void main(String[] args) { //创建三个线程对象 SellTicket st1=new SellTicket("窗口1"); SellTicket st2=new SellTicket("窗口2"); SellTicket st3=new SellTicket("窗口3"); //启动线程 st1.start(); st2.start(); st3.start(); } }
方式一运行结果:
通过对代码的分析,发现合情合理,将票数的tickets声明为静态的让三个线程对象共享,可运行结果上还是出现了,最后一张票居然卖了两次的问题。所以这是个问题,稍后再说这个问题,现在再用第二种方式实现一下:实现Runnable接口。代码如下:
public class SellTicket implements Runnable { //定义100张票 private int tickets=100; @Override public void run() { // 模拟影院一直有票在出售 while (true) { if (tickets > 0) { System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); } } } } /** * * 方式二:实现Runnable接口创建线程 * */ public class SellTicketDemo { public static void main(String[] args) { //创建资源对象 SellTicket st =new SellTicket(); //创建三个线程对象 Thread t1=new Thread(st,"窗口1"); Thread t2=new Thread(st,"窗口2"); Thread t3=new Thread(st,"窗口3"); //启动线程 t1.start(); t2.start(); t3.start(); } }
方式二运行结果:
之前说过两种方式的区别,这里能够清晰的发现第二种方式是比较好的,强烈推荐使用。方式二仅仅创建了一个资源对象,那就是100张票,不会出现同一张票被出售多次的奇葩情况。
上面的代码,实际上并不能完全的模拟售票,因为售票时的网络会出现适当的延迟,下面改进下方式二的代码模拟出现网络延迟的情形。改进后的代码如下:
public class SellTicket implements Runnable { //定义100张票 private int tickets=100; @Override public void run() { // 模拟影院一直有票在出售 while (true) { if (tickets > 0) { //为了模拟更真实的场景,需要加入延迟 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); } } } } /** * * 改进方式二:实现Runnable接口创建线程 * */ public class SellTicketDemo { public static void main(String[] args) { //创建资源对象 SellTicket st =new SellTicket(); //创建三个线程对象 Thread t1=new Thread(st,"窗口1"); Thread t2=new Thread(st,"窗口2"); Thread t3=new Thread(st,"窗口3"); //启动线程 t1.start(); t2.start(); t3.start(); } }
改进后的运行结果:
通过对改进后的运行结果的分析,发现两个问题:A:相同的票卖了多次;B:出现了负数票
先说说为什么会出现相同的票,这时因为CPU的一次操作必须是原子性的(原子性就是指最简单最基本的操作,不可再细分的操作)。分析如下:
在理想状态下,当线程t1跑起来到sleep时休息,此时线程t2抢到CPU的执行权,也跑到sleep时开始休息,此时t1又开始跑,打印出“正在出售第100张票”之后,做自减操作完毕后,t2也休息完了开始跑,就会打印“正在出售第99张票”,但是实际情况往往不同,就是会出现当t1打印完“正在出售第100张票”还没来得及做自减操作时,t2又跑起来了,所以就会打印相同的票,即是相同的票卖了多次。
再说说为什么会出现负数票,这是因为随机性和延迟导致的。分析如下:
由于随机性,可能会出现三个线程在票数为最后一张的时候相继进入到while循环里的sleep处,并且均休息了,然后,t1打印后自减,t2打印后自减,t3打印后自减,也就是说:
窗口1正在出售第1张票,tickets=0
窗口2正在出售第0张票,tickets=-1
窗口3正在出售第-1张票,tickets=-2
综上所述,就会出现出现了负数票。
一、线程安全
1.线程安全问题出现的原因(也是判断一个程序是否存在线程安全问题的标准):
A:是否是多线程环境
B:是否有共享数据
C:是否有多条语句操作共享数据
而其实,我前面的那个练习,就是满足了这三个条件,导致出现了线程安全问题,那么怎么解决线程安全问题呢?
2.解决线程安全问题的方法:
解决思想:把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行(Java的同步机制)。
解决方案一:同步代码块
格式:
synchronized(对象){ //需要同步的代码;(多条语句操作共享数据的代码部分) }
注意:
1.同步代码块能够解决线程安全问题的根本原因在于那个对象,该对象就如同一把锁,将其他的非正在运行的线程锁在门外。
2.多个线程必须是同一把锁
下面是通过同步代码块的方式解决线程安全问题的代码:
public class SellTicket implements Runnable { //定义100张票 private int tickets=100; //创建锁对象 private Object obj=new Object(); @Override public void run() { // 模拟影院一直有票在出售 while (true) { synchronized (obj) { if (tickets > 0) { //为了模拟更真实的场景,需要加入延迟 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); } } } } } /** * * 同步代码块解决线程安全问题 * */ public class SellTicketDemo { public static void main(String[] args) { //创建资源对象 SellTicket st =new SellTicket(); //创建三个线程对象 Thread t1=new Thread(st,"窗口1"); Thread t2=new Thread(st,"窗口2"); Thread t3=new Thread(st,"窗口3"); //启动线程 t1.start(); t2.start(); t3.start(); } }
对同步代码块的解释:如下图,这个图是我自己画的,不是特别好,当能稍微解释。
总结下有关同步的几点:
同步的特点:
前提:多个线程
解决问题的时候要注意:多个线程使用的是同一个锁对象
同步的好处 :同步的出现解决了多线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
文章到此结束。
以上是关于JavaSE之多线程的主要内容,如果未能解决你的问题,请参考以下文章