优雅停止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() 中断线程, 优雅停止线程及原理

腾讯面试:如何优雅停止一个正在运行的线程?我一脸蒙蔽。。。

Java 基础知识点 笔记总结

Android如何停止线程的方式

27多线程(多线程的三种实现方式Thread线程类的常见方法线程安全问题)

27多线程(多线程的三种实现方式Thread线程类的常见方法线程安全问题)