目录
3.wait(long timeout, int nanos) 函数
interrupted()和 isinterrupted()的区别
1.11.4 InheritableThreadLocal 类
1.1 什么是线程
1.2 线程创建与运行
Java 中有三种线程创建方式1、继承 Thread 类方式的实现。
使用继承方式的好处是,在 run() 方法内获取当前线程直接使用 this 就可以了,无须 使用 Thread.currentThread() 方法; 不好的地方是 Java 不支持多继承,如果继承了 Thread 类, 那么就不能再继承其他类。另外任务与代码没有分离,当多个线程执行一样的任务时需要多份任务代码。如上代码中的 MyThread 类继承了 Thread 类,并重写了 run() 方法。在 main 函数里 面创建了一个 MyThread 的实例,然后调用该实例的 start 方法启动了线程。需要注意的是, 当创建完 thread 对象后该线程并没有被启动执行,直到调用了 start 方法后才真正启动了 线程, 处于就绪状态,这个就绪状态是指该 线程已经获取了除 CPU 资源外的其他资源,等待获取 CPU 资源后才会真正处于运行状态。 一旦 run 方法执行完毕,该线程就处于终止状态。public class ThreadTest { //继承Thread类并重写run方法 public static class MyThread extends Thread { @Override public void run() { System.out.println("I am a child thread"); } } public static void main(String[] args) { // 创建线程 MyThread thread = new MyThread(); // 启动线程 thread.start(); } }
2、实现 Runnable 接口的 run 方法
任务与代码有分离,当多个线程执行一样的任务时需要一份任务代码。public static class RunableTask implements Runnable{ @Override public void run() { System.out.println("I am a child thread"); } } public static void main(String[] args) throws InterruptedException{ RunableTask task = new RunableTask(); new Thread(task).start(); new Thread(task).start(); }
3、使用 FutureTask 方式
如上面代码所示,两个线程共用一个 task 代码逻辑,如果需要,可以给 RunableTask 添加参数进行任务区分。另外, RunableTask 可以继承其他类。但是上面介绍的两种方式 都有一个缺点,就是任务没有返回值。下面看最后一种,即使用 FutureTask 的方式。如上代码中的 CallerTask 类实现了 Callable 接口的 call() 方法。在 main 函数内首先创 建了一个 FutrueTask 对象(构造函数为 CallerTask 的实例),然后使用创建的 FutrueTask 对象作为任务创建了一个线程并且启动它,最后通过 futureTask.get() 等待任务执行完毕并 返回结果。//创建任务类,类似Runable public static class CallerTask implements Callable<String>{ @Override public String call() throws Exception { return "hello"; } } public static void main(String[] args) throws InterruptedException { // 创建异步任务 FutureTask<String> futureTask = new FutureTask<>(new CallerTask()); //启动线程 new Thread(futureTask).start(); try { //等待任务执行完毕,并返回结果 String result = futureTask.get(); System.out.println(result); } catch (ExecutionException e) { e.printStackTrace(); } }
小结 :使用 继承方式的好处是方便传参 ,你可以在子类里面添加成员变量,通过 set 方法设置参数或者通过构造函数进行传递,而如果使用 Runnable 方式,则只能使用主线 程里面被声明为 final 的变量。不好的地方是 Java 不支持多继承 ,如果继承了 Thread 类, 那么子类不能再继承其他类,而 Runable 则没有这个限制。前两种方式都没办法拿到任务 的返回结果,但是 Futuretask 方式可以。
1.3 线程通知与等待
1.wait() 函数
当一个线程调用一个共享变量的 wait() 方法时,该调用线程会被阻塞挂起,直到发生 下面几件事情之一才返回: ( 1 )其他线程调用了该共享对象的 notify() 或者 notifyAll() 方法; ( 2 )其他线程调用了该线程的 interrupt() 方法,该线程抛出 InterruptedException 异常返回。 如果调用 wait() 方法的线程没有事先获取该对象的监视器锁,则 调用 wait() 方法时调用线程会抛出 IllegalMonitorStateException 异常。 那么一个线程如何才能获取一个共享变量的监视器锁呢?使用 synchronized关键字。 ( 1 )执行 synchronized 同步代码块时,使用该共享变量作为参数。( 2 )调用该共享变量的方法,并且该方法使用了 synchronized 修饰。synchronized(共享变量){ //doSomething }
唤醒:一个线程可以从挂起状态变为可以运行状态; 虚假唤醒: 该线程没有被其他线程调用 notify()、notifyAll() 方法进行通知,或者被中断,或者等待超时。 在一个循环中调用 wait() 方法对虚假唤醒进行防范,退出循环的条件是满足了唤醒该线程的条件。synchronized void add(int a,int b){ //doSomething }
synchronized (obj) { while (条件不满足){ obj.wait(); } }
例子:生产者和消费者假如生产者线程 A 首先通过 synchronized 获取到了 queue 上的锁,那么 后续所有企图生产元素的线程和消费线程将会在获取该监视器锁的地方被阻塞挂起。线程 A 获取锁后发现当前队列已满会调用 queue.wait() 方法阻塞自己,然后释放获取的 queue 上的锁,这里考虑下为何要释放该锁?如果不释放,由于其他生产者线程和所有消费者线 程都已经被阻塞挂起,而线程 A 也被挂起,这就处于了死锁状态。这里线程 A 挂起自己 后释放共享变量上的锁,就是为了打破死锁必要条件之一的持有并等待原则。线程 A 释放锁后,其他生产者线程和所有消费者线程中会有一个线程获 取 queue 上的锁进而进入同步块,这就打破了死锁状态。//生产线程 synchronized (queue) { //queue 为共享变量,生产者线程在调用 queue 的 wait() 方法前,使用 //synchronized 关键字拿到了该共享变量queue 的监视器锁,所以调用 wait() //方法才不会抛出 IllegalMonitorStateException 异常 //消费队列满,则等待队列空闲,这里使用循环就是为了避免上面说的虚假唤醒问题。 while (queue.size() == MAX_SIZE) { try { //挂起当前线程,并释放通过同步块获取的queue上的锁,让消费者线程可以获取该锁,然后 //获取队列里面的元素 queue.wait(); } catch (Exception ex) { ex.printStackTrace(); } } //空闲则生成元素,并通知消费者线程 queue.add(ele); queue.notifyAll(); } } //消费者线程 synchronized (queue) { //消费队列为空 while (queue.size() == 0) { try{ //挂起当前线程,并释放通过同步块获取的queue上的锁,让生产者线程可以获取该锁,将生 //产元素放入队列 queue.wait(); } catch (Exception ex) { ex.printStackTrace(); } } //消费元素,并通知唤醒生产者线程 queue.take(); queue.notifyAll(); } }
当前线程调用共享变量的 wait() 方法后只会释放当前共享变量上的锁,如果当前线程还持有其他共享变量的锁,则这些锁是不会被释放的。// 创建资源 private static volatile Object resourceA = new Object(); private static volatile Object resourceB = new Object(); public static void main(String[] args) throws InterruptedException { // 创建线程 Thread threadA = new Thread(new Runnable() { public void run() { try { // 获取resourceA共享资源的监视器锁 synchronized (resourceA) { System.out.println("threadA get resourceA lock"); // 获取resourceB共享资源的监视器锁 synchronized (resourceB) { System.out.println("threadA get resourceB lock"); // 线程A阻塞,并释放获取到的resourceA的锁 System.out.println("threadA release resourceA lock"); resourceA.wait(); } } } catch (InterruptedException e) { e.printStackTrace(); } } }); // 创建线程 Thread threadB = new Thread(new Runnable() { public void run() { try { //休眠1s Thread.sleep(1000); // 获取resourceA共享资源的监视器锁 synchronized (resourceA) { System.out.println("threadB get resourceA lock"); System.out.println("threadB try get resourceB lock..."); // 获取resourceB共享资源的监视器锁 synchronized (resourceB) { System.out.println("threadB get resourceB lock"); // 线程B阻塞,并释放获取到的resourceA的锁 System.out.println("threadB release resourceA lock"); resourceA.wait(); } } } catch (InterruptedException e) { e.printStackTrace(); } } }); // 启动线程 threadA.start(); threadB.start(); // 等待两个线程结束 threadA.join(); threadB.join(); System.out.println("main over"); }
结果:
线程 B 首先尝试获取 resourceA 上的锁,如果当时线程 A 还没有调用 wait() 方法释放该锁,那么线程 B 会被阻塞,当线程 A 释放了 resourceA 上的锁后,线程 B 就会获取到 resourceA 上的锁,然后尝试获取 resourceB 上的锁。由于线程 A 调用的是 resourceA 上的 wait() 方法,不是调用resourceB 上的 wait() 方法,所以线程 A 挂起自己后并没有释放获取到的 resourceB 上的锁, 所以线程 B 尝试获取 resourceB 上的锁时会被阻塞。
当一个线程调用共享对象的 wait() 方法被阻塞挂起后, 如果其他线程中断了该线程,则该线程会抛出 InterruptedException 异常并返回。结果: threadA 调用共享对象 obj 的 wait() 方法后阻塞挂起了自己,然后 主线程在休眠 1s 后中断了 threadA 线程,中断后 threadA 在 obj.wait() 处抛出 java.lang. InterruptedException 异常而返回并终止。public class WaitNotifyInterupt { static Object obj = new Object(); public static void main(String[] args) throws InterruptedException { //创建线程 Thread threadA = new Thread(new Runnable() { public void run() { try { System.out.println("---begin---"); //阻塞当前线程 synchronized (obj) { obj.wait(); } System.out.println("---end---"); } catch (InterruptedException e) { e.printStackTrace(); } } }); threadA.start(); Thread.sleep(1000); System.out.println("---begin interrupt threadA---"); threadA.interrupt(); System.out.println("---end interrupt threadA---"); } }
2.wait(long timeout) 函数
- 比 wait() 方法多了一个超时参数timeout,把timeout 设置为 0 则和 wait 方法效果一样,因为wait 方法内部就是调用了 wait(0)。
- 如果一个线程调用共享对象的该方法挂起后,没在指定的 timeout ms 时间内被唤醒(被其他线程调用该共享变量的 notify() 或者 notifyAll() 方法唤醒),那么该函数还是会因为超时而返回。
- 如果在调用该函数时,传递了一个负的 timeout 则会抛出 IllegalArgumentException 异常。
3.wait(long timeout, int nanos) 函数
在其内部调用的是 wait(long timeout) 函数,如下代码只有在 nanos>0 时才使参数 timeout 递增 1 。public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos > 0) { timeout++; } wait(timeout); }
4.notify() 函数
- 一个线程调用共享对象的 notify() 方法后,会唤醒一个在该共享变量上调用 wait 系列方法后被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的。
- 唤醒它的线程释放了共享变量上的监视器锁后,被唤 醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起 竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可以继续执行。
- 类似 wait 系列方法,只有当前线程获取到了共享变量的监视器锁后,才可以调用共 享变量的 notify() 方法,否则会抛出 IllegalMonitorStateException 异常。
5.notifyAll() 函数
不同于在共享变量上调用 notify() 函数会唤醒被阻塞到该共享变量上的随机一个线程, notifyAll() 方法则会唤醒 所有在 该共享变量上由于调用 wait 系列方法而被挂起的线程。
// 创建资源 private static volatile Object resourceA = new Object(); public static void main(String[] args) throws InterruptedException { // 创建线程 Thread threadA = new Thread(new Runnable() { public void run() { // 获取resourceA共享资源的监视器锁 synchronized (resourceA) { System.out.println("threadA get resourceA lock"); try { System.out.println("threadA begin wait"); resourceA.wait(); System.out.println("threadA end wait"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }); // 创建线程 Thread threadB = new Thread(new Runnable() { public void run() { synchronized (resourceA) { System.out.println("threadB get resourceA lock"); try { System.out.println("threadB begin wait"); resourceA.wait(); System.out.println("threadB end wait"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }); // 创建线程 Thread threadC = new Thread(new Runnable() { public void run() { synchronized (resourceA) { System.out.println("threadC begin notify"); resourceA.notify(); } } }); // 启动线程 threadA.start(); threadB.start(); Thread.sleep(1000); threadC.start(); // 等待线程结束 threadA.join(); threadB.join(); threadC.join(); System.out.println("main over"); }
结果:
线程 C 再调用 resourceA 的 notify() 方法,从而唤醒线程 A 和线程 B 。 但是从执行结果来看,只有一个线程 A 被唤醒,线程 B 没有被唤醒。 如果把线程 C 调用的 notify() 方法改为调用 notifyAll() 方法,结果为 如果调用 notifyAll() 方法后 ,又有一个线程D调用了该共享变量的 wait() 方法而被放入阻塞集合,则该线程是不会被唤醒的。
1.4 等待线程执行终止的 join 方法
1.3中的等待通知方法是 Object 类中的方法 ,join 方法是 Thread 类直接提供的。 join 是无参且返回值为 void 的方法。public static void main(String[] args) throws InterruptedException { Thread threadOne = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("child threadOne over!"); } }); Thread threadTwo = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("child threadTwo over!"); } }); //启动子线程 threadOne.start(); threadTwo.start(); System.out.println("wait all child thread over!"); //等待子线程执行完毕,返回 threadOne.join(); threadTwo.join(); System.out.println("all child thread over!"); }
结果:
1.5 让线程睡眠的 sleep 方法
- 当一个执行中的线程调用了 Thread 的 sleep 方法后,调用线程会暂时让出指定时间的执行权,也就是在这期间不参与 CPU 的调度,但是该线程所拥有的监视器资源,比如锁还是持有不让出的。
- 指定的睡眠时间到了后该函数会正常返回,线程就处于就绪状态,然后参与 CPU 的调度,获取到 CPU 资源后就可以继续运行了。
- 如果在睡眠期间其他线程调用了该线程的 interrupt() 方法中断了该线程 , 则该线程会在调用 sleep 方法的地方抛出 InterruptedException 异常而返回。
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class SleepTest2 { // 创建一个独占锁 private static final Lock lock = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { // 创建线程A Thread threadA = new Thread(new Runnable() { public void run() { // 获取独占锁 lock.lock(); try { System.out.println("child threadA is in sleep"); Thread.sleep(10000); System.out.println("child threadA is in awaked"); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 释放锁 lock.unlock(); } } }); // 创建线程B Thread threadB = new Thread(new Runnable() { public void run() { // 获取独占锁 lock.lock(); try { System.out.println("child threadB is in sleep"); Thread.sleep(10000); System.out.println("child threadB is in awaked"); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 释放锁 lock.unlock(); } } }); // 启动线程 threadA.start(); threadB.start(); } }
结果:
当一个线程处于睡眠状态时,如果另外 一个线程中断了它,会在调用 sleep 方法处抛出异常。public static void main(String[] args) throws InterruptedException { //创建线程 Thread thread = new Thread(new Runnable() { public void run() { try { System.out.println("child thread is in sleep"); Thread.sleep(10000); System.out.println("child thread is in awaked"); } catch (InterruptedException e) { e.printStackTrace(); } } }); //启动线程 thread.start(); //主线程休眠2s Thread.sleep(2000); //主线程中断子线程 thread.interrupt(); }
结果:
1.6 让出 CPU 执行权的 yield 方法
- 当一个 线程调用了 Thread 类的静态方法 yield 时,是在告诉线程调度器自己占有的时间片中还没有使用完的部分自己不想使用了,这暗示线程调度器现在就可以进行下一轮的线程调度。但是线程调度器可以无条件忽略这个暗示。
- 当一个线程调用 yield 方法时,当前线程会让出 CPU 使用权,然后处于就绪状态,线 程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到 刚刚让出 CPU 的那个线程来获取 CPU 执行权。
结果: 三个线程分别在 i=0 时调用了 Thread.yield() 方法,所以三个线程自己的两行输出没有在一起,因为输出了第一行后当前线程让出了 CPU 执行权。public class YieldTest implements Runnable { YieldTest() { //创建并启动线程 Thread t = new Thread(this); t.start(); } public void run() { for (int i = 0; i < 5; i++) { //当i=0时让出CPU执行权,放弃时间片,进行下一轮调度 if ((i % 5) == 0) { System.out.println(Thread.currentThread() + "yield cpu..."); //当前线程让出CPU执行权,放弃时间片,进行下一轮调度 // Thread.yield(); } } System.out.println(Thread.currentThread() + " is over"); } public static void main(String[] args) { new YieldTest(); new YieldTest(); new YieldTest(); } }
sleep 与 yield 方法的区别
- 当线程调用 sleep 方法时,调用线程会被阻塞挂起指定的时间,在这期间线程调度器不会去调度该线程。
- 调用 yield 方法时,线程只是 让出自己剩余的时间片,并没有被阻塞挂起,而是处于就绪状态,线程调度器下一次调度时就有可能调度到当前线程执行。
1.7 线程中断
Java 中的线程中断是一种线程间的协作模式,通过 设置线程的中断标志并不能直接终止该线程的执行 ,而是 被中断的线程根据中断状态自行处理void interrupt() 方法 :
- 获取的是当前线程的中断标志,例如,当线程 A 运行时,线程 B 可以调用线程 A 的 interrupt() 方法来设置线程 A 的中断标志为 true 并立即返回。设置标志仅仅是设置标志,线程 A 实际并没有被中断,它会继续往下执行。
- 如果线程 A 因为调用了wait 系列函数、join 方法或者 sleep 方法而被阻塞挂起,这时候若线程 B 调用线程 A 的 interrupt() 方法,线程 A 会在调用这些方法的地方抛出 InterruptedException 异常而返回。
boolean isInterrupted() 方法 :
检测当前线程是否被中断,如果是返回 true ,否则返回 false。 传递false,不清除中断标志public boolean isInterrupted() { //传递false,说明不清除中断标志 return isInterrupted(false); }
boolean interrupted() 方法 :
- 检测当前线程是否被中断,如果是返回 true,否则返回 false。
- 与boolean isInterrupted()方法不同的是,该方法如果发现当前线程被中断,则会清除中断标志并返回为 false ,并且该方法是 static 方法(静态方法),可以通过 Thread 类直接调用Thread.interrupted()。
- 在 interrupted() 内部是获取当前线程的中断标志,而不是调用 interrupted() 方法的实例对象的中断标志。
public static boolean interrupted() { //清除中断标志 return currentThread().isInterrupted(true); }
interrupted()和 isinterrupted()的区别
- interrupted()方法是 static 方法(静态方法),可以通过 Thread 类直接调用Thread.interrupted()。isinterrupted()不行。
- 使用interrupted()当前线程被中断
while ( Thread.currentThread().interrupted() ) { System.out.println("interrupted()的结果为真,只是返回的中断标志是false"); }
- 则会清除中断标志并返回为 false ,isinterrupted()不会清除
public static void main(String[] args) throws InterruptedException { Thread threadOne = new Thread(new Runnable() { public void run() { for(;;){ } } }); //启动线程 threadOne.start(); //设置中断标志 threadOne.interrupt(); //获取中断标志 System.out.println("isInterrupted:" + threadOne.isInterrupted()); //获取中断标志并重置 interrupted()会清除中断标志并返回为 false System.out.println("isInterrupted:" + threadOne.interrupted()); //获取中断标志并重置 System.out.println("isInterrupted:" + Thread.interrupted()); //获取中断标志 System.out.println("isInterrupted:" + threadOne.isInterrupted()); threadOne.join(); System.out.println("main thread is over"); }
1.8 理解线程上下文切换
- 线程上下文切换时机有 : 当前线程的 CPU 时间片使用完处于就绪状态时,当前线程被其他线程中断时。
- 在切换线程上下文时需要保存当前线程的执行现场,当再次执行时根据保存的执行现场信息恢复执行现场。
- 进程-线程-协程-CSDN博客
1.9 线程死锁
public class DeadLockTest2 { // 创建资源 private static Object resourceA = new Object(); private static Object resourceB = new Object(); public static void main(String[] args) { // 创建线程A Thread threadA = new Thread(new Runnable() { public void run() { synchronized (resourceA) { System.out.println(Thread.currentThread() + " get ResourceA"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + "waiting get sourceB"); synchronized (resourceB) { System.out.println(Thread.currentThread() + "get esourceB"); } } } }); // 创建线程B Thread threadB = new Thread(new Runnable() { public void run() { synchronized (resourceB) { System.out.println(Thread.currentThread() + " get ResourceB"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + "waiting get esourceA"); synchronized (resourceA) { System.out.println(Thread.currentThread() + "get ResourceA"); } }; } }); // 启动线程 threadA.start(); threadB.start(); } }
结果:
- resourceA 和 resourceB 都是互斥资源,当线程 A 调用 synchronized(resourceA) 方法获取到 resourceA 上的监视器锁并释放前,线程 B 再调用 synchronized(resourceA) 方 法尝试获取该资源会被阻塞,只有线程 A 主动释放该锁,线程 B 才能获得,这满足了资 源互斥条件。
- 线程 A 首先通过 synchronized(resourceA) 方法获取到 resourceA 上的监视器锁资源, 然后通过 synchronized(resourceB) 方法等待获取 resourceB 上的监视器锁资源,这就构成了请求并持有条件。
- 线程 A 在获取 resourceA 上的监视器锁资源后,该资源不会被线程 B 掠夺走,只有线 程 A 自己主动释放 resourceA 资源时,它才会放弃对该资源的持有权,这构成了资源的不可剥夺条件。
- 线程 A 持有 objectA 资源并等待获取 objectB 资源,而线程 B 持有 objectB 资源并等待 objectA 资源,这构成了循环等待条件。所以线程 A 和线程 B 就进入了死锁状态。
LASTING ....... 明天继续更
守护线程与用户线程
1.11ThreadLocal
ThreadLocal 使用示例
ThreadLocal 的实现原理
ThreadLocal 不支持继承性
InheritableThreadLocal 类
标签:Thread,编程,System,并发,线程,println,new,out From: https://blog.csdn.net/liiilbb/article/details/143854710