线程
概述
- 进程:应用程序在内存中分配的空间,也就是运行中的程序,各个进程之间互不干扰,进程是操作系统进行资源分配的基本单位,单独占有内存地址空间和其他系统资源
- 线程: 线程是操作系统进行调度(cpu分配时间)的基本单位, 是在进程中执行的一个任务,共享所属进程的内存地址空间和资源
jvm层面线程状态
- 以下状态为虚拟机线程状态,并不映射任何操作系统的线程状态
-
新建状态(NEW): 新创建的线程,还未调用start() 方法
-
可运行状态(RUNNABLE) : 调用了start() 方法,线程已经进入虚拟机,在等待操作系统的其他资源,如IO阻塞,网络阻塞,或者已经在运行中
-
阻塞状态(BLOCKED): 线程需要等待获取锁,等待进入同步(synchronized)方法或者同步块中, 或者说当因为获取不到锁而无法进入同步块时,线程处于 BLOCKED 状态
- 等待状态(WAITING): 当条件不满足时,等待其他线程完成操作使得条件满足,并通知等待中的线程,调用
Object.wait(),Thread.join(),LockSupport.park()方法会使得线程进入等待状态。当一个线程调用Object.wait()方法时,需要等待其他线程调用Object.notify()/Object.notifyAll()方法来唤醒, 当一个线程被 调用LockSupport.park()方法时,线程进入等待状态,需要对应的LockSupport.unPark(Thread)方法来唤醒指定的线程,或者通过interrupted()方法中断线程使得线程继续执行,使得线程继续运行
- 等待状态(WAITING): 当条件不满足时,等待其他线程完成操作使得条件满足,并通知等待中的线程,调用
-
限时等待(TIMED_WAITING): 等待指定的时间,调用
Thread.sleep,Object.wait(long),Thread.join(long)等方法会让线程进入该状态 -
结束状态(TERMINATED) : 线程执行完毕或者退出
操作系统层面的线程状态
- 操作系统层面的线程状态主要围绕cpu 操作
- new : 新建一个线程
- ready: 进入就绪状态,获得了除cpu 外的其他资源
- running : 运行状态, 线程已在cpu 上执行
- waiting : 等待状态,由于I/O ,或者其他等待事件,使得线程放弃cpu资源,从而进入等待状态,在这些事件完成后,线程会从新进入ready 状态,等待cpu资源的分配
- terminated : 线程执行完成
重要方法
-
Object.wait-
wait方法调用前需要获得锁, 应该总是在 循环中调用,即条件判断应该放在循环的条件判断,而不是放在 if 的判断中,防止虚假幻想(幻想后任然不符合执行条件)
synchronized (obj) { while (<condition does not hold>) obj.wait(timeout); ... // Perform action appropriate to condition } -
wait 方法必须在同步方法或者同步块中
synchronized,即要先获取锁才能调用wait 方法,这是第一次enter -
当前线程调用 wait方法后会释放 锁,当前线程进入该锁的 等待队列中,当前线程进入等待状态
WAITING -
当收到其他线程的 notify 或者 notifyAll 通知后,当前线程并不能立即恢复执行,因为他已经释放了锁,所以要重新执行wait方法后的代码需要重新获取 锁,再次进入同步块中 这是第二次进入(reenter)
-
-
Thread.join- 有线程a,b , 在线程 a 中调用 b.join , 相当与让 a线程等待 b线程的完成 , 此时a 停止执行, 等b执行完了,会通知a线程,使其恢复执行
-
Thread.sleep- 经常会听到说 wait方法会释放锁, sleep 方法不会释放锁,其实这句话是不准确的,sleep 方法与锁无关,执行sleep 方法前并不是一定要获取锁(执行wait 方法前一定要先获取锁), 如果执行sleep 方法时没未获取锁,也就无所谓释放不释放,如果执行sleep 方法时已经获取了锁,则不会释放锁
-
Thread.yield()- 中断当前线程的执行,让出cpu, 让自己和其他线程重新竞争cup 资源并运行
-
Thread.interrupt()
-
如果当前线程不是在中断中,则该操作一直是被允许的
-
Thread.interrupt()只是对线程做一个中断标记 -
该方法不会中断一个正在运行的线程,即简单调用该方法后,线程状态任然是
Runable, 只是设置线程的中断状态即通过isInterrupted()方法查询中断状态为true,让用户自己选择时间地点去结束线程@Test public void test() { Thread thread = new Thread(() -> { this.interruptRunning(); }); thread.start(); } public void interruptRunning() { int i =0; while (i<5) { i ++; System.out.println("try interrupt"); Thread.currentThread().interrupt(); } System.out.println("中断状态:"+Thread.currentThread().isInterrupted()); // true System.out.println("线程状态:"+ Thread.currentThread().getState()); // runable System.out.println("end"); } // 输出 try interrupt try interrupt try interrupt try interrupt try interrupt 中断状态:true 线程状态:RUNNABLE end -
如果线程处于中断状态,然后再调用阻塞方法(比如:
Thread.sleep,Thread.join,Object.wait等使得线程进入阻塞的方法),则会抛出InterruptedException中断异常,并且清理中断状态,即再次调用isInterrupted()方法中断状态为false@Test public void test() { Thread thread = new Thread(() -> { this.interruptBlock(); }); thread.start(); } public void interruptBlock() { System.out.println("try interrupt"); Thread.currentThread().interrupt(); if (Thread.currentThread().isInterrupted()) { System.out.println("block"); try { Thread.sleep(300L); System.out.println("sleep ing"); } catch (InterruptedException e) { System.out.println("InterruptedException"); System.out.println(Thread.currentThread().isInterrupted()); // false System.out.println(Thread.currentThread().getState()); // RUNNABLE } } } // 输出结果 try interrupt block InterruptedException false RUNNABLE
-
LockSupport.park()-
使得当前线程停止执行进入等待状态,当调用
LockSupport.unPark(Thread)方法指定解锁该线程时,线程继续从中断处运行,或者该线程调用了interrupt()方法中断该线程时,也会使得线程结束等待状态,继续运行public class LockSupportTest { public static class ParkThread extends Thread{ public ParkThread(String name) { super(name); } @Override public void run() { System.out.println(getName() + " running 00000000"); LockSupport.park(); if (Thread.currentThread().isInterrupted()) { System.out.println(getName() + " interrupted "); } System.out.println(getName() + " running continue...."); } } @Test public void parkTest() { ParkThread p1 = new ParkThread("p1"); ParkThread p2 = new ParkThread("p2"); p1.start(); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } LockSupport.unpark(p1); p2.start(); p2.interrupt(); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(p2.getState()); // 如果不对p2线程进行interrupt操作或者unpart()操作,p2 线程将一直处于 WAITING 等待状态 } } // 执行结果 p1 running 00000000 p1 running continue.... p2 running 00000000 p2 interrupted p2 running continue.... TERMINATED -
LockSupport.park()的作用和Object.wait()方法的作用类似,都是使当前线程进入等待状态,但是LockSupport.park()操作并不需要获取锁,结束等待状态需要指定unpark对应的线程或者通过interrupt对应的线程, 而通过wait方法进入等待状态的线程,通过notify方法只是随机唤醒一个等待队列中的线程,或者通过notifyAll唤醒所有等待队列中的线程
-
线程的创建
-
继承Thread类,重写run 方法
public class Demo { public static class MyThread extends Thread { @Override public void run() { System.out.println("MyThread"); } } public static void main(String[] args) { Thread myThread = new MyThread(); myThread.start(); } } -
实现 Runnable 接口, 实现 run 方法
public class Demo { public static class MyThread implements Runnable { @Override public void run() { System.out.println("MyThread"); } } public static void main(String[] args) { new MyThread().start(); // Java 8 函数式编程,可以省略MyThread类 new Thread(() -> { System.out.println("Java 8 匿名内部类"); }).start(); } } -
Callable、Future与FutureTask
-
线程的创建不考虑 线程池,其实就是以上两种 (继承 Thread或者实现Runnable), 经常会觉得 Callable,Future,FutureTask 的组合是第三种线程的创建方式,其实不然,这种方式并不会创建新的线程,Callable,Future,FutureTask 的正常使用姿势是配合线程池,可以说是一种任务的执行方式, 通过Thread 类和 Runnable创建的线程任务执行并不会返回执行结果,因为他的run 方法是没有返回值的,而通过callable 定义的任务是有返回值的,通过Future或者FutureTask 可以获得任务的执行返回值。
public class CallableTest { public static void main(String args[]) { callableFutureTaskTest(); } public static void callableFutureTaskTest() { MyCallable task = new MyCallable(); FutureTask futureTask = new FutureTask<>(task); futureTask.run(); System.out.println("test name:"+Thread.currentThread().getName()); try { System.out.println( futureTask.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } //自定义Callable class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { // 模拟计算需要一秒 Thread.sleep(1000); System.out.println("callable name:"+Thread.currentThread().getName()); return 2; } } // 输出结果 ,由此可看出执行 call() 方法的也是主线程,且FutureTask 并未提供类似于Thread 的 start() 方法 callable name:main test name:main 2 -
Callable、Future与FutureTask 的正确使用姿势,具体操作放在线程池中讲述
public static void executorCallableTest() { // 使用 ExecutorService executor = Executors.newCachedThreadPool(); MyCallable task = new MyCallable(); Future<Integer> result = executor.submit(task); // 注意调用get方法会阻塞当前线程,直到得到结果。 // 所以实际编码中建议使用可以设置超时时间的重载get方法。 try { System.out.println(result.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } FutureTask<Integer> futureTask = new FutureTask<>(task); executor.submit(futureTask); try { System.out.println(futureTask.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } //自定义Callable class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { // 模拟计算需要一秒 Thread.sleep(1000); System.out.println("callable name:"+Thread.currentThread()); return 2; } }
-