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()方法;(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://stackoverflow.com/questions/7313657/should-you-synchronize-the-run-method-why-or-why-not
参考:
https://blog.csdn.net/tomcat_2014/article/details/60575942