优雅停止Thread(线程)的3种方式
Posted 全栈行动派
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了优雅停止Thread(线程)的3种方式相关的知识,希望对你有一定的参考价值。
咱们都知道Java中实现线程的两种方式:继承Thread或者实现Runnable。不管是哪种方式最后都是操作的Thread类。本篇文章我们聊的就是正常活动中的Thread怎么停止,虽然Thread类提供了stop()方法,但是这种方法太暴力,不安全所以被弃用了。
为什么说stop()太暴力呢?我这里举个例子:一个餐厅是禁止抽烟的,但是一位男士进入餐厅掏出烟就抽,服务员发现后立马强制掐断男士手中的烟,大家可以想象后果是什么。这种方式就相当于将正在活动的Thread强制stop一样,太暴力。那服务员应该怎么做呢?
服务员是不是应该礼貌的告知抽香烟的男士:先生您好,我们餐厅是禁止抽烟的。如果男士明事理那就会掐断香烟,如果不明事理是不是就不会掐断香烟继续抽呀。这种协商机制导致的后果就是抽香烟的男士可能掐断香烟,也可能不会掐断香烟。Thread停止的方式也是类似,需要协商,假如t2线程想让t1线程停止,只能通知t1停止,但是t1是否停止只能看t1自己是否愿意停止了,咱们看看停止线程的三种方式。
1、通过volatile修饰的标识停止线程
volatile是Java中的关键字,用来修饰会被不同线程访问和修改的变量。通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。
可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。volatile即可实现线程之间的可见性。
实例代码:
package com.lc.test01;
import java.util.concurrent.TimeUnit;
/**
* @author liuchao
* @date 2023/4/8
*/
public class StopThreadOne
volatile static Boolean stopFlag = Boolean.FALSE;
public static void main(String[] args)
Thread t1 = new Thread(() ->
while (true)
if (stopFlag)
System.out.println(Thread.currentThread().getName() + ",线程被终止");
break;
System.out.println(Thread.currentThread().getName() + ",线程进行中");
, "t1");
t1.start();
try
TimeUnit.MILLISECONDS.sleep(1);
catch (InterruptedException e)
e.printStackTrace();
new Thread(() ->
stopFlag = Boolean.TRUE;
System.out.println("---已发出通知,告知t1线程停止");
, "t2").start();
效果:
t1,线程进行中
t1,线程进行中
......
t1,线程进行中
---已发出通知,告知t1线程停止
t1,线程被终止
2、通过CAS中的AtomicBoolean 标识停止线程
CAS 全称是 compare and swap,是一种用于在多线程环境下实现同步功能的机制。CAS 操作包含三个操作数 -- 内存位置、预期数值和新值。CAS 的实现逻辑是将内存位置处的数值与预期数值想比较,若相等,则将内存位置处的值替换为新值。若不相等,则不做任何操作。这种原子操作也可以保证写线程改变数据后,读线程立马能读取到改变后的数据。
实例:
package com.lc.test01;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author liuchao
* @date 2023/4/8
*/
public class StopThreadTwo
static AtomicBoolean stopFlag = new AtomicBoolean(Boolean.FALSE);
public static void main(String[] args)
Thread t1 = new Thread(() ->
while (true)
if (stopFlag.get())
System.out.println(Thread.currentThread().getName() + ",线程被终止");
break;
System.out.println(Thread.currentThread().getName() + ",线程进行中");
, "t1");
t1.start();
try
TimeUnit.MILLISECONDS.sleep(1);
catch (InterruptedException e)
e.printStackTrace();
new Thread(() ->
stopFlag.set(Boolean.TRUE);
System.out.println("---已发出通知,告知t1线程停止");
, "t2").start();
效果:
t1,线程进行中
t1,线程进行中
......
t1,线程进行中
---已发出通知,告知t1线程停止
t1,线程被终止
3、通过interrupt、isInterrupted方法配合停止线程
interrupt、isInterrupted两个方法都是Thread自带的api。
interrupt()是将一个线程的中断标识设置为true,通俗说就是告诉这个线程你需要中断。
isInterrupted()是判断线程的中断标识是否被设置为true
那使用的原理就是,t2线程将t1线程中断标识设置为true,t1线程判断中断标识是否为true,但是t1是否停止取决于t1自己是否想停止。
实例代码:
package com.lc.test01;
import java.util.concurrent.TimeUnit;
/**
* @author liuchao
* @date 2023/4/8
*/
public class StopThreadThree
public static void main(String[] args)
Thread t1 = new Thread(() ->
while (true)
if (Thread.currentThread().isInterrupted())
System.out.println(Thread.currentThread().getName() + ",线程被终止");
break;
System.out.println(Thread.currentThread().getName() + ",线程进行中");
, "t1");
t1.start();
try
TimeUnit.MILLISECONDS.sleep(1);
catch (InterruptedException e)
e.printStackTrace();
new Thread(() ->
t1.interrupt();
System.out.println("---已发出通知,告知t1线程停止");
, "t2").start();
效果:
t1,线程进行中
t1,线程进行中
......
t1,线程进行中
---已发出通知,告知t1线程停止
t1,线程被终止
这种方式使用时,有个地方需要注意:
如果t1线程内部阻塞的调用wait() 、wait(long)或wait(long, int)方法,或者在join() , join(long) , join(long, int) , sleep(long) ,或sleep(long, int) ,那么它的中断状态将被清除,并且将收到一个InterruptedException 。需要在catch中重新将中断标识设置为true
package com.lc.test01;
import java.util.concurrent.TimeUnit;
/**
* @author liuchao
* @date 2023/4/8
*/
public class StopThreadThree
public static void main(String[] args)
Thread t1 = new Thread(() ->
while (true)
if (Thread.currentThread().isInterrupted())
System.out.println(Thread.currentThread().getName() + ",线程被终止");
break;
System.out.println(Thread.currentThread().getName() + ",线程进行中");
try
TimeUnit.MILLISECONDS.sleep(5);
catch (InterruptedException e)
System.out.println("抛出异常" + e.getMessage());
//注意这里一定要重新将中断标识设置为true
Thread.currentThread().interrupt();
, "t1");
t1.start();
try
TimeUnit.MILLISECONDS.sleep(30);
catch (InterruptedException e)
e.printStackTrace();
new Thread(() ->
t1.interrupt();
System.out.println("---已发出通知,告知t1线程停止");
, "t2").start();
4、总结
不管是那种方式停止线程,都是以协商而不是暴力,t2线程告知t1线程中断,t1是不会里面停止的,最终t1是否停止取决于t1是否做了中断判断。
27多线程(多线程的三种实现方式Thread线程类的常见方法线程安全问题)
多线程
多线程实现方式一
Thread
Java虚拟机允许应用程序同时执行多个线程。
每个线程都有优先级,具有较高优先级的线程优先于优先级较低的线程执行。
当在某个线程中运行的代码创建一个新的Thread
对象时,新线程的优先级最初设置为创建线程的优先级。
每个线程都有一个用于识别的名称,多个线程可能具有相同的名称。如果在创建线程时未指定名称,则会为其生成一个新名称。
继承Thread类,重写Thread类的run方法
public class Thread implements Runnable {
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
//继承Thread,重写run方法的逻辑为:不再调用target.run()方法,而是调用子类自定义的[线程要执行的方法]
//所以Thread() / Thread(String name)的构造方式,target为null,执行的是子类MyThread的run()
//Thread类继承Runnable接口,实现run方法,target代表Runnable实例对象,即创建Thread对象时传递的Runnable类型的参数
//Thread(Runnable target)的构造方式,就不需要重写run方法,target.run()调用Runnable实现类的run逻辑
Thread类的常用方法
方法名 | 描述 |
---|---|
public final String getName()【final修饰方法,子类不允许重写】 | 获取当前线程的名称 |
public final int getPriority() | 获取当前线程的优先级 |
public final void setName(String name) | 设置当前线程的名称 |
public final void setPriority(int newPriority) | 设置当前线程优先级 |
public static native Thread currentThread(); | 获取当前执行的线程 |
public final static int MIN_PRIORITY = 1; | 最低优先级 |
public final static int NORM_PRIORITY = 5; | 默认优先级 |
public final static int MAX_PRIORITY = 10; | 最高优先级 |
package org.westos.demo;
/**
* @author lwj
* @date 2020/5/31 13:53
*/
public class MyTest {
public static void main(String[] args) {
System.out.println("主线程的main方法开始执行");
System.out.println(Thread.currentThread().getName());
//获取当前正在执行的线程对象Thread.currentThread()
//main
System.out.println(Thread.currentThread().getPriority());
//获取当前正在执行的线程的优先级
//5
//创建一个线程,1、继承Thread类,重写run方法
MyThread myThread = new MyThread();
myThread.setName("myThread-0");
//设置线程的名称
System.out.println(myThread.getName());
//获取线程的名称
//myThread-0
System.out.println(myThread.getPriority());
//获取线程的优先级
//5
myThread.setPriority(10);
//设置线程的优先级
System.out.println(myThread.getPriority());
//10
myThread.start();
//启动线程,同一线程不要重复启动
//创建另一个线程
MyThread myThread1 = new MyThread();
myThread1.setName("myThread-1");
System.out.println(myThread1.getName());
//myThread-1
System.out.println(myThread1.getPriority());
//5
myThread1.start();
/*
两个线程轮流被操作系统调度,cpu执行,具有随机性
*/
System.out.println("主线程的main方法执行完毕");
}
}
class MyThread extends Thread {
@Override
public void run() {
//run方法:该线程执行的代码
for (int i = 0; i < 100; i++) {
System.out.println("线程" + this.getName() + ":" + i);
}
}
}
线程并不一定立即执行,由操作系统进行调度。
package org.westos.demo;
/**
* @author lwj
* @date 2020/6/1 16:33
*/
public class MyTest {
public static void main(String[] args) {
MyThread myThread = new MyThread("myThread-0");
myThread.setPriority(Thread.MAX_PRIORITY);
myThread.start();
}
}
class MyThread extends Thread {
public MyThread() {}
public MyThread(String name) {
super(name);
//MyThread子类不能继承父类Thread的构造方法,但是可以使用super关键字调用父类的构造方法
}
@Override
public void run() {
System.out.println("线程" + this.getName() + this.getPriority());
//MyThread继承Thread,自动继承Thread类的getName()和getPriority()
//线程myThread-010
}
}
缺陷:单继承具有局限性。
多线程实现方式二
实现Runnable接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
package org.westos.demo2;
/**
* 实现Runnable接口
* @author lwj
* @date 2020/6/1 20:01
*/
public class MyTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable, "A线程");
//构造方法:Thread(Runnable runnableImpl)
//Thread(Runnable target, String name)
Thread thread1 = new Thread(myRunnable, "B线程");
//虽然是一个相同的任务(对象),但是两个线程在执行run方法时,都会有自己的内存空间来执行for循环,来存储变量i,所以两个变量i的内存地址不同,那么就是两次的0-19
thread.start();
//开启线程
thread1.start();
}
/*
B线程---0
B线程---1
B线程---2
B线程---3
B线程---4
A线程---0
A线程---1
A线程---2
B线程---5
B线程---6
B线程---7
B线程---8
B线程---9
A线程---3
A线程---4
A线程---5
A线程---6
A线程---7
A线程---8
A线程---9
每个线程都有自己的PC寄存器/程序计数器,还有自己的虚拟机栈内存,存储自己线程的方法调用
*/
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
优点:避免了单继承的局限性,方便同一个对象被多个线程使用。
多线程实现方式三
实现Callable接口
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
相较于实现Runnable接口的方式,方法有返回值,并且可以抛出异常。
这种方式需要 FutureTask 实现类的支持(包装Callable),用于接收运算结果。FutureTask 是Runnable接口的实现类,Thread(Runnable target)来接收FutureTask实例。
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
package org.westos.demo2;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 实现Callable接口
* @author lwj
* @date 2020/6/1 20:18
*/
public class MyTest2 {
public static void main(String[] args) {
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable(100));
Thread thread = new Thread(futureTask);
//Thread(Runnable target)
thread.start();
try {
Integer integer = futureTask.get();
//FutureTask<V>用于接收运算结果
System.out.println(integer);
//5050
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
private int num;
public MyCallable(int num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= num; i++) {
sum += i;
}
return sum;
}
}
Runnable和Callable的区别
- Runnable,让线程来执行run方法,没有返回值,如果run方法内部有异常,那么不能抛出异常,因为父类接口方法声明中没有抛出,所以只能try-catch;
- Callable,让线程来执行call()方法,有返回值,而且call方法可以抛出异常。
Thread常见方法
线程休眠
Thread.sleep()是Thread类的一个静态方法,使当前线程休眠,进入阻塞状态(暂停执行),休眠结束后,由阻塞态进入就绪态;如果线程在睡眠状态被中断,将会抛出IterruptedException中断异常。
注意:在哪个线程里面调用sleep()方法就阻塞哪个线程。
package org.westos.demo;
/**
* 线程休眠Thread.sleep,让当前线程休眠
* @author lwj
* @date 2020/6/1 16:49
*/
public class MyTest2 {
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程开始执行了");
Thread.sleep(2000);
//主线程休眠
System.out.println("主线程休眠后的代码");
MyThread3 myThread = new MyThread3("A线程");
MyThread3 myThread1 = new MyThread3("B线程");
myThread.start();
myThread1.start();
}
}
class MyThread3 extends Thread {
public MyThread3() {}
public MyThread3(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1000);
//实现Runnable接口的方式创建线程,在run方法中如果存在异常,那么不能向外抛出,只能在内部try-catch
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "---" + i);
}
}
}
join
在A线程中调用B线程的join()方法,那么A线程必须等待B线程执行完毕后,才可以继续执行。
package org.westos.demo;
/**
* join
* @author lwj
* @date 2020/6/1 18:52
*/
public class MyTest3 {
public static void main(String[] args) throws InterruptedException {
MyThread1 myThread = new MyThread1("刘备");
MyThread1 myThread1 = new MyThread1("关羽");
MyThread1 myThread2 = new MyThread1("张飞");
myThread.start();
myThread.join();
//此时进程中只有main线程和myThread线程,在main线程中调用myThread.join()方法,那么直到myThread线程执行完毕后,main线程才会继续执行
myThread1.start();
myThread1.join();
//此时只有main线程和myThread1线程,当myThread1线程执行完毕后,main线程才会继续执行
myThread2.start();
myThread2.join();
//同理,当myThread2线程执行完毕后,main线程才会继续执行
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
class MyThread1 extends Thread {
public MyThread1() {}
public MyThread1(String name) {
super(name);
}
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
System.out.println(name + "---" + i);
}
}
}
案例2
package org.westos.demo;
/**
* @author lwj
* @date 2020/6/4 15:53
*/
public class JoinTest implements Runnable{
public static int a = 0;
@Override
public void run() {
for (int i = 0; i < 5; i++) {
a += 1;
}
}
public static void main(String[] args) {
Thread thread = new Thread(new JoinTest());
thread.start();
//线程启动,需要准备资源,等待CPU的调度,而且和main线程是处于并发执行的状态,所以存在一种情况,main线程执行完毕了,thread线程才开始执行
System.out.println(a);
//0
}
}
package org.westos.demo;
/**
* @author lwj
* @date 2020/6/4 15:53
*/
public class JoinTest implements Runnable{
public static int a = 0;
@Override
public void run() {
for (int i = 0; i < 5; i++) {
a += 1;
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new JoinTest());
thread.start();
thread.join();
//在main线程中调用thread.join方法,那么main线程必须等待thread线程执行完毕后才可以继续执行
System.out.println(a);
//5
}
}
yield
线程让步
yield()的作用是让步。它能让当前线程由"运行状态"进入到"就绪状态",从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到"运行状态"继续运行!
package org.westos.demo;
/**
* 线程礼让yield
* @author lwj
* @date 2020/6/4 16:07
*/
public class MyTest5 {
public static void main(String[] args) {
MyThread4 thread = new MyThread4("A线程");
MyThread4 thread1 = new MyThread4("B线程");
thread.start();
thread1.start();
}
/*
...
A线程---0
B线程---3
A线程---1
B线程---4
A线程---2
B线程---5
A线程---3
B线程---6
A线程---4
B线程---7
A线程---5
B线程---8
A线程---6
B线程---9
A线程---7
B线程---10
A线程---8
B线程---11
A线程---9
B线程---12
A线程---10
B线程---13
A线程---11
B线程---14
A线程---12
B线程---15
A线程---13
B线程---16
A线程---14
B线程---17
A线程---15
B线程---18
A线程---16
B线程---19
*/
}
class MyThread4 extends Thread {
public MyThread4() {}
public MyThread4(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
Thread.yield();
}
}
}
守护线程
Java用户线程和守护线程
- 用户线程和守护线程都是线程,区别是Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束;守护线程是用来辅助用户线程的,如公司的员工和保安,各司其职,当员工都离开后,保安自然下班了。
- 必须在线程启动之前调用对象的setDaemon(true)方法。
- 还有就是经典坦克游戏,当你要守护的老鹰4掉了,坦克(守护线程)也就没有存在的必要了。
package org.westos.demo;
/**
* 守护线程
* @author lwj
* @date 2020/6/1 19:14
*/
public class MyTest4 {
public static void main(String[] args) {
//更改主线程的name
Thread.currentThread().setName("刘备");
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
MyThread2 myThread = new MyThread2("关羽");
MyThread2 myThread1 = new MyThread2("张飞");
myThread.setDaemon(true);
myThread1.setDaemon(true);
//设置为守护线程
//当主线程刘备消亡后,守护线程也要消亡
myThread.start();
myThread1.start();
System.out.println(Thread.currentThread().getName() + "线程执行完毕");
/*
刘备---0
刘备---1
刘备---2
刘备---3
刘备---4
刘备---5
刘备---6
刘备---7
刘备---8
刘备---9
刘备线程执行完毕
张飞---0
关羽---0
关羽---1
关羽---2
关羽---3
关羽---4
关羽---5
*/
}
}
class MyThread2 extends Thread {
public MyThread2() {}
public MyThread2(String name) {
super(name);
}
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
System.out.println(name + "---" + i);
}
}
}
创建守护线程
- 在线程启动之前调用Thread对象的setDaemon(true),设置该线程为守护线程
- 在守护线程中创建的新线程也是守护线程
线程停止
package org.westos.demo;
/**
* stop
* @author lwj
* @date 2020/6/4 16:29
*/
public class MyTest6 {
public static void main(String[] args) throws InterruptedException {
MyThread5 thread = new MyThread5("A线程");
thread.start();
//让主线程休眠2ms,给thread线程一些执行时间
Thread.sleep(2);
thread.stop();
//中断线程,已过时的方法
}
/*
A线程---0
...
A线程---90左右
*/
}
class MyThread5 extends Thread {
public MyThread5() {}
public MyThread5(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
interrupt()
:清除阻塞,打断线程的阻塞状态,当线程调用wait()、sleep(long mills)方法后,由运行态变为阻塞态。
package org.westos.demo2;
/**
* interrupt
* @author lwj
* @date 2020/6/4 16:48
*/
public class MyTest3 {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.setName("A线程");
myThread.start();
Thread.sleep(2);
//主线程休眠保证myThread已经启动
myThread.interrupt();
//清除myThread线程的阻塞状态
//触发了sleep方法的InterruptedException异常,进入catch块打印异常的堆栈信息
/*
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at org.westos.demo2.MyThread.run(MyTest3.java:26)
A线程---0
A线程---1
A线程---2
A线程---3
A线程---4
A线程---5
A线程---6
A线程---7
A线程---8
A线程---9
*/
}
}
class MyThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//手动让线程由运行态变为阻塞状态
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
线程安全问题
继承Thread类实现
package org.westos.demo3;
/**
* 售票案例
* @author lwj
* @date 2020/6/4 17:16
*/
public class MyTest {
public static void main(String[] args) {
CellThread a = new CellThread("A");
CellThread b = new CellThread("B");
CellThread c = new CellThread("C");
a.start();
b.start();
c.start();
/*
在增加static修饰后,售票不是按照tickets--的顺序输出的,所以是需要在run方法里加同步,每个线程执行完run方法后再轮到下一个线程
*/
}
}
class CellThread extends Thread {
//private int tickets = 100;
private static int tickets = 100;
/*
B窗口出售了第100张票
C窗口出售了第100张票
C窗口出售了第99张票
C窗口出售了第98张票
C窗口出售了第97张票
C窗口出售了第96张票
A窗口出售了第100张票
第一次是由于在main方法中创建了三个线程,每个线程对象都拥有tickets属性为100,即三个窗口各售100张票
所以需要将tickets属性作为三个线程共享的变量,添加static修饰
*/
public CellThread() {}
public CellThread(String name) {
super(name);
}
@Override
public void run() {
while (true) {
if (tickets >= 1) {
//只有在余票大于等于1张时才可以卖票
System.out.println(Thread.currentThread().getName() + "窗口出售了第" + tickets-- + "张票");
} else {
break;
}
}
}
}
实现Runnable接口实现
package org.westos.demo3;
/**
* 售票案例
* @author lwj
* @date 2020/6/4 19:57
*/
public class MyTest2 {
public static void main(String[] args) {
/*Thread a = new Thread(new CellRunnable(), "a");
Thread b = new Thread(new CellRunnable(), "b");
Thread c = new Thread(new CellRunnable(), "c");*/
/*
同样的问题,三个线程,分别执行三个不同的任务Runnable对象,那么分别都拥有tickets=100的属性
*/
CellRunnable cellRunnable = new CellRunnable();
Thread a = new Thread(cellRunnable, "a");
Thread b = new Thread(cellRunnable, "b");
Thread c = new Thread(cellRunnable, "c");
a.start();
b.start();
c.start();
/*
三个线程执行一个Runnable任务之后,那么只有一个对象,所以任何一个线程对tickets属性的修改都会影响其他线程
而当继承Thread的方式时,是因为有三个对象,所以tickets变量必须设置为静态,多个对象共享数据
*/
}
}
class CellRunnable implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets >= 1) {
System.out.println(Thread.currentThread().getName() + "窗口出售了第" + tickets-- + "张票");
} else {
break;
}
}
}
}
线程安全问题
package org.westos.demo3;
/**
* 模拟实际售票场景
* @author lwj
* @date 2020/6/4 20:07
*/
public class MyTest3 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread a = new Thread(myRunnable, "a");
Thread b = new Thread(myRunnable, "b");
Thread c = new Thread(myRunnable, "c");
a.start();
b.start();
c.start();
}
}
class MyRunnable implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets >= 1) {
try {
Thread.sleep(50);
//判断有票之后
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "窗口出售了第" + tickets-- + "张票");
} else {
break;
}
}
}
}
- c窗口出售了第0张票
- 卖出同一张票
- b窗口出售了第1张票
- a窗口出售了第1张票
以上是关于优雅停止Thread(线程)的3种方式的主要内容,如果未能解决你的问题,请参考以下文章
java多线程技术: interrupt() 中断线程, 优雅停止线程及原理