一、概念解释

1. 进入阻塞:

有时我们想让一个线程或多个线程暂时去休息一下,可以使用 wait(),使线程进入到阻塞状态,等到后面用到它时,再使用notify()、notifyAll() 唤醒它,线程被唤醒后,会等待CPU调度。不过需要注意的是:在执行 wait() 方法前必须先拿到这个对象的monitor锁。

2. 线程阻塞后,通常有以下四种方式唤醒

  • 另一个线程调用这个对象的notify()方法且刚好被唤醒的是本线程

  • 另一个线程调用这个对象的notify()方法

  • 过了waiting timeout()规定的超时时间,如果传入()就是永久等待

  • 线程自身调用了interrupt()

3. notify 与 notifyAll :

  • notify():唤醒单个正在等待monitor锁的线程,如果有多个正在等待monitor的线程,只会选取一个唤醒,具体唤醒那一个是任意的,不确定的,Java对此并没有一个严格的规范,JVM内部可以拥有自己的实现。
  • notifyAll():一次性把所有等待的线程唤醒,至于哪一个会获得monitor锁取决于cpu调度。

4. Monitor监视器锁原理

任何一个对象都有一个Monitor与之关联,当且一个Monitor被持有后,它将处于锁定状态。Synchronized在JVM里的实现都是 基于进入和退出Monitor对象来实现方法同步代码块同步

5. wait() 遇到 interrupt() 时:

假设线程已经执行了 wait() 方法,那么在此期间如果被中断了,它会和之前一样抛出InterruptException,并且释放掉目前已获得的monitor。

二、wait()和notify()、notifyAll()

1. wait()和notify()的基本用法

/**
* 描述: 展示wait和notify的基本用法 1. 研究代码执行顺序 2. 证明wait释放锁
*/
public class Wait { public static Object object = new Object(); static class Thread1 extends Thread { @Override
public void run() {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "开始执行了");
try {
object.wait(); //进入阻塞状态会释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁。");
}
}
} static class Thread2 extends Thread { @Override
public void run() {
synchronized (object) {
object.notify(); //唤醒正在等待这把锁的单个线程
System.out.println("线程" + Thread.currentThread().getName() + "调用了notify()");
}
}
} public static void main(String[] args) throws InterruptedException {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
Thread.sleep(200);
thread2.start();
}
}

打印结果

上面的代码表示,线程 A 在使用 wait() 方法后会进入阻塞状态并释放object的锁,然后另一个线程 B 会获取到该object的锁,在执行 notify() 方法后,会唤醒等待这把锁的 线程 A ,然后线程 A 继续执行!

2. notify()和notifyAll()的基本用法

(1)notifyAll()唤醒在等待某把锁的全部线程

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
* 描述: 3个线程,线程1和线程2首先被阻塞,线程3唤醒它们。notify, notifyAll。 start先执行不代表线程先启动。
*/
public class WaitNotifyAll implements Runnable { private static final Object resourceA = new Object(); public static void main(String[] args) throws InterruptedException {
Runnable r = new WaitNotifyAll();
Thread threadA = new Thread(r);
Thread threadB = new Thread(r);
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
resourceA.notifyAll();
// resourceA.notify();
System.out.println("ThreadC notified.");
}
}
});
threadA.start();
threadB.start();
Thread.sleep(200);
threadC.start();
}
@Override
public void run() {
synchronized (resourceA) {
System.out.println(Thread.currentThread().getName()+" got resourceA lock.");
try {
System.out.println(Thread.currentThread().getName()+" waits to start.");
resourceA.wait();
System.out.println(Thread.currentThread().getName()+"'s waiting to end.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

使用notifyAll()的打印结果:

线程1和线程2首先调用 wait() 进入阻塞,线程3使用 notifyAll 唤醒它们,然后线程1和线程2都会被唤醒继续执行,但是不能保证这两个线程哪个先执行。

(2)notify()唤醒在等待某把锁的一个线程

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
* 描述: 3个线程,线程1和线程2首先被阻塞,线程3唤醒它们。notify, notifyAll。 start先执行不代表线程先启动。
*/
public class WaitNotifyAll implements Runnable { private static final Object resourceA = new Object(); public static void main(String[] args) throws InterruptedException {
Runnable r = new WaitNotifyAll();
Thread threadA = new Thread(r);
Thread threadB = new Thread(r);
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
// resourceA.notifyAll();
resourceA.notify();
System.out.println("ThreadC notified.");
}
}
});
threadA.start();
threadB.start();
Thread.sleep(200);
threadC.start();
}
@Override
public void run() {
synchronized (resourceA) {
System.out.println(Thread.currentThread().getName()+" got resourceA lock.");
try {
System.out.println(Thread.currentThread().getName()+" waits to start.");
resourceA.wait();
System.out.println(Thread.currentThread().getName()+"'s waiting to end.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

使用notify()打印结果

由于是调用的 notify() 方法,所以只会唤醒等待这把锁的其中一个线程,但是具体会唤醒哪一个也不确定。

3. wait只释放当前调用者对象的那把锁

执行wait方法一定是一个Object对象,对象和 Monitor 监视器锁绑定,一个对象执行wait()就会释放掉该对象的锁,不会影响到其他对象的锁,每个对象的锁之间是独立的。

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
* 描述: 证明wait只释放当前的那把锁
*/
public class WaitNotifyReleaseOwnMonitor { private static volatile Object resourceA = new Object();
private static volatile Object resourceB = new Object(); public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println("ThreadA got resourceA lock.");
synchronized (resourceB) {
System.out.println("ThreadA got resourceB lock.");
try {
System.out.println("ThreadA releases resourceA lock.");
resourceA.wait(); } catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}); Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resourceA) {
System.out.println("ThreadB got resourceA lock.");
System.out.println("ThreadB tries to resourceB lock."); synchronized (resourceB) {
System.out.println("ThreadB got resourceB lock.");
}
}
}
}); thread1.start();
thread2.start();
}
}

打印结果:

resourceB 的锁一直在 ThreadA手中,没有被释放,所以线程ThreadB会一直等待中。

4. wait()、notify()和notifyAll() 引起的线程状态的特殊转换

上图是《线程的六种状态》一文中,线程的六种状态之间的正常转换轨迹,但是 wait()/notify()、notifyAll() 方法会导致状态之间的特殊转换:

比如,线程A从 Object.wait() 状态刚被唤醒时,通常不能立刻抢到 monitor 锁,会先等待 CPU 调度,这时的状态转换是由 Waiting 进入Blocked状态,等抢到锁后再转换到Runnable状态,但是 wait() 时如果发生异常,会直接跳到终止Terminated状态,即从Waiting直接到Terminated。

三、sleep方法详解

作用:只想让线程在预期的时间执行,其他时间不要占用CPU资源

特点:sleep方法可以让线程进入Timed_Waiting状态,并且不占用CPU资源,但是不释放锁(包括synchronize和lock),直到规定时间后再执行,休眠期间如果被中断,会抛出异常并清除中断标志。

1. sleep不释放锁

(1) sleep不释放synchronized的Monitor

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
* 展示线程sleep的时候不释放synchronized的monitor,等sleep时间到了以后,正常结束后才释放锁
*/
public class SleepDontReleaseMonitor implements Runnable { public static void main(String[] args) {
SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
new Thread(sleepDontReleaseMonitor).start();
new Thread(sleepDontReleaseMonitor).start();
} @Override
public void run() {
syn();
} private synchronized void syn() {
System.out.println("线程" + Thread.currentThread().getName() + "获取到了monitor。");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "退出了同步代码块");
}
}

执行结果:

sleep() 方法不会释放锁,等到sleep的指定时间一过,会继续执行,然后该线程的全部代码执行结束,才会释放锁,其他线程继续执行。

(2)sleep不释放lock

package threadcoreknowledge.threadobjectclasscommonmethods;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; /**
* 描述: 演示sleep不释放lock(lock需要手动释放)
*/
public class SleepDontReleaseLock implements Runnable { private static final Lock lock = new ReentrantLock(); @Override
public void run() {
lock.lock();
System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁");
try {
Thread.sleep(5000);
System.out.println("线程" + Thread.currentThread().getName() + "已经苏醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} public static void main(String[] args) {
SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
new Thread(sleepDontReleaseLock).start();
new Thread(sleepDontReleaseLock).start();
}
}

执行结果:

线程1在获取到锁之后,进入sleep()休眠中,但是并没有释放锁,直到 lock.unlock() 之后,所才会被释放,其他线程继续执行!

2. sleep响应中断

sleep() 有两种写法:TimeUnit.SECONDS.sleep(1) 、Thread.sleep(1000);

package threadcoreknowledge.threadobjectclasscommonmethods;

import java.util.Date;
import java.util.concurrent.TimeUnit; /**
* 描述: 每个1秒钟输出当前时间,被中断,观察。
* Thread.sleep()
* TimeUnit.SECONDS.sleep() 方便开发人员对时间的把控,不需要通过毫秒换算
*/
public class SleepInterrupted implements Runnable{ public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new SleepInterrupted());
thread.start();
Thread.sleep(6500);
thread.interrupt();
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(new Date());
try {
// TimeUnit.HOURS.sleep(3);
// TimeUnit.MINUTES.sleep(25);
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
System.out.println("我被中断了!");
e.printStackTrace();
}
}
}
}

执行结果:

在 sleep() 期间,如果当前线程调用了 interrupt() 方法,会抛出 InterruptedException 异常来响应中断!

四、join()

作用:因为新的线程加入了我们,所以我们要等他执行完再出发

用法:主线程等待执行 join() 方法加入的子线程

基于 join() 封装的工具类:CountDownLatch 或 CyclicBarrier 类

join 期间主线程处于什么状态:WAITING 状态

代码演示:演示 join,注意语句输出顺序,会变化。

public class Join {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "开始执行");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
});
Thread thread2 = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "开始执行");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
}); thread.start();
thread2.start();
System.out.println("开始等待子线程运行完毕");
thread.join();
thread2.join();
System.out.println("所有子线程执行完毕");
}
}

打印结果:

由于使用了join() 方法,主线程会等到子线程都执行完毕,才会继续执行 System.out.println("所有子线程执行完毕");

        thread.start();
thread2.start();
System.out.println("开始等待子线程运行完毕");
// thread.join();
// thread2.join();
System.out.println("所有子线程执行完毕");

如果注释掉 join() 方法,则主线程会先打印出“所有子线程执行完毕”,之后子线程会继续执行,并陆续打印。如下所示

2. join遇到中断

/******
* Join的中断演示
*/
public class JoinInterrupted {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
//中断主线程
mainThread.interrupt();
Thread.sleep(5000);
System.out.println("Thread1 sleep 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程执行完毕");
}
}); thread1.start();
System.out.println("等待子线程运行完毕");
try {
thread1.join();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"主线程被中断");
e.printStackTrace();
}
System.out.println("主线程等待子线程执行完毕");
}
}

主线程被 interrupt 中断后,直接跳到最后一句 System.out.println("主线程等待子线程执行完毕") ,但是子线程依然在运行,最后打印如下:

明显主线程运行被中断之后,子线程依然在继续执行,“主线程等待子线程执行完毕” 在 “子线程执行完毕” 之前打印,join() 的效果失效。如果要保证 join () 期间被中断时,主线程和子线程的一致性,也需要也将子线程中断掉,这就 需要在 catch 到 InterruptedException之后,也要将中断传给子线程:

public class JoinInterrupted {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
//中断 主线程
mainThread.interrupt();
Thread.sleep(5000);
System.out.println("Thread1 执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("子线程中断");
}
}
}); thread1.start();
System.out.println("等待子线程运行完毕");
try {
thread1.join();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"主线程被中断");
e.printStackTrace();
//中断 子线程
thread1.interrupt();
}
System.out.println("子线程运行完毕");
}
}

打印结果:

主线程和子线程的 catch 中的逻辑是并行执行的,所以不能保证哪个最先停止运行。

3.在 join()期间,线程是什么状态

主线程是 WAITING 状态,而调用 join() 方法的子线程是 Runnable 状态。

代码演示:

/******
* 先join,再mainThread.getState();
* 通过debug,看线程状态
*/
public class JoinThreadState {
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread(); Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
//查看主线程状态
System.out.println(mainThread.getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}); thread.start();
System.out.println("子线程启动");
thread.join();
System.out.println("子线程运行完毕");
}
}

打印结果:

3. join 源码

如果调用了wait(),那么他不应该被notify()唤醒吗?但是没有在join()方法里面看到notify????

  • Thread类是 Object的一个特殊的子类,每个Thread实例运行完毕之后,都会执行一次notify()的操作!!!

从 join() 的源码可以看出,thread.join() 相当于以下代码:

//等价代码实现join()
synchronized (thread){
thread.wait();
}

五、yield

作用:释放当前线程的 CPU 时间片;让当前处于运行状态的线程退回到可运行状态,让出抢占资源的机会,但是它的状态依然是 Runnable。

定位:JVM 不保证遵循yield的规则,如果cpu资源充足的话,可能yield不生效。和 join 相反,join 是别人插队进来了,yield 是线程让出位置。

yield 和 sleep 区别:yield只是暂时让出cpu,由于当前线程还是 Runnable,并没有阻塞,所以随时可能再次被调度;sleep期间,线程调度器认为它已经被阻塞了,不会去调度它。

六、课后测验:

1. 为什么wait/notify/notifyAll被定义在Object类中,而sleep定义在Thread类中

因为java中每个对象都有一把称之为monitor监控器的锁,每个对象头中有一个用来保存锁信息的位置,所以每个对象都可以上锁,这个锁是对象级别的,而非线程级别的,wait/notify/notifyAll也都是锁级别的操作,他们的锁属于对象,所以把他们定义在Object类中最合适,因为Object类是所有对象的父类。

而如果把 wait/notify/notifyAll 方法定义在Thread类中,会带来很大的局限性,比如一个线程可能持有多把锁,以便实现相互配合的复杂逻辑,假设此时wait方法定义到Thread类中,如何实现让一个线程持有多把锁呢?又如何明确线程等待的是那把锁呢?既然我们是让当前线程去等待某个对象的锁,自然应该通过操作对象来实现,而不是操作线程。

对于sleep为什么被定义在Thread中,我们只要从sleep方法的作用来看就知道了,sleep的作用是:让线程在预期的时间内执行,其他时候不要来占用CPU资源。从上面的话术中,便可以理解为sleep是属于线程级别的,它是为了让线程在限定的时间后去执行,由线程控制。

2. wait/notify和sleep方法的异同

相同点

1.他们都可以让线程阻塞

2.它们都可以响应interrupt中断:在等待的过程中如果收到中断信号,都可以进行响应,

并抛出InterruptedException

不同点

1.wait方法必须在synchronized保护的代码中使用,而sleep方法并没有这个要求

2.在同步代码中执行sleep方法时,并不会释放monitor锁,但执行wait方法时会主动释放monitor锁

3.sleep方法中要求必须定义一个时间,时间到期后会主动恢复,而对于没有time参数的 wait() 方法而言,意味着永久等待,直到被中断或者唤醒才能恢复,他并不会主动恢复.

4.wait/notify是Object方法,而sleep是Thread类的方法

3. wait方法是属于Object对象的,那调用Thread.wait会怎么样?

Thread也是个对象,这样调用也没有问题,但是Thread是个特殊的对象,线程退出的时候会自动执行notify,这样会是我们设计的流程受到干扰,所以我们一般不这么用。

4. 代码练习:wait/notify 实现生产者消费者模式

(1)为什么要使用生产者和消费者模式?

在线程的世界中,生产者就是生产一些数据,而消费者就是把这些数据消费使用,但是他们的速度很可能就是不一致的,有的时候是生产者快有的时候生产者慢而消费者快,就需要有个设计模式去解决这个问题,而不至于一个过快一个过慢,于是就诞生了生产者消费者模式,这个设计模式实际上把生产方和消费方进行了解耦,从而达到更加流畅的配合。

(2)能解决什么问题?

能解决生产过快消费不足或者生产不足消费过快的问题,而且能让生产方和消费方之间解耦。

(3)代码演示

定义一个数据容器: 用于存储生产出的数据,并定义该容器的两个方法:生产数据的put方法和消费数据的take方法。

put方法逻辑:在方法中使用 synchronized 代码块加同步锁,防止出现线程安全问题。在 synchronized 代码块中,如果storage队列满了,就调用 wait() 等待,不再生产数据。代码块的末尾加上notify()调用,表示每次 put 数据,都要唤醒一下消费者端的线程。

take方法逻辑:同样使用 synchronized 代码块,在代码块中,如果队列为空,就调用 wait() 等待,末尾也同样加上notify()调用,表示每次消费都唤醒一下生产端的线程。

为什么使用notify()调用:

  • 如果生产者生产过快时,storage会处于满的状态,这时候不再生产数据处于等待状态,消费者那边会消费数据,消费完最后会调用notify方法唤醒生产者继续生产数据。
  • 如果消费者消费过快时,storage会处于空的状态,这时候消费者这边检查到没有数据消费就处于等待状态,生产者那边生产出一条数据后会唤醒消费者继续消费数据。
// 数据容器:用于存储生产出的数据,并定义该容器的两个方法:生产数据的put方法和消费数据的take方法。
class EventStorage { private int maxSize;
private LinkedList<Date> storage; public EventStorage() {
maxSize = 10;
storage = new LinkedList<>();
} //生产产品
public synchronized void put() {
//如果满了
while (storage.size() == maxSize) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没满
storage.add(new Date());
System.out.println("仓库里有了" + storage.size() + "个产品。");
// 每次生产都执行一次唤醒
notify();
} //消费产品
public synchronized void take() {
//如果空了
while (storage.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没空
System.out.println("拿到了" + storage.poll() + ",现在仓库还剩下" + storage.size());
// 每次消费都执行一次唤醒
notify();
}
}
// 生产者类: 用于生产数据,可以看到,生产者中storage用于存储生产出的数据,run方法用于完成生产数据的任务。
class Producer implements Runnable { private EventStorage storage; public Producer(EventStorage storage) {
this.storage = storage;
} @Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.put();
}
}
}
// 消费者类: 用于消费数据
class Consumer implements Runnable { private EventStorage storage; public Consumer(EventStorage storage) {
this.storage = storage;
} @Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.take();
}
}
}
// 主类: 启动一个线程生产数据,另一个线程消费数据。
public class ProducerConsumerModel { public static void main(String[] args) {
EventStorage eventStorage = new EventStorage();
Producer producer = new Producer(eventStorage);
Consumer consumer = new Consumer(eventStorage);
new Thread(producer).start();
new Thread(consumer).start();
}
}

打印结果:

5. 代码练习:实现两个线程交替打印 0~100 的奇偶数

问题描述:有两个线程,一个线程只打印奇数,一个线程只打印偶数,而且要按照顺序打印,就是说要按照这样的方式打印:

偶线程:0
奇线程:1
偶线程:2
奇线程:3

(1)使用synchronized关键字实现

分析:

  • 创建两个线程,一个线程处理偶数,一个线程处理奇数
  • 两个线程之间通过synchronized进行同步,保证count++每次只有一个线程进行操作
  • 为什么两个线程能交替执行,这里很巧的是count从0123自增过程就是一个奇偶数交替的过程,实际上两个线程都是在不停的尝试(while循环)进入synchronized代码块,如果满足相对应的条件(偶数或是奇数)就打印输出。

代码展示:

public class WaitNotifyPrintOddEvenSyn {

    private static int count;

    private static final Object lock = new Object();

    /**
* 新建2个线程,第一个只处理偶数,第二个只处理奇数(用位运算);用synchronized来通信
*/
public static void main(String[] args) {
new Thread(() -> {
while (count < 100) {
synchronized (lock) {
if ((count & 1) == 0) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
}
}
}
}, "偶线程").start(); new Thread(() -> {
while (count < 100) {
synchronized (lock) {
if ((count & 1) == 1) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
}
}
}
}, "奇线程").start();
}
}

打印结果:

(2) 方案二:使用wait/notify关键字(推荐)

分析:

  • 偶数线程拿到锁打印输出同时count++,然后进行休眠,因为wait()方法的特性,休眠的同时会释放monitor锁,奇数线程就可以进来了,进来后打印输出,同时notify唤醒偶数线程继续下一轮,奇数线程往下执行wait方法休眠,就这样,偶数线程唤醒奇数线程,奇数线程唤醒偶数线程,直到满足count<100条件后,线程不再休眠,直接退出程序。
  • 这个要点一个在于wait/notify的等待唤醒机制,一个在于wait()方法的特性,休眠后会释放锁。
  • 这种方式和上面那种方式不同点在于,这种方式是被动唤醒的机制,而上面那个是线程不断重试的机制(一直while重试,直到满足条件就打印,有些浪费资源),很明显这种方式优于上面那种!
public class WaitNotifyPrintOddEveWait {

    private static int count = 0;

    private static final Object lock = new Object();

    public static void main(String[] args) {
new Thread(new TurningRunner(), "偶线程").start();
new Thread(new TurningRunner(), "奇线程").start();
} /**
* 1. 拿到锁,立刻打印
* 2. 打印完,唤醒其他线程,自己就休眠
*/
static class TurningRunner implements Runnable { @Override
public void run() {
while (count < 100) {
synchronized (lock) {
//拿到锁就打印
System.out.println(Thread.currentThread().getName() + ":" + count++);
lock.notify();
if (count < 100) {
try {
//如果任务还没结束,就让出当前的锁,并休眠
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}

文章来源:wait,notify,notifyAll,sleep,join等线程方法的全方位演练

个人微信:CaiBaoDeCai

微信公众号名称:Java知者

微信公众号 ID: JavaZhiZhe

谢谢关注!

wait,notify,notifyAll,sleep,join等线程方法的全方位演练的更多相关文章

  1. 零基础学习java------day18------properties集合,多线程(线程和进程,多线程的实现,线程中的方法,线程的声明周期,线程安全问题,wait/notify.notifyAll,死锁,线程池),

    1.Properties集合 1.1 概述: Properties类表示了一个持久的属性集.Properties可保存在流中或从流中加载.属性列表中每个键及其对应值都是一个字符串 一个属性列表可包含另 ...

  2. wait(),notify(),notifyAll()用来操作线程为什么定义在Object类中?

    这些方法存在于同步中: 使用这些方法必须标识同步所属的锁: 锁可以是任意对象,所以任意对象调用方法一定定义在Object类中. Condition是在java 1.5中才出现的,它用来替代传统的Obj ...

  3. java 并发——理解 wait / notify / notifyAll

    一.前言 前情简介: java 并发--内置锁 java 并发--线程 java 面试是否有被问到过,sleep 和 wait 方法的区别,关于这个问题其实不用多说,大多数人都能回答出最主要的两点区别 ...

  4. 使用Object的wait,notify,notifyAll做线程调度

    我们知道java中的所有类的祖先都是Object,Object类有四个个方法wait(),wait(long timeout),notify(),notifyAll(),这四个方法可以用来做线程的调度 ...

  5. wait() 与 notify/notifyAll()

    wait() 与 notify/notifyAll() 是Object类的方法 1. wait() 与notify/notifyAll方法必须在同步代码块中使用 在执行以上方法时,要先获得锁.那么怎么 ...

  6. -1-5 java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait(),notify(),notifyAll()等方法都定义在Object类中

     本文关键词: java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁  sleep()和wait()方法的区别 为什么wait( ...

  7. 线程sleep,wait,notify,join,yield方法解析

    线程的五种状态 线程从创建到销毁一般分为五种状态,如下图: 1) 新建 当用new关键字创建一个线程时,就是新建状态. 2) 就绪 调用了 start 方法之后,线程就进入了就绪阶段.此时,线程不会立 ...

  8. 并发编程——线程中sleep(),yield(),join(),wait(),notify(),notifyAll()区别

    前言 今天简单的讲一讲线程中sleep(),join(),yield(),wait(),notify(),notifyAll()这些方法的使用以及区别. 不过在讲这些方法之前,需要简单的介绍一下锁池和 ...

  9. 【转】wait,notify,notifyAll,join,yield,sleep的区别和联系

    1.  Thread.sleep(long) 和Thread.yield()都是Thread类的静态方法,在调用的时候都是Thread.sleep(long)/Thread.yield()的方式进行调 ...

  10. java ---线程wait/notify/sleep/yield/join

    一.线程的状态 Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态). New:新建状态,当线 ...

随机推荐

  1. python + BeautifulSoup + selenium 实现爬取中医智库的古籍分类的数据

    爬取内容为 该图片下的七个分类, 然后对应的每个种类的书本信息(摘要和目录) 效果为 代码如下 import requests from bs4 import BeautifulSoup import ...

  2. Rancher系列文章-Rancher v2.6使用脚本实现导入集群

    概述 最近在玩 Rancher, 先从最基本的功能玩起, 目前有几个已经搭建好的 K8S 集群, 需要批量导入, 发现官网已经有批量导入的文档了. 根据 Rancher v2.6 进行验证微调后总结经 ...

  3. scp上传文件No such file or directory 问题

        问题描述 window使用scp复制文件到linux时报 No such file or directory 错误 解决办法 本地复制到Linux,win环境必须用绝对路径 上传成功

  4. ACM-学习记录-DP-1

    DPL_1_A: Coin Changing Problem 每次均有两种选择,即选择当前的,即为在当前状态+1,否则维持原来的T[j+d[i]] #include<iostream> # ...

  5. MySQL--索引的数据结构

    1.为什么使用索引 索引是存储引擎用于快速找到数据记录的一种数据结构,就好比一本教科书的目录部分,通过目录中找到对应文章的页面,便可以快速定位到需要的文章,mysql中也是一样的道理,进行数据查找时, ...

  6. idea的上git的拉取推送

    下载好idea和git idea的下载破解查看https://www.cnblogs.com/badfisher/p/14709120.html git官网要求下载即可. 获取仓库路径点击复制. 在i ...

  7. [ElasticSearch]#Search Guard#sgadmin参数说明

    sgadmin.sh/sgadmin.bat是Elastic Search的开源安全组件Search Guard内的可执行脚本. 在修改SG组件的用户角色.权限.密码时,需使用到此脚本. 在修改完Se ...

  8. [Linux]常用命令之【systemctl/service/chkconfig/pstree】

    1 systemctl 1-0 systemctl 基本使用 systemctl start/stop/restart/status sshd systemctl enable/disable ssh ...

  9. 【Java SE】集合

    1.java集合框架 使用Array存储对象有一定的弊端.java集合就是一种容器,动态地存储多个对象,存储主要是内存层面的存储,不涉及到持久化的存储(txt,avi,数据库). ①一旦初始化好,数组 ...

  10. 深度学习--魔法类nn.Module

    深度学习--魔法类nn.Module 作用 pytorch 封装了一些基本的网络类,可以直接调用 好处: 可以直接调用现有的类 容器机制:self.net = nn.Sequential() 参数返回 ...