Java创建多线程的两种基本方法:
方法1.继承Thread类
(1)定义子类,继承Thread类,重写该类的run()方法作为线程执行体;
(2)创建该子类的实例作为线程对象;
(3)调用线程对象的start()方法来启动线程;
我们以模拟火车售票系统为例:
public class SellTicket { public static void main(String[] args) { for(int i=1; i<4; i++){ TicketWindow tw = new TicketWindow(); tw.setName("TicketWindow-" + i); tw.start(); } } } class TicketWindow extends Thread{ private int tickets = 100;//车票总量 @Override public void run(){ while(true){ if(tickets>0){ System.out.println(Thread.currentThread().getName() + "准备出票,剩余票数:" + tickets + "张"); tickets--; System.out.println(Thread.currentThread().getName() + "卖出一张,剩余票数:" + tickets + "张"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } else{ System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!"); break; } } } }
方法2.实现Runnable接口
(1)定义类实现Runnable接口,重写run()方法;(2)创建该实现类的实例,以该实例作为Thread的target来创建Thread对象;
(3)调用该Thread对象的start()方法来启动该线程;
还是以模拟火车售票窗口为例:
public class SellTicket { public static void main(String[] args) { TicketWindow tw = new TicketWindow(); for(int i=1; i<4; i++){ Thread t = new Thread(tw,"TickWindow-" + i); t.start(); } } } class TicketWindow implements Runnable{ private int tickets = 100;//车票总量 @Override public void run(){ while(true){ if(tickets>0){ System.out.println(Thread.currentThread().getName() + "准备出票,剩余票数:" + tickets + "张"); tickets--; System.out.println(Thread.currentThread().getName() + "卖出一张,剩余票数:" + tickets + "张"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } else{ System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!"); break; } } } }
注意:
继承Thread类与实现Runnable接口的区别及优缺点对比:
继承Thread类 | 实现Runnable接口 | |
能否继承其他类 | 不能 | 能 |
如何访问当前线程 | this即为当前线程 | Thread.currentThread() |
是否共享同一target | 否 | 是,适合多个相同线程处理同一份资源的情况 |
虽然上面实现Runnable接口的火车售票系统共享了车票总数,但是没有控制同一时刻只能有一个线程进行卖票操作,因此需要同步关键字 synchronized 进行控制,
线程同步分为同步块和同步方法:
1.同步块
同步块的语法格式为:
synchronized(object){ //...同步代码块 }
上述代码的意思是,要想执行同步代码块,必须先获得同步监视器的锁定.
其中object为同步监视器,一般使用可能被并发访问的共享资源当同步监视器;
下面给出java多线程模拟火车售票系统的同步块实现:
public static void main(String[] args) { TicketWindow tw = new TicketWindow(); for(int i=1; i<4; i++){ Thread t = new Thread(tw,"TickWindow-" + i); t.start(); } } } class TicketWindow implements Runnable{ private int tickets = 10;//车票总量 @Override public void run(){ while(true){ synchronized (this) { if(tickets>0){ System.out.println(Thread.currentThread().getName() + "准备出票,剩余票数:" + tickets + "张"); tickets--; System.out.println(Thread.currentThread().getName() + "卖出一张,剩余票数:" + tickets + "张"); try { //休眠100ms卖票完会报错ERROR: JDWP Unable to get JNI 1.2 environment, jvm->GetEnv() return code = -2JDWP exit error AGENT_ERROR_NO_JNI_ENV(183): [../../../src/share/back/util.c:820] //Thread.sleep(100); Thread.sleep(500);//出票成功后让当前售票窗口睡眠,以便让其他售票窗口卖票 } catch (InterruptedException e) { e.printStackTrace(); } } else{ System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!"); break; } } } } }
输出:
TickWindow-1准备出票,剩余票数:10张 TickWindow-1卖出一张,剩余票数:9张 TickWindow-1准备出票,剩余票数:9张 TickWindow-1卖出一张,剩余票数:8张 TickWindow-1准备出票,剩余票数:8张 TickWindow-1卖出一张,剩余票数:7张 TickWindow-1准备出票,剩余票数:7张 TickWindow-1卖出一张,剩余票数:6张 TickWindow-1准备出票,剩余票数:6张 TickWindow-1卖出一张,剩余票数:5张 TickWindow-1准备出票,剩余票数:5张 TickWindow-1卖出一张,剩余票数:4张 TickWindow-1准备出票,剩余票数:4张 TickWindow-1卖出一张,剩余票数:3张 TickWindow-1准备出票,剩余票数:3张 TickWindow-1卖出一张,剩余票数:2张 TickWindow-3准备出票,剩余票数:2张 TickWindow-3卖出一张,剩余票数:1张 TickWindow-3准备出票,剩余票数:1张 TickWindow-3卖出一张,剩余票数:0张 TickWindow-3余票不足,停止售票! TickWindow-2余票不足,停止售票! TickWindow-1余票不足,停止售票!
2.同步方法.
同步方法就是使用 synchronized 关键字修饰某个方法,synchronized 修饰的实例方法(非static方法)的同步监视器是this.
使用同步方法解决共享资源的多线程访问冲突的一般方式是:
//把修改共享资源的方法使用synchronized进行修饰 public synchronized void someMethod(){ //do something... } //在run()方法中调用该同步方法 public void run(){ someMethod(); }
下面给出使用同步方法实现的模拟火车售票系统:
public class SellTicket { public static void main(String[] args) { TicketWindow tw = new TicketWindow(); for(int i=1; i<4; i++){ Thread t = new Thread(tw,"TickWindow-" + i); t.start(); } } } class TicketWindow implements Runnable{ private int tickets = 10;//车票总量 @Override public void run(){ while(true){ sellTicket(); } } public synchronized void sellTicket(){ if(tickets>0){ System.out.println(Thread.currentThread().getName() + "准备出票,剩余票数:" + tickets + "张"); tickets--; System.out.println(Thread.currentThread().getName() + "卖出一张,剩余票数:" + tickets + "张"); try { Thread.sleep(500);//出票成功后让当前售票窗口睡眠,以便让其他售票窗口卖票 } catch (InterruptedException e) { e.printStackTrace(); } } else{ System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
输出:
TickWindow-1准备出票,剩余票数:10张 TickWindow-1卖出一张,剩余票数:9张 TickWindow-1准备出票,剩余票数:9张 TickWindow-1卖出一张,剩余票数:8张 TickWindow-1准备出票,剩余票数:8张 TickWindow-1卖出一张,剩余票数:7张 TickWindow-1准备出票,剩余票数:7张 TickWindow-1卖出一张,剩余票数:6张 TickWindow-1准备出票,剩余票数:6张 TickWindow-1卖出一张,剩余票数:5张 TickWindow-1准备出票,剩余票数:5张 TickWindow-1卖出一张,剩余票数:4张 TickWindow-1准备出票,剩余票数:4张 TickWindow-1卖出一张,剩余票数:3张 TickWindow-1准备出票,剩余票数:3张 TickWindow-1卖出一张,剩余票数:2张 TickWindow-1准备出票,剩余票数:2张 TickWindow-1卖出一张,剩余票数:1张 TickWindow-1准备出票,剩余票数:1张 TickWindow-1卖出一张,剩余票数:0张 TickWindow-1余票不足,停止售票! TickWindow-1余票不足,停止售票! TickWindow-1余票不足,停止售票! TickWindow-1余票不足,停止售票! TickWindow-1余票不足,停止售票! TickWindow-3余票不足,停止售票! TickWindow-2余票不足,停止售票! TickWindow-3余票不足,停止售票! TickWindow-3余票不足,停止售票!
注意:while(true){}要放在run()方法里面,而不是sell方法里. 否则会出现某一个窗口一直把票卖完的情况.
要想让售票窗口在售完票之后停止,需要在run()方法里的作条件限制,修改如下:
public class SellTicket { public static void main(String[] args) { TicketWindow tw = new TicketWindow(); for(int i=1; i<4; i++){ Thread t = new Thread(tw,"TickWindow-" + i); t.start(); } } } class TicketWindow implements Runnable{ private int tickets = 10;//车票总量 @Override public void run(){ while(true){ if(tickets>0){ sellTicket(); } else{ System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!"); break; } } } public synchronized void sellTicket(){ if(tickets>0){ System.out.println(Thread.currentThread().getName() + "准备出票,剩余票数:" + tickets + "张"); tickets--; System.out.println(Thread.currentThread().getName() + "卖出一张,剩余票数:" + tickets + "张"); try { Thread.sleep(500);//出票成功后让当前售票窗口睡眠,以便让其他售票窗口卖票 } catch (InterruptedException e) { e.printStackTrace(); } } } }
输出如下:
TickWindow-1准备出票,剩余票数:10张 TickWindow-1卖出一张,剩余票数:9张 TickWindow-1准备出票,剩余票数:9张 TickWindow-1卖出一张,剩余票数:8张 TickWindow-1准备出票,剩余票数:8张 TickWindow-1卖出一张,剩余票数:7张 TickWindow-1准备出票,剩余票数:7张 TickWindow-1卖出一张,剩余票数:6张 TickWindow-1准备出票,剩余票数:6张 TickWindow-1卖出一张,剩余票数:5张 TickWindow-1准备出票,剩余票数:5张 TickWindow-1卖出一张,剩余票数:4张 TickWindow-1准备出票,剩余票数:4张 TickWindow-1卖出一张,剩余票数:3张 TickWindow-1准备出票,剩余票数:3张 TickWindow-1卖出一张,剩余票数:2张 TickWindow-1准备出票,剩余票数:2张 TickWindow-1卖出一张,剩余票数:1张 TickWindow-1准备出票,剩余票数:1张 TickWindow-1卖出一张,剩余票数:0张 TickWindow-1余票不足,停止售票! TickWindow-3余票不足,停止售票! TickWindow-2余票不足,停止售票!
注意!
使用 synchronized 修饰run()方法是无效的:
public class SellTicket { public static void main(String[] args) { TicketWindow tw = new TicketWindow(); for(int i=1; i<4; i++){ Thread t = new Thread(tw,"TickWindow-" + i); t.start(); } } } class TicketWindow implements Runnable{ private int tickets = 10;//车票总量 @Override public synchronized void run(){ while(true){ if(tickets>0){ System.out.println(Thread.currentThread().getName() + "准备出票,剩余票数:" + tickets + "张"); tickets--; System.out.println(Thread.currentThread().getName() + "卖出一张,剩余票数:" + tickets + "张"); try { Thread.sleep(500);//出票成功后让当前售票窗口睡眠,以便让其他售票窗口卖票 } catch (InterruptedException e) { e.printStackTrace(); } } else{ System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
会输出:
TickWindow-1准备出票,剩余票数:10张 TickWindow-1卖出一张,剩余票数:9张 TickWindow-1准备出票,剩余票数:9张 TickWindow-1卖出一张,剩余票数:8张 TickWindow-1准备出票,剩余票数:8张 TickWindow-1卖出一张,剩余票数:7张 TickWindow-1准备出票,剩余票数:7张 TickWindow-1卖出一张,剩余票数:6张 TickWindow-1准备出票,剩余票数:6张 TickWindow-1卖出一张,剩余票数:5张 TickWindow-1准备出票,剩余票数:5张 TickWindow-1卖出一张,剩余票数:4张 TickWindow-1准备出票,剩余票数:4张 TickWindow-1卖出一张,剩余票数:3张 TickWindow-1准备出票,剩余票数:3张 TickWindow-1卖出一张,剩余票数:2张 TickWindow-1准备出票,剩余票数:2张 TickWindow-1卖出一张,剩余票数:1张 TickWindow-1准备出票,剩余票数:1张 TickWindow-1卖出一张,剩余票数:0张 TickWindow-1余票不足,停止售票! TickWindow-1余票不足,停止售票! TickWindow-1余票不足,停止售票! TickWindow-1余票不足,停止售票!
原因见:
https://*.com/questions/7313657/should-you-synchronize-the-run-method-why-or-why-not
参考:
https://blog.csdn.net/tomcat_2014/article/details/60575942