Java——多线程小例子
某电影院正在上映《速度与激情7》,共有100张票。它又三个售票窗口正在售票。请设计一个应用程序来模拟该电影院的售票
两种方式实现:继承Thread类;实现Runable接口
两种实现方式的对比:
方法1:需要多个对象
方法2:只需要新建一个对象即可,放入三个不同线程;实现了数据和业务模型的分离
该程序跟实际情况还有一些距离,因为实际情况下,售票数据通过网络传输,总是存在一些延迟的情况。所以在真正售出一张票后,需要一段时间,才可以真正去修改剩余票数。
继续更新我们的代码:每次买票延迟100ms,后再去修改剩余票数
问题1
相同的票 卖了多次
问题2
出现了负数的票数
注意:线程安全问题在理想情况下不易出现,但是一旦出现,影响将非常大
如何解决线程安全问题:
分析出问题的原因:多线程,共享数据,操作共享数据并非原子操作(是否有多条语句)
解法:将出问题的原因或条件破坏掉
解决问题:
同步代码块
格式: Synchronized(对象){ //让这里的代码变成一个原子操作,不会再代码块的某一个地方切换到其他线程;对象可以是Object 需同步代码块 } |
同步代码块的对象可以是?
需要同步的代码块是?
MainClass.java | |
package com.java.ticket; public class MainClass { public static void main(String[] args) { //方法一 Window window1=new Window(100); Window window2=new Window(100); Window window3=new Window(100); window1.start(); window2.start(); window3.start(); //方法二 /* SellTicket sellTicket=new SellTicket(); Thread t1=new Thread(sellTicket,"窗口1"); //用同一个对象初始化三个线程,并每个线程命名 Thread t2=new Thread(sellTicket,"窗口2"); Thread t3=new Thread(sellTicket,"窗口3"); t1.start(); t2.start(); t3.start();*/ } } | |
Window.java | SellTicket.java |
package com.java.ticket; public class Window extends Thread { static int ticket; Object object=new Object(); public void run() { super.run(); while(ticket>0){ //卖票 //方式一: /* synchronized (object){ sell(); }*/ //方式二 sell(); } } /* void sell(){ if(ticket>0){ System.out.println(getName()+"卖出第 "+ticket--+" 张票"); //执行这条语句的同时,共享数据自减 } }*/ public synchronized void sell(){ //最佳 if(ticket>0){ //执行这条语句的同时,共享数据自减,属于一个原子操作 } } public Window(int ticket) { super(); this.ticket = ticket; } } | package com.java.ticket; public class SellTicket implements Runnable { Object object=new Object(); int ticket=100; public void run() { //synchronized(object){ //让这里的代码变成一个原子操作,不会再代码块的某一个地方挂起切换到其他线程 //同步语句在这里就只有一个窗口卖票,其他窗口无法打断 while(ticket>0){ //出现卖负票是因为在这里挂起 //synchronized(object){ //同步语句加在这里,会出现卖负票 synchronized(object){ //括号里(new Object)就不行,会出现多张相同的票;因为每个线程调用run方法,都会new一个对象,多个线程就会有多个锁 //这里object也可以是this,因为三个线程都是用同一个对象来初始化的,所以obj也不用声明成静态的 if(ticket>0){ System.out.println(Thread.currentThread().getName()+"卖出第 "+ticket--+" 张票"); //ticket--; } } } } } |
test1.java | test2.java |
package com.java.threadExercise; import java.util.Random; public class test2 { /** * 创建一个任务,它将睡眠1到10秒之间的随机数量的时间, * 然后显示它的睡眠时间并退出。创建并运行多个这种任务。 */ public static void main(String[] args) { Thread1 t1=new Thread1("No.1"); Thread1 t2=new Thread1("No.2"); Thread1 t3=new Thread1("No.3"); t1.start(); t2.start(); try { //等待线程t2执行完毕才会执行下面的语句;开始t3线程 } catch (InterruptedException e) { e.printStackTrace(); } t3.start(); } } class Thread1 extends Thread { public Thread1(String string){ super(string); } public void run(){ super.run(); Random r1=new Random(); int i=r1.nextInt(11)*1000; //随机生成10以内的数,后面sleep里面要是毫秒,所以乘以1000 try { sleep(i); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" 睡眠时间: "+i/1000); } } | package com.java.threadExercise; import java.util.Random; public class test3 { /** * 将所有线程修改成守护线程,并验证一旦main函数退出,程序立刻终止。 */ public static void main(String[] args) { Thread2 t1=new Thread2("No.1"); Thread2 t2=new Thread2("No.2"); Thread2 t3=new Thread2("No.3"); t1.setDaemon(true); t2.setDaemon(true); t3.setDaemon(true); t1.start(); t2.start(); t3.start(); System.out.println("主线程结束。。。。"); } } class Thread2 extends Thread { public Thread2(String string){ super(string); } public void run(){ super.run(); Random r1=new Random(); int i=r1.nextInt(11)*1000; //随机生成10以内的数,后面sleep里面要是毫秒,所以乘以1000 try { sleep(i); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" 睡眠时间: "+i/1000); } } //主线程结束,守护线程也就结束了; |
test3.java | //test3.java |
public class test4 { public static void main(String[] args) { Scanner in=new Scanner(System.in); int num=in.nextInt(); Object obj=new Object(); int x=in.nextInt(); Thread3.getValue(num, x); for(int i=0;i<num;++i){ new Thread3("第"+(i+1)+"个下载线程").start(); } } } class Thread3 extends Thread{ public static int Num; public static int X; static Object obj=new Object(); public Thread3(String string){ super(string); } public static void getValue(int num,int x){ Num=num; X=x; } public void run() { super.run(); while(X>=0){ synchronized(obj){ //注意 ,这里要Obj必须是同一个对象才能实现加锁,不然会出现多把锁,失去意义;所以如果有多个线程对象,obj要声明成静态的 if(X<=0){ System.out.println("下载完成"); System.exit(0); } System.out.println(this.getName()+" :剩余"+X+"M未下载"); X--; } } } } | public class test4 { public static void main(String[] args) { Scanner in=new Scanner(System.in); int num=in.nextInt(); Object obj=new Object(); int x=in.nextInt(); Thread3.getValue(num, x); Thread3 t3=new Thread3(); for(int i=0;i<num;++i){ //用同一个对象初始化线程,可以共用数据,下面run方法就可以不用把obj定义成static,因为所有线程都共用数据 } } } class Thread3 implements Runnable{ public static int Num; public static int X; //不用定义成静态的 public static void getValue(int num,int x){ Num=num; X=x; } public void run() { while(X>=0){ synchronized(obj){ //this也可以,因为是一个对象初始化的线程 if(X<=0){ System.out.println("下载完成"); System.exit(0); } System.out.println(Thread.currentThread().getName()+" :剩余"+X+"M未下载"); X--; } } } } |