THINKING IN JAVA --- 并发(21.1-21.6)

21.2基本的线程机制

Thread.State

查看Java官方文档Thread类,包含一个子类枚举类State,枚举了线程的所有状态如下:
THINKING IN JAVA --- 并发(21.1-21.6)_第1张图片
THINKING IN JAVA --- 并发(21.1-21.6)_第2张图片
线程状态图
THINKING IN JAVA --- 并发(21.1-21.6)_第3张图片

21.2.1 Runnable

THINKING IN JAVA --- 并发(21.1-21.6)_第4张图片

package concurrency;

/**
 * 打印发射之前的倒计时
 */
public class LiftOff implements Runnable{
    protected int countDown = 10; // Default
    private static int taskCount = 0;
    private final int id = taskCount++;

    public LiftOff(){}
    public LiftOff(int countDown){
        this.countDown = countDown;
    }

    public String status(){
        return "#" + id + "(" + (countDown > 0 ? countDown : "Lift off!") + "), ";
    }

    @Override
    public void run(){
        while (countDown-- > 0){
            System.out.print(status());
            Thread.yield(); // 线程进入就绪状态,等待处理器调度
        }
    }


    public static void main(String[] args) {
    }
}

package concurrency;

public class MainThread {
    public static void main(String[] args) {
        LiftOff launch = new LiftOff();
        launch.run();
    }
}

THINKING IN JAVA --- 并发(21.1-21.6)_第5张图片

21.2.2 Thread

package concurrency;

public class BasicThreads {
    // 程序中同时运行main线程和t线程,打印效果不确定
    // 多线程并发,某个时刻执行哪个线程由线程调度器调度决定
    public static void main(String[] args) {
        Thread t = new Thread(new LiftOff());
        t.start(); // 为线程执行进行必要初始化,然后调用Runnable的run()方法
        System.out.println("Waiting for liftoff...");
    }
}

下面都是可能的输出:
在这里插入图片描述
在这里插入图片描述
下面的代码输出更是多种多样:

package concurrency;

public class MoreBasicThreads {
    public static void main(String[] args) {
        for(int i = 0; i < 5; i++){
            new Thread(new LiftOff()).start();
        }
        System.out.println("Waiting for liftoff...");
    }
}

21.2.3 Executor

THINKING IN JAVA --- 并发(21.1-21.6)_第6张图片

package concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPool {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool(); // ExecutorService接口实现了Executor接口
        for(int i = 0; i < 5; i++){
            exec.execute(new LiftOff()); // Executor接口提供了方法:void execute(Runnable command)
        }
        // Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted.
        exec.shutdown(); 
    }
}

THINKING IN JAVA --- 并发(21.1-21.6)_第7张图片
THINKING IN JAVA --- 并发(21.1-21.6)_第8张图片

package concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingleThreadExecutor {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newSingleThreadExecutor(); // ExecutorService接口实现了Executor接口
        for(int i = 0; i < 5; i++){
            exec.execute(new LiftOff()); // 顺序执行各个任务
        }
        // Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted.
        exec.shutdown();
    }
}

输出:

#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Lift off!), #1(9), #1(8), #1(7), #1(6), #1(5), #1(4), #1(3), #1(2), #1(1), #1(Lift off!), #2(9), #2(8), #2(7), #2(6), #2(5), #2(4), #2(3), #2(2), #2(1), #2(Lift off!), #3(9), #3(8), #3(7), #3(6), #3(5), #3(4), #3(3), #3(2), #3(1), #3(Lift off!), #4(9), #4(8), #4(7), #4(6), #4(5), #4(4), #4(3), #4(2), #4(1), #4(Lift off!),
Process finished with exit code 0

21.2.4 从任务中产生返回值

package concurrency;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * 要使线程执行完任务后有返回值,需要实现Callable接口
 */
class TaskWithResult implements Callable<String>{
    private int id;
    public TaskWithResult(int id){
        this.id = id;
    }
    @Override
    public String call() {
        return "result of TaskWithResult " + id;
    }
}

public class CallableDemo {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        List<Future<String>> results = new ArrayList<>();
        for(int i = 0; i < 10; i++){
            // 执行ExecutorService的submit方法,返回Future类型
            results.add(exec.submit(new TaskWithResult(i)));
        }
        for(Future<String> fs : results){
            try {
            	// get() blocks util completion
                System.out.println(fs.get());
            }catch (Exception e){
                System.out.println(e);
            }finally {
                exec.shutdown();
            }
        }
    }
}

THINKING IN JAVA --- 并发(21.1-21.6)_第9张图片

21.2.5 sleep()

THINKING IN JAVA --- 并发(21.1-21.6)_第10张图片

package concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SleepTask extends LiftOff {
    @Override
    public void run(){
        while (countDown-- > 0){
            System.out.println(status());
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) { // 必须捕获异常,因为异常不能跨线程抛到main线程
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        for(int i = 0; i < 3; i++){
            exec.execute(new SleepTask());
        }
        exec.shutdown();
    }
}

THINKING IN JAVA --- 并发(21.1-21.6)_第11张图片
上面看到的顺序打印行为是依赖底层的线程机制的,依赖操作系统。真想顺序打印,最靠谱的是使用同步。

21.2.6 优先级

下面这段程序想通过设置优先级来让CPU更大概率的调用优先级高的线程,笔者在Mac下跑来作者的程序,显然和作者在Windows XP上跑的效果不同,并没有达到预期效果。

package concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SimplePriorities implements Runnable {
    private int countDown = 5;
    private volatile double d; // 禁止编译器优化
    private int priority;
    public SimplePriorities(int priority){
        this.priority = priority;
    }
    public String toString(){
        return Thread.currentThread() + ": " + countDown;
    }
    @Override
    public void run() {
        Thread.currentThread().setPriority(priority); // 设定优先级
        while (true){
            // 进行开销较大的浮点运算,以便线程调度器介入,进而看出优先级效果
            for(int i = 1; i < 10000000; i++){
                d += (Math.PI + Math.E) / (double)i;
                if(i % 1000 == 0){
                    Thread.yield();
                }
            }
            System.out.println(this);
            if(--countDown == 0){
                return;
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        for(int i = 0; i < 5; i++){
            exec.execute(new SimplePriorities(Thread.MIN_PRIORITY));
        }
        exec.execute(new SimplePriorities(Thread.MAX_PRIORITY));
        exec.shutdown();
    }
}

THINKING IN JAVA --- 并发(21.1-21.6)_第12张图片

21.2.8 后台线程

后台线程(daemon),是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程不属于程序中不可或缺的部分。当所有非后台线程结束时,程序就终止了,同时会杀死进程中的所有后台线程。反过来说,只要有任何非后台线程还在运行,程序就不会终止。下面执行main的就是非后台线程。

package concurrency;

public class SimpleDaemons implements Runnable {
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100);
                System.out.println(Thread.currentThread() + ": " + this);
            } catch (InterruptedException e) {
                System.out.println("sleep() interrupted");
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        for(int i = 0; i < 5; i++){
            Thread daemon = new Thread(new SimpleDaemons());
            daemon.setDaemon(true); // 在线程调用start()启动之前必须调用setDaemon(true)才能设置为daemon
            daemon.start();
        }
        System.out.println("All daemons started");
        try {
            Thread.sleep( 500); // 尝试修改这个时间,可以看到下面的效果,印证了本节开头的那段话
        }catch (InterruptedException e){
            System.out.println("sleep() interrupted");
            e.printStackTrace();
        }
    }
}

(当唯一的非后台线程把main执行完之后,后台线程会被全部杀死)
Thread.sleep( 0);
THINKING IN JAVA --- 并发(21.1-21.6)_第13张图片
Thread.sleep( 10);
THINKING IN JAVA --- 并发(21.1-21.6)_第14张图片
Thread.sleep( 100);
THINKING IN JAVA --- 并发(21.1-21.6)_第15张图片
Thread.sleep( 500);
THINKING IN JAVA --- 并发(21.1-21.6)_第16张图片

可以通过isDaemon()确定线程是否是后台线程。如果是后台线程,它创建的任何线程都会被设置为后台线程。

后台线程执行完,JVM就会立即杀死所有后台线程,后台线程的finally不会执行。如下:

package concurrency;

public class ADaemon implements Runnable {

    @Override
    public void run() {
        try {
            System.out.println("Starting ADaemon");
            Thread.sleep(100);
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            System.out.println("This should always run ?");
        }
    }

    public static void main(String[] args) {
        Thread t = new Thread(new ADaemon());
        t.setDaemon(true);
        t.start();
        // 非后台线程执行完,JVM就会立即杀死所有后台线程,后台线程的finally不会执行
    }
}

21.2.9 继承Thread

package concurrency;

public class SimpleThread extends Thread {
    private int countDown = 5;
    private static int threadCount = 0;
    public SimpleThread(){
        super(Integer.toString(++threadCount));
        start();
    }

    @Override
    public String toString() {
        return "#" + getName() + "(" + countDown + ")";
    }

    public void run(){
        while (true){
            System.out.println(this);
            if(--countDown == 0){
                return;
            }
        }
    }

    public static void main(String[] args) {
        for(int i = 0; i < 5; i++){
            new SimpleThread();
        }
    }
}

在非常简单的情况下,可以直接继承Thread制作线程类。但是,继承了Thread之后就无法继承别的类了;另外直接在构造器中调用start()可能会带来问题,因为另一个任务可能会在构造器结束之前开始执行这意味着该任务能够访问处于不稳定状态的对象。

21.2.11 join()

THINKING IN JAVA --- 并发(21.1-21.6)_第17张图片
THINKING IN JAVA --- 并发(21.1-21.6)_第18张图片
线程A执行了线程B.join(),A线程被挂起,直到线程B死亡(B.isAlive() == false)。join()方法中可以传入最长等待时间,在这段时间中如果线程B不死,join()方法也会返回。

package concurrency;

class Sleeper extends Thread{
    private int duration;
    public Sleeper(String name, int sleepTime){
        super(name);
        duration = sleepTime;
        start();
    }
    public void run(){
        try {
            sleep(duration);
        }catch (InterruptedException e){
            System.out.println(getName() + " was interrupted. " + "isInterrupted(): " + isInterrupted());
            return; // 这里直接return,线程执行结束,死亡
        }
        System.out.println(getName() + " has awakened.");
    }
}

class Joiner extends Thread{
    private Sleeper sleeper;
    public Joiner(String name, Sleeper sleeper){
        super(name);
        this.sleeper = sleeper;
        start();
    }
    public void run(){
        try {
            sleeper.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName() + " join has completed.");
    }
}

public class Joining {
    public static void main(String[] args) {
        Sleeper sleepy = new Sleeper("Sleepy", 1000);
        Sleeper grumpy = new Sleeper("Grumy", 1000);

        Joiner dopey = new Joiner("dopey", sleepy);
        Joiner doc = new Joiner("Doc", grumpy);
        /**
         * 线程A(此处是主线程)在线程B上调用interrupt(),会给线程B设定一个标志,表明B线程
         * 已经被中断。然而,异常捕获时将清理这个标志,所以上面程序中的isInterrupted()一直是false
         */
        grumpy.interrupt();
    }
}

THINKING IN JAVA --- 并发(21.1-21.6)_第19张图片

21.2.14 捕获异常

一个线程抛出的异常无法被别的线程捕获。

package concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExceptionThread implements Runnable {
    @Override
    public void run() {
        throw new RuntimeException(); // 该线程会抛出异常
    }

    public static void main(String[] args) {
        try{
            ExecutorService exec = Executors.newCachedThreadPool();
            exec.execute(new ExceptionThread());
            exec.shutdown();
        }catch (RuntimeException e){
            // 下面语句不会被执行。
            // 虽说另一个线程一定会抛异常,但一个线程抛的异常无法被另一个线程捕获。
            System.out.println("Exception has been handled!");
        }
    }
}

笔者试了直接在线程的run方法里进行try-catch是可以的。Thread.UncaughtExceptionHandler捕获线程异常,如下。

package concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

class ExceptionThread2 implements Runnable{
    @Override
    public void run() {
        Thread t = Thread.currentThread();
        System.out.println("run() by " + t);
        System.out.println("ExceptionHandler: " + t.getUncaughtExceptionHandler());
        throw new RuntimeException();
    }
}

class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("caught: " + e);
    }
}

class HandlerThreadFactory implements ThreadFactory{
    @Override
    public Thread newThread(Runnable r) {
        System.out.println(this + " creating new Thread");
        Thread t = new Thread(r);
        System.out.println("created: " + t);
        t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        System.out.println("ExceptionHandler: " + t.getUncaughtExceptionHandler());
        return t;
    }
}

public class CaptureUncaughtException {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool( new HandlerThreadFactory());
        exec.execute(new ExceptionThread2());
        exec.shutdown();
    }
}

THINKING IN JAVA --- 并发(21.1-21.6)_第20张图片
上面打印输出可以看出,创建了两个线程,笔者思考认为这和Executor框架底层有关,线程池觉得应该要再创建一个线程了,于是乎又创建了一个Thread-1。

在此,本节的第一个例子可以如下修改:

Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new ExceptionThread());
exec.shutdown();

在这里插入图片描述
虽然第一句代码传入了new MyUncaughtExceptionHandler(),但是由于ExceptionThread没有通过setUncaughtExceptionHandler()方法绑定自定义的UncaughtExceptionHandler,所以就使用getDefaultUncaughtExceptionHandler()方法使用默认的异常处理方式。

21.3 共享受限资源

21.3.1 不正确的访问临界区资源

这里是一个例子,多个消费偶数的线程共享同一个偶数生成器,这里的偶数生成器中使用++运算符,无法保证原子性,导致多线程访问临界区资源时得不到预期结果,很可能导致偶数生成器生成奇数,从而终止掉偶数生成器。
THINKING IN JAVA --- 并发(21.1-21.6)_第21张图片

package concurrency.shareResources;

/**
 * 整数生成器
 */
public abstract class IntGenerator {
    private volatile boolean canceled = false;
    public abstract int next();
    public void cancel(){
        canceled = true;
    }
    public boolean isCanceled(){
        return canceled;
    }
}

package concurrency.shareResources;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 消费偶数的线程
 */
public class EvenChecker implements Runnable {
    private IntGenerator generator;
    private final int id;
    public EvenChecker(IntGenerator generator, int id){
        this.generator = generator;
        this.id = id;
    }
    @Override
    public void run(){
        while (!generator.isCanceled()){
            int val = generator.next();
            if(val % 2 != 0){
                System.out.println(val + " not even");
                generator.cancel();
            }
        }
    }
    public static void test(IntGenerator generator, int count){
        System.out.println("Press Control-C to exit.");
        ExecutorService exec = Executors.newCachedThreadPool();
        for(int i = 0; i < count; i++){
            exec.execute(new EvenChecker(generator, i));
        }
        exec.shutdown();
    }
    public static void test(IntGenerator generator){
        test(generator, 10);
    }
}

package concurrency.shareResources;

/**
 * 偶数生成器(共享的资源,临界区资源)
 */
public class EvenGenerator extends IntGenerator {
    private int currentEvenValue = 0;
    @Override
    public int next(){
        ++currentEvenValue; // 这里++不是原子操作,会因此而终止共享的生成器
        Thread.yield(); // 增强示例效果
        ++currentEvenValue;
        return currentEvenValue;
    }

    public static void main(String[] args) {
        EvenChecker.test(new EvenGenerator());
    }
}

21.3.2 synchronized和Lock

针对21.3.1中多线程共享临界区资源的错误做法,可以通过加锁进行串行化处理。

synchronized

只需要对偶数生成器的next()方法加synchronized关键字即可。

Lock对象

偶数生成器的next()方法中使用Lock显示加锁:

public int next(){
        lock.lock();
        try{
            ++currentEvenValue; // 这里++不是原子操作,会因此而终止共享的生成器
            Thread.yield(); // 增强示例效果
            ++currentEvenValue;
            return currentEvenValue; 
        }finally {
            lock.unlock(); // 确保return返回后再释放锁
        }
    }

如果想实现尝试获取锁最终失败或者尝试获取锁一段时间没得到而放弃,需要使用Lock显示加锁(而synchronized做不到这两种情形)。如下代码示例:

package concurrency.shareResources;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class AttemptLocking {
    private ReentrantLock lock = new ReentrantLock();
    public void untimed(){
        boolean captured = lock.tryLock(); // 尝试获得锁
        try{
            System.out.println("tryLock(): " + captured);
        }finally {
            if(captured){
                lock.unlock();
            }
        }
    }
    public void timed(){
        boolean captured = false;
        try {
            // 尝试获得锁尝试2秒时间
            captured = lock.tryLock(2, TimeUnit.SECONDS);
        }catch (Exception e){
            e.printStackTrace();
        }
        try{
            System.out.println("tryLock(2, TimeUnit.SECONDS): " + captured);
        }finally {
            if(captured){
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        final AttemptLocking al = new AttemptLocking();
        al.untimed();
        al.timed();
        new Thread(){
            public void run(){
                al.lock.lock();
                System.out.println("acquired");
            }
        }.start();
        al.untimed();
        al.timed();
    }
}

21.3.3 原子性

注意:JVM把long、double这64位拆分成两个32位操作,直接对long、double变量操作不具有原子性。

21.3.4 原子类

下面这个代码由于使用了value++没有保证原子性,多线程对value=0进行了加10000次,本应能得到10000,但事实上根本得不到。

package concurrency.shareResources;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AtomicIntegerTest implements Runnable{
    private int value = 0;
    public int getIncrementValue(){
        return value++;
    }

    @Override
    public void run(){
        getIncrementValue();
        if(value == 10000){
            System.out.println(value);
        }
    }

    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        AtomicIntegerTest ait = new AtomicIntegerTest();
        for(int i = 0; i < 10000; i++){
            exec.execute(ait);
        }
        exec.shutdown();
    }
}

针对上述的代码,可以对方法getIncrementValue()使用synchronized关键字/显示Lock锁或者原子类保证原子性。如下代码示例:

package concurrency.shareResources;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest implements Runnable{
    private AtomicInteger value = new AtomicInteger(0);
    public int getIncrementValue(){
        return value.getAndIncrement();
    }

    @Override
    public void run(){
        getIncrementValue();
        if(value.get() == 10000){
            System.out.println(value.get());
        }
    }

    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        AtomicIntegerTest ait = new AtomicIntegerTest();
        for(int i = 0; i < 10000; i++){
            exec.execute(ait);
        }
        exec.shutdown();
    }
}

21.3.7 ThreadLocal

每个线程都跟着本线程初始值不断加1。如下代码:

package concurrency.shareResources;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class Accessor implements Runnable{
    private final int id;
    public Accessor(int id){
        this.id = id;
    }
    public void run(){
        int count = 3;
        while (count-- > 0){
            ThreadLocalVariableHolder.increment();
            System.out.println(this);
        }
    }
    public String toString(){
        return "#" + id + ": " + ThreadLocalVariableHolder.get();
    }
}

public class ThreadLocalVariableHolder {
    private static ThreadLocal<Integer> value = new ThreadLocal<Integer>(){
        private Random rand = new Random(100);
        protected Integer initialValue(){
            return rand.nextInt(1000);
        }
    };
    public static void increment(){
        value.set(value.get()+1);
    }
    public static int get(){
        return value.get();
    }

    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        for(int i = 0; i < 5; i++){
            exec.execute(new Accessor(i));
        }
        exec.shutdown();
    }
}

THINKING IN JAVA --- 并发(21.1-21.6)_第22张图片

21.4 终结任务

21.4.1 统计经过某公园各个门口的人数及总人数

package concurrency.shareResources;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 统计经过某公园各个门口的人数及总人数
 */

class Count{
    private int count = 0;
    public synchronized int getCount(){
        return count;
    }
    public synchronized int increment(){
        return ++count;
    }
}

class Entrance implements Runnable{
    private static Count count = new Count(); // 用Count维护公园总人数
    private static List<Entrance> entrances = new ArrayList<>(); // 维护所有公园大门的集合
    private static volatile boolean canceled = false; // 所有Entrance任务取消标志

    private int number; // Entrance每个实例对象维护的人数
    private final int id; // 每个Entrance的id

    public Entrance(int id){
        this.id = id;
    }
    @Override
    public void run(){
        while (!canceled){
            ++number;
            count.increment();
            System.out.println(this + " Total: " + count.getCount());
            try {
                Thread.sleep(100); // 为了演示效果
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    @Override
    public String toString(){
        return "Entrance " + id + ": " + number;
    }
    public static void cancel(){
        canceled = true;
    }
    public static int getTotalCount(){
        return count.getCount();
    }

}

public class Garden {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        for(int i = 0; i < 5; i++){
            exec.execute(new Entrance(i));
        }
        try {
            Thread.sleep(300); // 为了看到适当多的结果打印
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Entrance.cancel();
        exec.shutdown();
        try {
            if(!exec.awaitTermination(2, TimeUnit.MILLISECONDS)){
                System.out.println("Some tasks have not terminated.");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Total: " + Entrance.getTotalCount());
    }
}

THINKING IN JAVA --- 并发(21.1-21.6)_第23张图片

21.4.3 中断

关于InterruptedException

每个线程都有一个与之相关联的 Boolean 属性,用于表示线程的中断状态(interrupted status)。中断状态初始时为 false;当另一个线程通过调用 Thread.interrupt() 中断一个线程时,会出现以下两种情况之一。如果那个线程在执行一个低级可中断阻塞方法,例如 Thread.sleep()、 Thread.join() 或 Object.wait(),那么它将取消阻塞并抛出 InterruptedException。否则, interrupt() 只是设置线程的中断状态。 在被中断线程中运行的代码以后可以轮询中断状态,看看它是否被请求停止正在做的事情。中断状态可以通过 Thread.isInterrupted() 来读取,并且可以通过一个名为 Thread.interrupted() 的操作读取和清除(返回当前是否中断的状态,并把中断状态清除,设置为false)。

中断是一种协作机制。当一个线程中断另一个线程时,被中断的线程不一定要立即停止正在做的事情。相反,中断是礼貌地请求另一个线程在它愿意并且方便的时候停止它正在做的事情。有些方法,例如 Thread.sleep(),很认真地对待这样的请求,但每个方法不是一定要对中断作出响应。

Thread的三个方法:
在这里插入图片描述
在这里插入图片描述
THINKING IN JAVA --- 并发(21.1-21.6)_第24张图片
THINKING IN JAVA --- 并发(21.1-21.6)_第25张图片

package test;

public class Solution {
    public static void main(String[] args) {
        Thread.currentThread().interrupt();
        System.out.println(Thread.currentThread().isInterrupted());
        System.out.println(Thread.interrupted()); // 返回true,但是重置了中断状态为false
        System.out.println(Thread.currentThread().isInterrupted()); // 本应该是true,但是被重置了中断状态,返回false
        System.out.println(Thread.interrupted());
        System.out.println(Thread.currentThread().isInterrupted());
    }
}


THINKING IN JAVA --- 并发(21.1-21.6)_第26张图片

下面代码就展示了interrupt()方法的使用,在调用Future的cancel()方法的时候,底层实际就调用了Thread的interrupt()方法。下面代码中,只有SleepBlocked任务接受中断,抛出InterruptedException,而IOBlocked和SynchronizedBlocked没接受中断,一直执行,无法结束。针对I/O阻塞无法中断的情况,《Java编程思想》给出了结束任务的方法——关闭对应的底层资源。

package concurrency.shareResources;

import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

class SleepBlocked implements Runnable{
    @Override
    public void run(){
        try {
            System.out.println("Sleep...");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            System.out.println("Interrupted from sleep !!!");
        }
    }
}

class IOBlocked implements Runnable{
    private InputStream in;
    public IOBlocked(InputStream is){
        in = is;
    }
    @Override
    public void run(){
        try {
            System.out.println("Waiting for read()...");
            in.read();
        } catch (Exception e) {
            if(Thread.currentThread().isInterrupted()){
                System.out.println("Interrupted from blocked I/O !!!");
            }else{
                throw new RuntimeException(e);
            }
        }
    }
}

class SynchronizedBlocked implements Runnable{
    public synchronized void f(){
        while (!Thread.currentThread().isInterrupted()){

        }
    }
    public SynchronizedBlocked(){
        new Thread(){
            @Override
            public void run(){
                f();
            }
        }.start();
    }
    @Override
    public void run(){
        System.out.println("Trying to call f()...");
        f();
        System.out.println("Exiting SynchronizedBlocked.run() !!!");
    }
}

public class Interrupting {
    private static ExecutorService exec = Executors.newCachedThreadPool();
    static void test(Runnable r) throws InterruptedException {
        Future<?> f = exec.submit(r);
        System.out.println("Interrupting " + r.getClass().getName());
        Thread.sleep(300); // 先睡会儿,再调用cancel(),防止线程刚来到run()就被取消执行了,看不到效果
        f.cancel(true); // 底层调用的依然是Thread.interrupt()方法
        System.out.println("Interrupt sent to " + r.getClass().getName());
    }

    public static void main(String[] args) throws Exception {
        test(new SleepBlocked());
        test(new IOBlocked(System.in));
        test(new SynchronizedBlocked());
        System.out.println("Aborting with System.exit(0)");
        System.exit(0); // 可尝试注释掉这行代码,会发现后两个线程无法终止
    }
}

THINKING IN JAVA --- 并发(21.1-21.6)_第27张图片

synchronized可重入

package concurrency.shareResources;

public class MultiLock {

    public synchronized void f1(int count) {
        if (count-- >0) {
            System.out.println("f1() calling f2() with count = " + count);
            f2(count);
        }
    }

    public synchronized void f2(int count) {
        if (count-- >0) {
            System.out.println("f2() calling f1() with count = " + count);
            f1(count);
        }
    }

    public static void main(String[] args) throws Exception{
        final MultiLock multiLock = new MultiLock();
        new Thread(){
            public void run() {
                multiLock.f1(5);
            };
        }.start();
    }
}

THINKING IN JAVA --- 并发(21.1-21.6)_第28张图片

RentrantLock上阻塞的任务可被中断

THINKING IN JAVA --- 并发(21.1-21.6)_第29张图片

package concurrency.shareResources;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class BlockedMutex{
    private Lock lock = new ReentrantLock();
    public BlockedMutex(){
        lock.lock();
    }
    public void f(){
        try {
            lock.lockInterruptibly();
            System.out.println("lock acquired in f()");
        }catch (InterruptedException e) {
            System.out.println("Interrupted from lock acquisition in f()");
        }
    }
}

class Blocked2 implements Runnable{
    BlockedMutex blockedMutex = new BlockedMutex();
    @Override
    public void run() {
        System.out.println("Waiting for f() in BlockedMutex");
        blockedMutex.f();
        System.out.println("Broken out of blockedMutex.f()");
    }
}

public class Interrupting2 {
    public static void main(String[] args) {
        Thread t = new Thread(new Blocked2());
        t.start();
        t.interrupt();
    }
}

THINKING IN JAVA --- 并发(21.1-21.6)_第30张图片

21.4.4 检查中断

package concurrency.shareResources;

import java.util.concurrent.TimeUnit;

class NeedsCleanup{
    private final int id;
    public NeedsCleanup(int id){
        this.id = id;
        System.out.println("NeedsCleanup " + id);
    }
    public void cleanup(){
        System.out.println("Cleaning up " + id);
    }
}

class Blocked3 implements Runnable{
    private volatile double d = 0.0;
    @Override
    public void run(){
        try {
            while (!Thread.interrupted()){
                NeedsCleanup n1 = new NeedsCleanup(1);
                try {
                    System.out.println("Sleeping");
                    // 这里时间设置的长,增大概率,使得执行到这里中断,然后跳到finally,然后跳到异常处理
                    TimeUnit.SECONDS.sleep(1); 
                    NeedsCleanup n2 = new NeedsCleanup(2);
                    try {
                        System.out.println("Calculating");
                        for(int i = 1; i < 2500000; i++){
                            d = d + (Math.E + Math.PI) / d;
                        }
                        System.out.println("Finished time-consuming operation");
                    }finally {
                        n2.cleanup();
                    }
                }finally {
                    n1.cleanup();
                }
            }
            System.out.println("Exiting via while() test");
        }catch (InterruptedException e){
            System.out.println("Exiting via InterruptedException");
        }
    }
}

public class InterruptingIdiom {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Blocked3());
        t.start();
        TimeUnit.SECONDS.sleep(3);
        t.interrupt();
    }
}

THINKING IN JAVA --- 并发(21.1-21.6)_第31张图片

21.5 线程之间的协作

THINKING IN JAVA --- 并发(21.1-21.6)_第32张图片
wait(),线程被挂起,对象上的锁被释放。而sleep()、yield()不会释放锁。

21.5.1 wait()、notifyAll()

package concurrency.shareResources;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * WaxOMatic有两个过程,一个是将蜡涂到Car上,一个是抛光它。
 * 一个抛光任务在对应的涂蜡任务完成之前,不能执行。
 * 在执行新的涂蜡任务之前,必须先完成抛光任务。
 */

class Car{
    private boolean waxOn = false; // 涂蜡进行中,尚未完成
    // 涂蜡
    public synchronized void waxed(){
        waxOn = true; // 涂蜡进行
        notifyAll(); // 涂蜡完成,召唤抛光可以开始
    }
    // 抛光
    public synchronized void buffed(){
        waxOn = false; // 涂蜡完成后通知抛光可执行了,那么抛光就把waxOn设置为false,然后召唤新的涂蜡任务开始
        notifyAll();
    }
    public synchronized void waitForWaxing() throws InterruptedException {
        while (waxOn == false){ // 涂蜡尚未完成,就等待涂蜡完成。
            wait();
        }
    }
    public synchronized void waitForBuffing() throws InterruptedException {
        while (waxOn == true){ // 抛光尚未完成,等待抛光执行完。抛光做的就是把waxOn设置为false,然后召唤新的涂蜡任务开始
            wait();
        }
    }
}

class WaxOn implements Runnable{
    private Car car;
    public WaxOn(Car car){
        this.car = car;
    }
    public void run(){
        try {
            while (!Thread.interrupted()){
                System.out.println("Wax On!"); // 涂蜡进行中
                TimeUnit.MILLISECONDS.sleep(200);
                car.waxed(); // 完成涂蜡
                car.waitForBuffing(); // 等待抛光执行完
            }
        }catch (InterruptedException e){
            System.out.println("Exiting via interrupt");
        }
        System.out.println("Ending Wax On task");
    }
}

class WaxOff implements Runnable{
    private Car car;
    public WaxOff(Car car){
        this.car = car;
    }
    public void run(){
        try{
            while (!Thread.interrupted()){
                car.waitForWaxing(); // 等待涂蜡完成
                System.out.println("Wax Off!"); // 涂蜡完成了
                TimeUnit.MILLISECONDS.sleep(200);
                car.buffed(); // 抛光
            }
        }catch (InterruptedException e){
            System.out.println("Exiting via interrupt");
        }
        System.out.println("Ending Wax Off task");
    }
}

public class WaxOMatic {
    public static void main(String[] args) throws InterruptedException {
        Car car = new Car();
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new WaxOff(car));
        exec.execute(new WaxOn(car));
        TimeUnit.SECONDS.sleep(3);
        exec.shutdownNow();
    }
}

THINKING IN JAVA --- 并发(21.1-21.6)_第33张图片

21.5.3 生产者和消费者

package concurrency.shareResources;

import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 这里摘自原文啦!
 * 请考虑这样一个饭店,它有一个厨师和一个服务员。这个服务员必须等待厨师准备好膳食。
 * 当厨师准备好时,他会通知服务员,之后服务员上菜,然后返回继续等待。这是一个任务
 * 协作当示例:厨师代表生产者,而服务员代笔消费者。两个任务必须在膳食被生产和消费
 * 时进行握手,而系统必须以有序的方式关闭。
 */

class Meal{
    private final int orderNum;
    public Meal(int orderNum){
        this.orderNum = orderNum;
    }
    @Override
    public String toString(){
        return "Meal " + orderNum;
    }
}

class WaitPerson implements Runnable{
    private Restaurant restaurant;
    public WaitPerson(Restaurant r){
        this.restaurant = r;
    }
    @Override
    public void run(){
        try{
            while (!Thread.interrupted()){
                synchronized (this){
                    while (restaurant.meal == null){
                        wait();
                    }
                }
                System.out.println("Waitperson got  " + restaurant.meal);
                synchronized (restaurant.chef){
                    restaurant.meal = null;
                    restaurant.chef.notifyAll();
                }
            }
        } catch (InterruptedException e){
            System.out.println("WaitPerson interrupted");
        }
    }
}

class Chef implements Runnable{
    private Restaurant restaurant;
    private int count = 0;
    public Chef(Restaurant r){
        restaurant = r;
    }
    @Override
    public void run(){
        try {
            while (!Thread.interrupted()){
                synchronized (this){
                    while (restaurant.meal != null){
                        wait();
                    }
                }
                if(++count == 10){
                    System.out.println("Out of food, closing");
                    restaurant.exec.shutdownNow();
                }
                System.out.print("Order up! ");
                synchronized (restaurant.waitPerson){
                    restaurant.meal = new Meal(count);
                    restaurant.waitPerson.notifyAll();
                }
                TimeUnit.MILLISECONDS.sleep(100);
            }
        }catch (InterruptedException e){
            System.out.println("Chef interrupted");
        }
    }
}

public class Restaurant {
    Meal meal;
    ExecutorService exec = Executors.newCachedThreadPool();
    WaitPerson waitPerson = new WaitPerson(this);
    Chef chef = new Chef(this);
    public Restaurant(){
        exec.execute(chef);
        exec.execute(waitPerson);
    }
    public static void main(String[] args) {
        new Restaurant();
    }
}

THINKING IN JAVA --- 并发(21.1-21.6)_第34张图片

21.5.4 生产者-消费者队列

THINKING IN JAVA --- 并发(21.1-21.6)_第35张图片

package concurrency.shareResources;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * BlockingQueue
 * 有一台机器具有三个任务:一个制作吐司、一个给吐司抹黄油,另一个在抹黄油的吐司上涂果酱
 */

class Toast{
    public enum Status{
        DRY, BUTTERED, JAMMED
    }
    private Status status = Status.DRY;
    private final int id;
    public Toast(int id){
        this.id = id;
    }
    public void butter(){
        status = Status.BUTTERED;
    }
    public void jam(){
        status = Status.JAMMED;
    }

    public Status getStatus(){
        return status;
    }
    public int getId(){
        return id;
    }
    public String toString(){
        return "Toast " + id + ":" + status;
    }
}

class ToastQueue extends LinkedBlockingQueue<Toast> {}

class Toaster implements Runnable{
    private ToastQueue toastQueue;
    private int count = 0;
    private Random rand = new Random(47);
    public Toaster(ToastQueue toastQueue){
        this.toastQueue = toastQueue;
    }
    public void run(){
        try{
            while (!Thread.interrupted()){
                TimeUnit.MILLISECONDS.sleep(100 + rand.nextInt(500));
                Toast t = new Toast(count++);
                System.out.println(t);
                toastQueue.put(t);
            }
        }catch (InterruptedException e){
            System.out.println("Toast interrupted");
        }
        System.out.println("Toaster off");
    }
}
// Apply butter to toast:
class Butterer implements Runnable{
    private ToastQueue dryQueue, butteredQueue;
    public Butterer(ToastQueue dryQueue, ToastQueue butteredQueue){
        this.dryQueue = dryQueue;
        this.butteredQueue = butteredQueue;
    }
    public void run(){
        try{
            while (!Thread.interrupted()){
                Toast t = dryQueue.take();
                t.butter();
                System.out.println(t);
                butteredQueue.put(t);
            }
        }catch (InterruptedException e){
            System.out.println("Butterer interrupted");
        }
        System.out.println("Butterer off");
    }
}
// Apply jam to buttered toast:
class Jammer implements Runnable{
    private ToastQueue butteredQueue, finishedQueue;
    public Jammer(ToastQueue butteredQueue, ToastQueue finishedQueue){
        this.butteredQueue = butteredQueue;
        this.finishedQueue = finishedQueue;
    }
    public void run(){
        try{
            while (!Thread.interrupted()){
                Toast t = butteredQueue.take();
                t.jam();
                System.out.println(t);
                finishedQueue.put(t);
            }
        }catch (InterruptedException e){
            System.out.println("Jammer interrupted");
        }
        System.out.println("Jammer off");
    }
}
// Consume the toast:
class Eater implements Runnable{
    private ToastQueue finishedQueue;
    private int counter = 0;
    public Eater(ToastQueue finishedQueue){
        this.finishedQueue = finishedQueue;
    }
    public void run(){
        try{
            while (!Thread.interrupted()){
                Toast t = finishedQueue.take();
                if(t.getId() != counter++
                        || t.getStatus() != Toast.Status.JAMMED){
                    System.out.println(">>>> Error: " + t);
                    System.exit(1);
                }else{
                    System.out.println("Chomp! " + t);
                }
            }
        }catch (InterruptedException e){
            System.out.println("Eater interrupted");
        }
        System.out.println("Eater off");
    }
}

public class ToastOMatic {
    public static void main(String[] args) throws InterruptedException {
        ToastQueue dryQueue = new ToastQueue();
        ToastQueue butteredQueue = new ToastQueue();
        ToastQueue finishedQueue = new ToastQueue();
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new Toaster(dryQueue));
        exec.execute(new Butterer(dryQueue, butteredQueue));
        exec.execute(new Jammer(butteredQueue, finishedQueue));
        exec.execute(new Eater(finishedQueue));
        TimeUnit.SECONDS.sleep(1);
        exec.shutdownNow();
    }
}

THINKING IN JAVA --- 并发(21.1-21.6)_第36张图片

21.6 死锁—哲学家就餐问题

package concurrency.shareResources;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 死锁---哲学家就餐问题(5个哲学家+5根筷子)
 * ponder:沉思
 */

class Chopstick{
    private boolean taken = false;
    public synchronized void take() throws InterruptedException{
        while (taken){
            wait();
        }
        taken = true;
    }
    public synchronized void drop(){
        taken = false;
        notifyAll();
    }
}

class Philosopher implements Runnable{
    private Chopstick left, right;
    private final int id;
    private final int ponderFactor;
    private Random rand = new Random(47);
    private void pause() throws InterruptedException{
        if(ponderFactor == 0){
            return;
        }
        TimeUnit.MILLISECONDS.sleep(rand.nextInt(ponderFactor*250));
    }
    public Philosopher(Chopstick left, Chopstick right, int id, int ponderFactor){
        this.left = left;
        this.right = right;
        this.id = id;
        this.ponderFactor = ponderFactor;
    }
    public void run(){
        try{
            while (!Thread.interrupted()){
                System.out.println(this + " " + "thinking");
                pause();
                System.out.println(this + " " + "grabbing right");
                right.take();
                System.out.println(this + " " + "grabbing left");
                left.take();
                System.out.println(this + " " + "eating");
                pause();
                right.drop();
                left.drop();
            }
        }catch (InterruptedException e){
            System.out.println(this + " " + "exiting via interrupt");
        }
    }
    public String toString(){
        return "Philosophy " + id;
    }
}

public class DeadlockingDiningPhilosophers {
    public static void main(String[] args) throws Exception{
        int ponderFactor = 5;
        int size = ponderFactor;
        ExecutorService exec = Executors.newCachedThreadPool();
        Chopstick[] chopsticks = new Chopstick[size];
        for(int i = 0; i < size; i++){
            chopsticks[i] = new Chopstick();
        }
        for(int i = 0; i < size; i++){
            exec.execute(new Philosopher(chopsticks[i], chopsticks[(i+1)%size], i, ponderFactor));
        }
        /**
         * 解决死锁方法之一就很简单了,让其中一个人先拿左边筷子再拿右边筷子,其他人均与此相反。把上面的for循环略微改变如下:
         */
//        for(int i = 0; i < size-1; i++){
//            exec.execute(new Philosopher(chopsticks[i], chopsticks[(i+1)], i, ponderFactor));
//        }
//        exec.execute(new Philosopher(chopsticks[0], chopsticks[size-1], size-1, ponderFactor));

        TimeUnit.SECONDS.sleep(2);
        exec.shutdownNow();
    }
}

你可能感兴趣的:(Java并发)