Java编程思想 第21章并发 学习笔记

基本线程机制

  • Java的线程机制是抢占式的,这表示调度机制会周期性地中断线程,将上下文切换到另一个线程,从而为每一个线程都提供时间片,使得每个线程都会分配到数量合理的时间去驱动它的任务。
  • 线程使你能够创建更加松散耦合的设计。无需显式地关注通常可以由线程来处理的任务。
  • 通过使用多线程机制,这些独立任务 (也被称为子任务)中的每一个都将由执行线程来驱动。
  • 定义任务:只需实现Runnable接口并编写run()方法,使得该任务可以执行你的命令。但它不会产生任何内在的线程能力,要实现线程行为,你必须显式地将一个任务附着在线程上。
    class RunnableImpl implements Runnable {
          
          
      public RunnableImpl() {
          
          
        System.out.println(Thread.currentThread().getName()+" constructed!");  // 任务在main线程被构造
      }
    
      @Override
      public void run() {
          
          
        for (int i = 0; i < 3; i++) {
          
          
          System.out.println(Thread.currentThread().getName() + " " + i);  // 在新的执行线程上执行该子任务
          Thread.yield();
        }
      }
    }
    
  • Thread类:将Runnable对象转变为工作任务的传统方式是把它提交给一个Thread构造器。实际上,Thread内部有一个Runnable对象,但如果构造器中传入Runnable对象之后,会替换成该Runnable对象。构造器中还可以加入String类型的参数,用来设置该线程的名称。
    public class test{
          
          
      public static void main(String[] args) {
          
          
        for (int i = 0; i < 5; i++) {
          
          
          new Thread(new RunnableImpl()).start();
        }
      }
    }
    
    调用Thread对象的start()方法为该线程执行必须的初始化操作,然后调用Runnable的run方法,以便在这个新线程中启动该任务。
    每个Thread都注册了它自己,因此确实有一个对它的引用,而且在它的任务退出其run()并死亡之前,垃圾回收器无法清除它。因此,一个线程会创建一个单独的执行线程,在对start()的调用完成之后,它仍旧会继续存在。
  • 使用Executor:Executor(执行器)将会为你管理Thread对象,从而简化了并发编程。
    ExecutorService是具有服务生命周期的Executor,知道如何构建恰当的上下文来执行Runnable对象。
    shutdown()方法的调用可以防止新任务被提交到这个Executor,当前线程将会继续运行在shutdown()方法调用之前提交的所有任务。
    ExecutorService exec = Executors.newCachedThreadPool();  // 获取线程池
    for (int i = 0; i < 5; i++) {
          
          
        exec.execute(new RunnableTask());  // 将Runnable任务放进线程池中,让线程池来执行任务
    }
    exec.shutdown();
    
    有了FixedTreadPool可以一次性预先执行代价高昂的线程分配,因此也就可以限制线程的数量了。这可以节省时间,因为不用为每个任务都固定地赋初创建线程的开销。
    当线程用完之后,接下来的任务会等待其他任务结束之后,再开始执行。
    ExecutorService exec = Executors.newFixedThreadPool(3);  // 有限线程集去执行任务
    for (int i = 0; i < 5; i++) {
          
          
    	exec.execute(new RunnableTask());
    }
    exec.shutdown();
    
    SingleThreadExecutor就像是线程数量为1的FixedTreadPool。对于你希望在另一个线程中连续运行的任何事物来说,都是很有用的。比如,监听进入的套接字连接任务,更新本地或远程日志的小任务,或者是事件分发线程。
    ExecutorService exec = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 5; i++) {
          
          
    	exec.execute(new RunnableTask());
    }
    exec.shutdown();
    
    可以用SingleThreadExecutor来执行多个线程对同一个文件系统的操作,实现任意时刻都只有唯一的任务在运行。
  • 在任务中产生返回值
    实现Callable接口,通过ExecutorService.submit()方法调用它。
    submit()会产生Future对象,它用Callable返回结果的特定类型进行了参数化。可以用isDone()方法来查询Future是否已经完成,如果任务完成时,它会具有一个结果,可以调用get()方法来获取该结果。也可以直接调用get(),如果还没完成,get()就会阻塞,直至结果准备就绪。
    // Callable接口的实现
    public class CallableImpl implements Callable<String> {
          
          
      private static int count = 0;
      private final int id;
    
      public CallableImpl() {
          
          
        this.id = count++;
      }
    
      @Override
      public String call() throws Exception {
          
          
        return "id: " + id;
      }
    }
    
    public class CallableTest {
          
          
    public static void main(String[] args) {
          
          
        ExecutorService exec = Executors.newCachedThreadPool();
        ArrayList<Future<String>> strings = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
          
          
            strings.add(exec.submit(new CallableTask()));
        }
        for (Future<String> string : strings) {
          
          
            try {
          
          
                System.out.println(string.get());
            } catch (Exception e) {
          
          
                e.printStackTrace();
            } finally {
          
          
                exec.shutdown();
            }
        }
    }
    }
    
  • 休眠
    任务中止执行给定的时间。
    Thread.sleep(100);
    TimeUnit.MILLISECONDS.sleep(100); //新方法
    
  • 优先级
    线程的优先级将该线程的重要性传递给调度器。调度器将倾向于让优先权最高的线程先执行,但不意味着优先权低的线程就不执行。但是,在绝大多数情况下,所有线程都应该以默认优先级执行。
    // 在run()方法中
    Thread.currentThread().setPriority(Thread.MIN_PRIORITY);  // 最小优先级
    Thread.currentThread().setPriority(Thread.NORM_PRIORITY);  // 普通优先级
    Thread.currentThread().setPriority(Thread.MAX_PRIORITY);  // 最大优先级
    
  • 让步:给线程调度机制一个暗示,工作已经完成地差不多了,可以给其他线程使用CPU了。但是,这是建议由相同优先级的其他线程可以运行。
    Thread.yield();
    
  • 后台线程:是指程序运行的时候,在后台提供的一种通用服务的线程,并且这种线程并不属于程序不可或缺的部分。因此,在所有非后台线程结束时,程序就终止了,所有后台进程也会被杀死。
    public class DaemonTest {
          
          
      public static void main(String[] args) throws InterruptedException {
          
          
        for (int i = 0; i < 5; i++) {
          
          
          Thread daemon = new Thread(new SimpleDaemon());
          daemon.setDaemon(true);   // 设置这个线程是后台线程
          daemon.start();
        }
        System.out.println("All daemon has built!");
        TimeUnit.MILLISECONDS.sleep(1000);
      }
    }
    
    class SimpleDaemon implements Runnable {
          
          
      @Override
      public void run() {
          
          
        try {
          
          
          while (true) {
          
          
            TimeUnit.MILLISECONDS.sleep(100);
            System.out.println(Thread.currentThread().getName() + " " + this);
          }
        } catch (InterruptedException e) {
          
          
          e.printStackTrace();
        }
      }
    }
    
    必须在start()之前设置为后台线程。
    可以通过编写定制的ThreadFactory定制由Executor创建的线程的属性(后台、优先级、名称)。
    class DaemonThreadFactory implements ThreadFactory {
          
          
      @Override
      public Thread newThread(Runnable r) {
          
          
        Thread thread = new Thread(r);
        thread.setDaemon(true);
        return thread;
      }
    }
    
    应该是一个工厂模式,可以给出创建的对象之前,处理预处理一些内容。
    public class ThreadFactoryTest implements Runnable{
          
          
      public static void main(String[] args) {
          
          
      // 在创建线程池的时候,把ThreadFactory放进去
        ExecutorService exec = Executors.newCachedThreadPool(new DaemonThreadFactory());
        for (int i = 0; i < 5; i++) {
          
          
          exec.execute(new ThreadFactoryTest());
        }
        try {
          
          
          TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
          
          
          e.printStackTrace();
        }
      }
    
      @Override
      public void run() {
          
          
        try {
          
          
          while (true){
          
          
            System.out.println(Thread.currentThread().getName());
            TimeUnit.MILLISECONDS.sleep(100);
          }
        } catch (InterruptedException e) {
          
          
          e.printStackTrace();
        }
      }
    }
    
    后台线程创建的线程,也是后台线程。
  • 编码的变体
    也可以通过继承Thread类,重写run()方法来实现。
    class MyThread extends Thread {
          
          
        @Override
        public void run() {
          
          
            super.run();
            for (int i = 0; i < 1000; i++) {
          
          
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
        }
    }
    
    – 自管理的Runnable
    有可能变得有问题,因为另一个任务可能在构造器结束之前开始执行,这意味着该任务能够访问处于不稳定状态的对象。
    class RunnableImpl implements Runnable {
          
          
      private Thread t = new Thread(this);  // 创建线程
      public RunnableImpl() {
          
          
        t.start();  // 创建实例的时候,就开启
      }
      @Override
      public void run() {
          
          
        for (int i = 0; i < 3; i++) {
          
          
          System.out.println(Thread.currentThread().getName() + " " + i);
          Thread.yield();
        }
      }
    }
    
    – 线程代码隐藏在类中
    class InnerThread1 {
          
          
    	private Inner inner;
    	// 内部类
    	private class Inner extends Thread {
          
          
    		Inner(String name) {
          
          
    			super(name);  // 定义线程的名字
    			start();  // 直接就开启了
    		}
    		@Override
    		public void run() {
          
          
    			...
    		}
    	}
    	// 构造器构造的时候,顺便把线程实例化了
    	public InnerThread1(String name) {
          
          
    		inner = new Inner(name);
    	}
    }
    
    class InnerThread2 {
          
          
    	private Thread t;
    	public InnerThread2(String name) {
          
          
    	// 匿名内部类
    		t = new Thread(name) {
          
          
    			@Override
    			public void run() {
          
          
    				...
    			}
    		}
    		t.start();  // 开启
    	}
    }
    
    class InnerRunnable1 {
          
          
    	private Inner inner;
    	public InnerRunnable1(String name) {
          
          
    		inner = new Inner(name);
    	}
    	private class Inner implements Runnable {
          
          
    		Thread t;
    		public Inner(String name) {
          
          
    			// 在Runnable实现类内部,直接创建Thread对象
    			t = new Thread(this, name);
    			t.start();
    		}
    		@Override
    		public void run() {
          
          
    			...
    		}
    	}
    }
    
    class InnerRunnable2 {
          
          
    	private Thead t;
    	public InnerRunnable2(String name) {
          
          
    		t = new Thread(new Runnable() {
          
          
    			@Override
    			public void run() {
          
          
    				...
    			}
    	}, name);
    	t.start();
    	}
    }
    
    class ThreadMethod {
          
          
    	private Thread t;
    	private String name;
    	public ThreadMethod(String name) {
          
          
    		this.name = name;
    	}
    	public void runTask() {
          
          
    		t = new Thread(name) {
          
          
    			@Override
    			public void run() {
          
          
    				...
    			}
    		}
    		t.start();
    	}
    }
    
  • 加入一个线程
    一个线程可以通过其他线程之上调用join()方法,效果是此线程会被挂起,直到目标线程结束,才恢复。
    class Joiner extends Thread {
          
          
    	Thread t;
    	public Joiner (Thread t) {
          
          
    		this.t = t;
    	}
    	@Override
    	public void run() {
          
          
    		...
    		t.join();   // 允许其他线程插入
    		...
    	}
    }
    
  • 捕获异常
    为了解决在run()方法中,无法捕获异常的问题。允许在每个Thread中附着一个异常处理器Thread.UncaughtExceptionHandler.uncaughtException()会在线程因未捕获的异常而临近死亡时被调用。为了使用它,需要创建一个新类型的ThreadFactory,它将在每个新创建的Thread对象上附着一个Thread.UncaughtExceptionHandler
    public class ExceptionHandlerTest {
          
          
      public static void main(String[] args) {
          
          
        ExecutorService exec = Executors.newCachedThreadPool(new MyThreadFactory());
        exec.execute(new Runnable() {
          
          
          @Override
          public void run() {
          
          
            throw new RuntimeException();
          }
        });
      }
    }
    
    class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
          
          
      @Override
      public void uncaughtException(Thread t, Throwable e) {
          
          
        System.out.println(t.getName() + " caught " + e);
      }
    }
    
    class MyThreadFactory implements ThreadFactory {
          
          
      @Override
      public Thread newThread(Runnable r) {
          
          
        Thread t = new Thread(r);
        t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());   // 设置一个异常处理器
        return t;
      }
    }
    
    或者在Thread类中设置一个静态域,并将这个处理器设置为默认的未捕获异常处理器
    Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
    

    共享受限资源

  • 对于并发工作,你需要某些方式来防止两个任务访问相同的资源,至少在关键阶段不能出现这种情况。
  • 基本上所有的并发模式在解决线程冲突问题的时候,都是采用序列化访问共享资源的方案。这意味着在给定时刻只允许一个任务访问共享资源。通常通过在代码前面加上一句锁语句来实现。锁语句产生了一种互相排斥的效果,所以这种机制常常被称为互斥量(mutex)。
  • 所有要访问同一个共享资源的方法都要标志为synchronized。如果某个任务处于一个对标记为synchronized的方法的调用中,那么在这个线程从这个方法返回之前,其他所有要调用类中任何标记为synchronized的方法都会被阻塞。
  • 所有对象都自动含有一把单一的锁。所有synchronized共享一把锁,这样才可以防止多个任务同时访问被编码的对象内存
  • 在使用并发的时候,将域(属性)设置为private很重要。外面的对象只能通过方法调用来获取域。否则,外界直接获取并设置域就好了。
    public synchronized int getXxx(){
          
          }  // 同步方法
    public synchronized int setXxx(){
          
          }
    
  • Lock对象:显示地创建、锁定、释放。相比较内建的锁,缺乏优雅性,但具备灵活性。
    private Lock lock = new ReentrantLock();   // 创建锁
    public int next() {
          
          
    	lock.lock();   // 上锁
    	...
    	lock.unlock();   // 释放锁
    }
    
    public class AttemptLocking {
          
          
    	private ReentrantLock lock = new ReetrantLock();
    	private void untimed() {
          
          
    		boolean captured = lock.tryLock();  // 查询是否可以进入,是则返回true,否则返回false
    		// boolean captured = lock.tryLock(2, TimeUnit.SECONDS);  // 等两秒,还没释放那就算了
    		try{
          
          
    			System.out.println("tryLock(): " + captured);
    		} finally {
          
          
    			if (captured)   // 如果已经被锁住了,那就不进去了
    				lock.unlocked();
    		}
    		
    	}
    }
    
  • 原子性和易变性
    – 原子性可以应用于除longdouble之外的所有基本数据类型之上的“简单操作”。对于读取和写入除longdouble之外的基本类型变量这样的操作,可以保证它们会被当做不可分(原子)的操作来操作内存
    – 当定义longdouble变量时,如果使用volatile关键字,就会获得(简单的赋值与返回操作的)原子性。
    volatile关键字还确保了应用中的可视性,如果你将一个域声明为volatile的,那么只要你对这个域产生了写操作,那么所有的读操作就都可以看到这个修改。即使使用了本地缓存,情况也确实如此,volatile域会立即被写入到内存中,而读取操作就发生在主存中。
    – 如果一个域完全由synchronized方法或语句块来防护,那么就不必将其设置为volatile的。
    – 以下操作均不是原子性的。其中包括取数字、加法、存数字几个操作。
    i++;
    i += 2;
    
  • 原子类:AtomicIntegerAtomicLongAtomicReference等特殊的原子性变量类。这些类被调整为可以使用在某些现代处理器上的可获得的,并且是在机器级别上的原子性。在涉及性能调优时,它们就大有用武之地。
    public class AtomicIntegerTest implements Runnable {
          
          
    	private AtomicInteger i = new AtomicInteger(0);  // 原子类
    	public int getValue() {
          
           return i; }  // 获取值
    	private int evenIncrement() {
          
           return i.addAndGet(2); }  // 自增2
    	public void run() {
          
          
    		while (true) {
          
          
    			evenIncrement();
    		}
    	}
    }
    
  • 临界区:为了防止多个线程同时方法内部的部分代码而非防止访问整个方法。通过这种方法分离出来的代码被称为临界区。它也可以使用synchronized关键字建立。这里,sychronized被用来指定某个对象。
    synchronized (syncObject) {
          
          
    // 临界区
    }
    
  • 线程本地存储:是一种自动化机制,可以为使用相同变量的每个不同线程都创建不同的存储。
    因此,如果有5个线程都要使用变量x所表示的对象,拿线程本地存储就会生成5个用于x的不同的存储块。创建和管理线程本地存储可以由java.lang.ThreadLocal类来实现
    class Accessor implements Runnable {
          
             // 任务
    	private final int id;  // 每个实例都有一个独有的id
    	public Accessor(int idn) {
          
           id = idn; }
    	public void run() {
          
           
    		while(!Thread.currentThread().isInterrupted()) {
          
            // 如果当前线程没有被中断
    			ThreadLocalVariableHolder.increment();
    			System.out.println(this);  // 输出当前值
    			Thread.yield();   // 让步
    		}
    	}
    	
    	public String toString() {
          
          
    		return "#" + id + ":" + ThreadLocalVariableHolder.get();
    	}
    }
    
    public class ThreadLocalVariableHolder {
          
          
    	private static ThreadLocal<Integer> value = new ThreadLocal<>() {
          
             // 不同的Thread都有不同的存储块
    		private Random rand = new Random(47);
    		@Override
    		protected synchronized Integer InitialValue() {
          
          
    			return rand.nextInt(10000);
    		}
    	}
    	public static void increment() {
          
          
    		value.set(value.get() + 1);  // 自增
    	}
    	public static int get() {
          
           return value.get(); }
    	public static void main(String[] args) {
          
          
    		ExecutorService exec = Exectors.newCachedThreadPool();
    		for(int i = 0; i < 5; i++) {
          
          
    			exec.execute(new Accessor(i));  // 创建5个任务
    		}
    		TimeUnit.SECONDS.sleep(3);
    		exec.shutdownNow();
    	}
    }
    
    /* output
    #0: 9259
    #1: 556
    #2: 6694
    #3: 1862
    #4: 962
    #0: 9260
    #1: 557
    #2: 6695
    #3: 1863
    #4: 963
    ...
    */
    
    当运行这个程序时,可以看到每个单独的线程都被分配了自己的存储。因为它们每个都需要跟踪自己的计数值,即使只有一个TheadLocalVariableHolder对象。

终结任务

  • 装饰性花园
    通过标志位,实现任务的终结
    class Count {
          
          
    	private int count = 0;
    	private Random rand = new Random(47);
    	public synchronized int increment() {
          
             // 通过同步方法递增
    		int temp = count;
    		if(rand.nextBoolean()) Thread.yield();  // 50% 概率让步
    		return (count = ++temp);  // 返回count
    	}
    	public synchronized int value() {
          
           return count; }  // 通过同步方法获取当前个数
    }
    
    class Entrance implements Runnable {
          
          
    	private static Count count = new Count();
    	private static List<Entrance> entrances = new ArrayList<>();   // 保存所有Entrance的List
    	private int number = 0;
    	private final int id;
    	private static volatile boolean canceled = false;  // cancle标志位,通过volatile保证原子性
    	private static void cancel() {
          
           canceled = true; }
    	public Entrance(int id) {
          
          
    		this.id = id;
    		entrances.add(this);  // 把当前entrance保存在List中
    	} 
    	public void run() {
          
          
    		while(!canceled) {
          
            // 停止标志位
    			synchronized(this) {
          
          
    				++number;  // number递增
    			}
    			print(this + " Total: " + count.increment());
    			try {
          
          
    				TimeUnit.MILLISECONDS.sleep(100);
    			} cache(InterruptedException e) {
          
          
    				print("sleep interrupted!");
    			}
    		}
    		print("Stopping" + this);  // 停止该任务
    	}
    	public synchronized int getValue() {
          
           return number; }
    	public String toString() {
          
          
    		return "Entrance " + id + ": " + getValue();
    	}
    	public static int getTotalCount() {
          
          
    		return count.value();
    	}
    	public static int sumEntrances() {
          
          
    		int sum = 0;
    		for(Entrance entrance: entrances) {
          
          
    			sum += entrance.getVlue();  // 叠加
    		}
    		return sum;
    	}
    }
    
    public class OrnamentalGarden {
          
          
    	public static void main(String[] args) throws Exception {
          
          
    		ExecutorService exec = Executors.newCachedThreadTool();
    		for(int i = 0; i < 5; i++) {
          
          
    			exec.execute(new Entrance(i));  // 分别执行任务
    		}
    		// 跑一段时间
    		TimeUnit.SECONDS.sleeo(3);
    		Entrance.cancel();   // 取消标志位开启
    		exec.shutdown();   // 关闭线程池
    		if(!exec.awaitTermination(250, TimeUnit.MILLISENCONDS))   // 等250ms,检查是否关闭了所有线程
    			print("Some tasks were not terminated!");
    		print("Total: " + Entrance.getTotalCount());  // 获取个数
    		print("Sum of Entrances: " + Entrance.sumEntrances());  // 获取总和
    	}
    }
    
  • 进入阻塞状态的几个原因
    – 通过调用sleep(milliseconds)使任务进入休眠状态
    – 通过调用wait()使线程挂起,直到线程得到了notify()notifyAll()消息
    – 任务等待某个输入/输出完成
    – 任务试图在某个对象上调用其同步控制方法,但对象锁不可用,因为另一个任务已经获取了这个锁
  • 中断:Thread包含interrupt()方法,因此可以终止被阻塞的任务。如果一个线程已经被阻塞,或者试图执行一个阻塞操作,那么设置这个线程的中断状态将抛出InterruptedException,当抛出该异常或者该任务调用Thread.interrupted()时,中断状态将被复位。
    class SleepBlocked implements Runnable {
          
            // 因为睡眠导致的阻塞
    	public void run() {
          
          
    		try {
          
          
    			TimeUnit.SECONDS.sleep(100);  // 休眠100s
    		} catch (InterruptedException e) {
          
             // 捕获到InterruptedException异常
    			print("InterruptedException");
    		}
    		print("Exiting SleepBlocked.run()");  // 关闭该任务
    	}
    }
    
    class IOBlocked implements Runnable {
          
            // 由于输入流导致的阻塞
    	private InputStream in;
    	public IOBlocked(InputStream is) {
          
           in = is; }
    	public void run() {
          
          
    		try {
          
          
    			print("Waiting for read():");
    			in.read();  // 等待,阻塞中
    		} catch(IOException e) {
          
          
    			if(Thread.currentThread().isInterrupted()) {
          
            // 是否是被中断导致的异常
    				print("Interrupted from blocked I/O");
    			} else {
          
          
    				throw new RuntimeException(e);
    			}
    		}
    		print("Exiting IOBlocked.run()");
    	}
    }
    
    class SynchronizedBlocked implements Runnable {
          
          
    	public synchronized void f() {
          
             // 同步方法
    		while(true) {
          
            // 永远不会释放
    			Thread.yield();
    		}
    	}
    	public SynchronizedBlocked() {
          
          
    		new Thread() {
          
          
    			public void run() {
          
          
    				f(); // 获取了对象锁
    			}
    		}.start();  // 直接在构造器中开启一个线程执行f()
    	}
    	public void run() {
          
          
    		print("Trying to call f()"); // 尝试去获取f()
    		f();  // 将会一直卡在这,阻塞了
    		print("Exiting SynchronizedBlocked.run()");
    	}
    }
    
    public class Interrupting {
          
          
    	private static ExecutorService exec = Executors.newCachedThreadPool();  // 线程池
    	static void test (Runnable r) throws InterruptedException {
          
          
    		Future<?> f = exec.submit(r); //  为了实现单个单个线程的中断,使用submit而不用execute
    		TimeUnit.MILLISECONDS.sleep(100);  // 休眠100ms
    		print("Interrupting " + r.getClass().getName());  // 获取当前任务的名字
    		f.cancel(true);  // 中断
    		print("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());  // 测试同步锁导致的阻塞
    		TimeUnit.SECONDS.sleep(3);
    		print("Aborting with System.exit(0)");
    		System.exit(0);
    	}
    }
    /* output
    Interrupting SleepBlocked      // 睡眠阻塞
    InterruptedException     // 中断成功
    Exiting SleepBlocked.run()
    Interrupt send to SleepBlocked
    Waiting for read():  // 输入的阻塞
    Interrupting IOBlocked // 没有中断消息,说明中断失败了
    Interrupt send to IOBlocked
    Trying to call f()   // 尝试调用同步方法,但是阻塞
    Interrupting SynchronizedBlocked   // 没有中断消息,说明中断失败了
    Interrupt sent to SynchronizedBlocked
    Aboring with System.exit(0)
    */
    
    说明SleepBlock是可中断的阻塞,IOBlockedSynchronizedBlocked是不可中断的阻塞。

猜你喜欢

转载自blog.csdn.net/weixin_42524843/article/details/113769930