线程的生命周期和常用方法 生命周期 根据jdk
官方文档,线程状态有以下几种
一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。
如图示所见
代码演示 NEW / TIMED_WAITING / TERMINATED 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package ThreadMethod;import java.util.concurrent.TimeUnit;public class ThreadState { public static void main (String[] args) throws InterruptedException { Thread thread = new Thread (new Runnable () { @Override public void run () { try { TimeUnit.SECONDS.sleep(3 ); } catch (InterruptedException e) { throw new RuntimeException (e); } } }); System.out.println("线程状态:" + thread.getState()); thread.start(); Thread.sleep(2000 ); System.out.println("线程状态:" + thread.getState()); Thread.sleep(2000 ); System.out.println("线程状态:" + thread.getState()); } }
结果 1 2 3 4 线程状态:NEW 线程状态:TIMED_WAITING 线程状态:TERMINATED 线程状态:TERMINATED
WAITING / BLOCKED 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package ThreadMethod;public class BlockThreadState implements Runnable { static final Object lock = new Object (); public static void main (String[] args) throws InterruptedException { BlockThreadState blockThread = new BlockThreadState (); Thread thread = new Thread (blockThread); System.out.println("thread state : " + thread.getState()); thread.start(); Thread.sleep(20 ); System.out.println("thread state : " + thread.getState()); synchronized (lock){ lock.notify(); } System.out.println("thread state : " + thread.getState()); Thread.sleep(20 ); System.out.println("thread state : " + thread.getState()); } @Override public void run () { try { synchronized (lock){ lock.wait(); for (int i = 0 ; i < 1000000000 ; i++) { continue ; } } } catch (InterruptedException e) { e.printStackTrace(); } } }
结果 1 2 3 4 thread state : NEW thread state : WAITING thread state : BLOCKED thread state : TERMINATED
常见方法 wait() wait()方法执行后,会阻塞线程,同时释放锁,如果想要唤醒该线程,则需要以下条件
另一个线程调用这个对象的notify()方法,且刚好被唤醒的时本线程
另一个线程调用了这个对象的notifyAll
()方法
过了wait(long timeout) 规定的超时时间,如果传入0就是永久等待
线程自身调用了interrupt()
PS: 使用wait()方法时,必须先拥有monitor锁, 也就是说wait方法需要放在同步代码块中执行
notify/notifyAll
notify/notifyAll
用于唤醒线程,当另一个线程调用wait()进入 waitting
状态时,另一个线程调用notifyAll
()可唤醒当前线程(如果有多个线程,使用notify并不一定能够唤醒线程)
组合使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 public class BlockThread { public static void main (String[] args) { Message message = new Message (); Thread waitThread = new Thread (new WaitThread (message)); Thread notifyThread = new Thread (new NotifyThread (message)); waitThread.start(); notifyThread.start(); } } class Message { private boolean isReady = false ; public synchronized void waitForMessage () { while (!isReady) { try { System.out.println("线程进入等待状态" ); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("收到消息!" ); } public synchronized void sendMessage () { isReady = true ; System.out.println("唤醒等待的线程" ); notify(); } } class WaitThread implements Runnable { private Message message; public WaitThread (Message message) { this .message = message; } @Override public void run () { message.waitForMessage(); } } class NotifyThread implements Runnable { private Message message; public NotifyThread (Message message) { this .message = message; } @Override public void run () { message.sendMessage(); } }
图示 monitor锁
Entry Set 入口集
The owner 锁持有线程
如果方法没有执行完之前没有释放锁,则程序正常退出并释放锁
如果方法没有执行完之前释放了锁如调用了wait()方法,则程序再次进入等待集进行抢锁
Wait Set 等待集
如果上方入口集,不同点是等待集在执行方法锁时中途释放了锁
wait、notify 实现生产者消费者模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 import java.util.LinkedList;import java.util.List;import java.util.TreeMap;public class ProductAndConsumer { List<Object> container = new LinkedList <>(); public static void main (String[] args) { ProductAndConsumer productAndConsumer = new ProductAndConsumer (); Thread thread1 = new Thread (new Product (productAndConsumer)); Thread thread2 = new Thread (new Consumer (productAndConsumer)); thread1.start(); thread2.start(); } public synchronized void addObject () throws InterruptedException { if (container.size() >=100 ){ wait(); } container.add(new Object ()); System.out.println("正在生产第" + container.size() + "个" ); notify(); } public synchronized void conObject () throws InterruptedException { if (container.size() == 0 ){ wait(); } container.remove(0 ); System.out.println("正在消费第" + container.size() + "个" ); notify(); } } class Product implements Runnable { private final ProductAndConsumer productAndConsumer; Product(ProductAndConsumer productAndConsumer){ this .productAndConsumer = productAndConsumer; } @Override public void run () { while (true ){ try { productAndConsumer.addObject(); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Consumer implements Runnable { private final ProductAndConsumer productAndConsumer; Consumer(ProductAndConsumer productAndConsumer){ this .productAndConsumer = productAndConsumer; } @Override public void run () { while (true ){ try { productAndConsumer.conObject(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
解释
使用类ProductAndConsumer
作为锁对象,container作为容器
wait notify来进行线程之间的通讯,在数量满足条件时使用wait释放当前锁对象,另一个对象拿到锁之后进行生产或消费
图示
sleep() Thread.sleep()
是Java中一个静态native方法,用于使当前线程进入休眠状态(暂停执行)一段指定的时间。
它的方法签名为:
1 public static native void sleep (long millis) throws InterruptedException;
参数millis
表示线程休眠的时间,以毫秒为单位。传入的值是一个正整数,表示线程要休眠的毫秒数。注意,该方法会抛出InterruptedException
异常,因为线程在休眠期间可能被其他线程中断。
特点和用法
线程阻塞 :调用Thread.sleep()
方法会导致当前线程暂停执行,进入阻塞状态。在指定的时间内,线程不会进行任何操作。
时间精度 :传入的休眠时间是以毫秒为单位,但实际的休眠时间可能会稍长或稍短。具体的精度取决于底层操作系统和JVM的实现。
中断响应 :如果在线程休眠期间,另一个线程中断了正在休眠的线程,Thread.sleep()
方法会抛出InterruptedException
异常。可以在catch
块中处理该异常,或者将异常继续向上抛出。
不会释放锁 :Thread.sleep()
方法会暂停当前线程的执行,但不会释放任何锁。如果线程在执行同步代码块或同步方法时调用了Thread.sleep()
,其他线程仍无法获得该锁。
静态方法 :Thread.sleep()
是一个静态方法,可以直接通过Thread
类调用,无需创建线程对象。
用途 :常见的用途包括模拟延迟、定时任务、控制线程执行顺序等。
Thread.sleep
和TimeUnit
比较
精度和可读性: Thread.sleep()
的参数是以毫秒为单位的时间值,表示线程要休眠的时间。而TimeUnit
提供了更高层次的时间单位,如TimeUnit.SECONDS
表示秒,TimeUnit.MILLISECONDS
表示毫秒等。使用TimeUnit
可以使代码更具可读性,而不需要手动计算毫秒数。
异常处理: Thread.sleep()
方法会抛出InterruptedException
异常,因为线程在休眠期间可能会被其他线程中断。而TimeUnit
方式不会直接抛出异常,需要开发者手动处理中断情况。
静态与非静态: Thread.sleep()
是Thread
类的静态方法,可以直接通过类名调用。而TimeUnit
是一个枚举类,需要通过具体的枚举常量来调用其方法,例如TimeUnit.SECONDS.sleep(1)
。
可读性和易用性: 使用TimeUnit
可以提高代码的可读性,因为可以直观地表示时间单位。此外,TimeUnit
还提供了其他方法,如TimeUnit.toMillis()
、TimeUnit.toSeconds()
等,方便进行时间单位之间的转换。
TimeUnit
源码以下是截取部分源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public enum TimeUnit { public void sleep (long timeout) throws InterruptedException { if (timeout > 0 ) { long ms = toMillis(timeout); int ns = excessNanos(timeout, ms); Thread.sleep(ms, ns); } } }
可以看到TimeUnit
底层还是调用了Thread.sleep()
有一个比较隐含的地方就是当使用过TimeUnit
的sleep
方法时,如果传入的时间小于是不会进入if判断,而Thread.sleep()
方法如果传参小于0则会抛出异常(源码见下)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static void sleep (long millis, int nanos) throws InterruptedException { if (millis < 0 ) { throw new IllegalArgumentException ("timeout value is negative" ); } if (nanos < 0 || nanos > 999999 ) { throw new IllegalArgumentException ( "nanosecond timeout value out of range" ); } if (nanos >= 500000 || (nanos != 0 && millis == 0 )) { millis++; } sleep(millis); }
join() Thread.join()
是Java中的一个方法,用于等待调用该方法的线程执行完毕。它的作用是让当前线程等待指定线程执行结束,然后再继续执行当前线程的后续代码。
简单来说就是阻塞主线程。
简单示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package ThreadMethod;public class JoinMethod { public static void main (String[] args) { Thread thread1 = new Thread (()->{ try { Thread.sleep(3000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程执行完毕" ); },"子线程" ); thread1.start(); try { thread1.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("主线程执行完毕" ); } }
结果
可以看到子线程等待了3秒,但是最终还是子线程先执行完毕在执行主线程打印,原因是因为thread1.join 对主线程进行了阻塞,这是主线程需要等子线程执行完毕才会执行后面的语句
特点和注意事项
等待执行: 调用join()
方法的线程将会等待指定线程执行完毕。如果指定线程已经执行完毕,则join()
方法会立即返回。
阻塞调用线程: 在调用join()
方法期间,当前线程将会被阻塞,暂停执行。只有当指定线程执行完毕后,当前线程才会解除阻塞,继续执行。
异常处理: join()
方法会抛出InterruptedException
异常,因为在等待过程中,当前线程可能会被中断。可以在catch
块中处理该异常,或将异常继续向上抛出。
顺序执行: 通过使用join()
方法,可以控制线程的执行顺序。调用join()
方法后,当前线程会等待指定线程执行完毕,然后再继续执行后续代码。
调用对象: join()
方法是一个实例方法,需要通过线程对象调用。例如,如果thread1
是一个Thread
对象,可以使用thread1.join()
来等待thread1
执行完毕。
源码和底层实现 以下截取部分Thread.join
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public final void join () throws InterruptedException { join(0 ); } public final synchronized void join (long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0 ; if (millis < 0 ) { throw new IllegalArgumentException ("timeout value is negative" ); } if (millis == 0 ) { while (isAlive()) { wait(0 ); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0 ) { break ; } wait(delay); now = System.currentTimeMillis() - base; } } } public final native void wait (long timeout) throws InterruptedException;
可以看到join的底层还是使用wait方法实现的,子线程调用wait方法让主线程进入等待状态,在运行结束后自动调用notify方法唤醒主线程。(具体唤醒的方法在jvm中)
有一点疑问我没有找到解释:为什么调用子线程的wait(0)方法,阻塞的确是主线程呢?
CountDownLatch
和CyclicBarrier
使用countDownLatch
和CyclicBarrier
也可以实现线程之间的阻塞,具体暂不讨论
yeild() (让步) yield()
是一个静态方法,它属于 Thread
类,用于提示调度器将当前线程让出 CPU 的执行权,使得其他具有相同优先级的线程有机会执行。
yield()
方法的调用并不能保证一定会使其他线程获得执行机会,它仅是一个提示。具体的调度行为取决于操作系统和 JVM 的实现。因此,在实际应用中,不应过度依赖 yield()
方法来控制线程的执行顺序,而应使用更可靠的线程同步机制来实现需要的线程协作和同步。