1.Wait()—痴汉方法
wait()就是使线程停止运行,会释放对象锁。
- wait()方法会使当前线程调用该方法后进行等待,并且将该线程置入锁对象的等待队列中,直到接到通知或被中断为止。
- wait()方法只能在同步方法或同步代码块中调用,如果调用wait()时没有适当的锁,会抛出异常。
- wait()方法执行后,当前线程释放锁,其他线程可以竞争该锁。
例:
package www.java.test;
class MyThread implements Runnable{
private Object object = new Object();
@Override
public void run() {
synchronized (object){
System.out.println("wait方法开始...");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait方法结束...");
}
}
}
public class Test{
public static void main(String[] args) {
MyThread mt = new MyThread();
Thread thread = new Thread(mt);
thread.start();
}
}
我们可以运行这个程序,会发现它一直都没有停下来,在死等,如果没有调用notify()唤醒,它就不会停下来,但还有一种让它停下来的方法就是被中断。
例:
package www.java.test;
class MyThread implements Runnable{
private Object object = new Object();
@Override
public void run() {
synchronized (object){
System.out.println("wait方法开始...");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait方法结束...");
}
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
MyThread mt = new MyThread();
Thread thread = new Thread(mt);
thread.start();
Thread.sleep(2000);
thread.interrupt();
}
}
我们发现wait()方法通过中断也停止了。
wait()之后的线程继续执行有两种方法:
- 调用该对象的notify()方法唤醒等待线程
- 线程等待时调用interrupt()中断该进程
wait(long time):如果到了预计时间还未被唤醒,线程将继续执行。
例:
package www.java.test;
class MyThread implements Runnable{
private Object object = new Object();
@Override
public void run() {
synchronized (object){
System.out.println("wait方法开始...");
try {
object.wait(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait方法结束...");
}
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
MyThread mt = new MyThread();
Thread thread = new Thread(mt);
thread.start();
}
}
2. notify()
- notify()方法也必须在同步方法或同步代码块中调用,用来唤醒等待在该对象上的线程。如果有多个线程等待,则任意挑选一个线程唤醒。
- notify()方法执行后,唤醒线程不会立刻释放对象锁,要等待唤醒线程全部执行完毕后才释放对象锁。
证明notify()是在等待线程执行完之后才会释放对象锁:
package www.java.test;
class MyThread implements Runnable{
private Object object;
private boolean flag;
public MyThread(Object object, boolean flag) {
this.object = object;
this.flag = flag;
}
public void waitMethod(){
synchronized (object){
System.out.println("wait方法开始..."+Thread.currentThread().getName());
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait方法结束..."+Thread.currentThread().getName());
}
}
public void notifyMethod(){
synchronized (object){
System.out.println("notify方法开始..."+Thread.currentThread().getName());
object.notify();
System.out.println("notify方法结束..."+Thread.currentThread().getName());
}
}
@Override
public void run() {
if(flag){
waitMethod();
}else{
notifyMethod();
}
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
MyThread mt1 = new MyThread(object,true);
MyThread mt2 = new MyThread(object,false);
Thread waitThread = new Thread(mt1,"等待线程");
Thread notifyThread = new Thread(mt2,"唤醒线程");
waitThread.start();
Thread.sleep(1000);
notifyThread.start();
}
}
证明:多个线程等待时,是任意挑选一个线程唤醒
package www.java.test;
class MyThread implements Runnable{
private Object object;
private boolean flag;
public MyThread(Object object, boolean flag) {
this.object = object;
this.flag = flag;
}
public void waitMethod(){
synchronized (object){
System.out.println("wait方法开始..."+Thread.currentThread().getName());
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait方法结束..."+Thread.currentThread().getName());
}
}
public void notifyMethod(){
synchronized (object){
System.out.println("notify方法开始..."+Thread.currentThread().getName());
object.notify();
System.out.println("notify方法结束..."+Thread.currentThread().getName());
}
}
@Override
public void run() {
if(flag){
waitMethod();
}else{
notifyMethod();
}
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
MyThread mt1 = new MyThread(object,true);
MyThread mt2 = new MyThread(object,false);
for(int i = 0; i < 10; i++){
Thread threadi = new Thread(mt1,"等待线程"+i);
threadi.start();
}
Thread notifyThread = new Thread(mt2,"唤醒线程");
Thread.sleep(1000);
notifyThread.start();
}
}
wait方法结束的只有一个,其他9个都还在运行。
3. notifyAll()
唤醒所有在该对象上等待的线程。
例:
package www.java.test;
class MyThread implements Runnable{
private Object object;
private boolean flag;
public MyThread(Object object, boolean flag) {
this.object = object;
this.flag = flag;
}
public void waitMethod(){
synchronized (object){
System.out.println("wait方法开始..."+Thread.currentThread().getName());
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait方法结束..."+Thread.currentThread().getName());
}
}
public void notifyMethod(){
synchronized (object){
System.out.println("notify方法开始..."+Thread.currentThread().getName());
object.notifyAll();
System.out.println("notify方法结束..."+Thread.currentThread().getName());
}
}
@Override
public void run() {
if(flag){
waitMethod();
}else{
notifyMethod();
}
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
MyThread mt1 = new MyThread(object,true);
MyThread mt2 = new MyThread(object,false);
for(int i = 0; i < 10; i++){
Thread threadi = new Thread(mt1,"等待线程"+i);
threadi.start();
}
Thread notifyThread = new Thread(mt2,"唤醒线程");
Thread.sleep(1000);
notifyThread.start();
}
}
4. 线程阻塞的几种情况
- 调用sleep()方法,主动放弃占有的cpu,不会释放对象锁
- 调用阻塞式IO方法(read()、write()),在该方法返回前,线程阻塞
- 线程试图获取一个monitor,但该monitor被其他线程所持有,导致阻塞
- 线程等待某个通知,即调用wait(),释放对象锁
- 调用线程suspend(),将线程挂起,容易导致死锁,已被废弃
5.monitor的两个队列
同步队列中存放了因为竞争monitor失败导致阻塞的线程,这些线程等待CPU调度再次竞争锁。
等待队列存放因为调用wait()导致线程等待的线程,唤醒后进入同步队列竞争锁。
我们先来看一个简单的生产者消费者模型:
在看消费者模型前,我们先来看看这段代码:
package www.java.test;
class Goods{
private String goodsName;
private int count;
//生产商品方法
public synchronized void set(String goodsName){
this.goodsName = goodsName;
this.count++;
System.out.println(Thread.currentThread().getName()+"生产"+goodsName+toString());
}
//消费商品方法
public synchronized void get(){
this.count--;
System.out.println(Thread.currentThread().getName()+"消费"+goodsName+toString());
}
@Override
public String toString() {
return "Goods{" +
"goodsName='" + goodsName + '\'' +
", count=" + count +
'}';
}
}
//消费者线程
class Consumer implements Runnable{
private Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
goods.get();
}
}
//生产者线程
class Producer implements Runnable{
private Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
goods.set("杨树林口红");
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
Goods goods = new Goods();
Thread consumerThread = new Thread(new Consumer(goods),"消费者线程");
Thread producerThread = new Thread(new Producer(goods),"生产者线程");
consumerThread.start();
Thread.sleep(1000);
producerThread.start();
}
}
这段代码有什么不对吗?它不应该也是生产者消费者模型吗?
不是的,这个是先调用了消费者线程,可是这时还没有生产,所以这样是错误的。
再来看看下边这段代码,只要加上wait()、notify(),就算是先启动消费者线程也不会出错。
package www.java.test;
class Goods{
private String goodsName;
private int count;
//生产商品方法
public synchronized void set(String goodsName){
if(count > 0){
System.out.println("还有库存,等会再生产");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.goodsName = goodsName;
this.count++;
System.out.println(Thread.currentThread().getName()+"生产"+goodsName+toString());
notify();
}
//消费商品方法
public synchronized void get(){
if(count == 0){
System.out.println("目前没有产品,等会再来");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.count--;
System.out.println(Thread.currentThread().getName()+"消费"+goodsName+toString());
notify();
}
@Override
public String toString() {
return "Goods{" +
"goodsName='" + goodsName + '\'' +
", count=" + count +
'}';
}
}
//消费者线程
class Consumer implements Runnable{
private Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
goods.get();
}
}
//生产者线程
class Producer implements Runnable{
private Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
goods.set("杨树林口红");
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
Goods goods = new Goods();
Thread consumerThread = new Thread(new Consumer(goods),"消费者线程");
Thread producerThread = new Thread(new Producer(goods),"生产者线程");
producerThread.start();
Thread.sleep(1000);
consumerThread.start();
}
}
下面来看看多生产者多消费者模型的代码:
package www.java.test;
import java.util.ArrayList;
import java.util.List;
class Goods{
private String goodsName;
private int count;
public int getCount() {
return count;
}
//生产商品方法
public synchronized void set(String goodsName) throws Exception {
Thread.sleep(1000);
this.goodsName = goodsName;
this.count++;
System.out.println(Thread.currentThread().getName()+"生产"+goodsName+toString());
notifyAll();
}
//消费商品方法
public synchronized void get() throws InterruptedException {
while(count == 0){
System.out.println("目前没有产品,等会再来");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Thread.sleep(1000);
this.count--;
System.out.println(Thread.currentThread().getName()+"消费"+goodsName+toString());
notifyAll();
}
@Override
public String toString() {
return "Goods{" +
"goodsName='" + goodsName + '\'' +
", count=" + count +
'}';
}
}
//消费者线程
class Consumer implements Runnable {
private Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while(true){
try {
goods.get();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//生产者线程
class Producer implements Runnable{
private Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while(this.goods.getCount() < 10){
try {
goods.set("杨树林口红");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
Goods goods = new Goods();
//存储多个生产、消费者线程
List<Thread> list = new ArrayList<>();
//10个消费者线程
for(int i = 0; i < 10; i++){
Thread thread = new Thread(new Consumer(goods),"消费者线程"+i);
list.add(thread);
}
//5个生产者线程
for(int i = 0; i < 5; i++){
Thread thread = new Thread(new Producer(goods),"生产者线程"+i);
list.add(thread);
}
for(Thread thread : list){
thread.start();
}
}
}
这个代码是如何对单生产者消费者模型的代码做修改的呢?
在消费产品的方法里if(count == 0)要改为while(count == 0)
,这是为什么呢?
因为这里有多个生产者线程,如果只判断一个线程的count是否为0,但另外几个线程还有产品,这时是不需要生产的,可以继续消费,所以需要用while来判断,直到所有线程的count都不为0,才需要等待生产者生产。
还有就是要对生产做修改,它不可能一直生产,总要有个度,但是修改生产方法呢还是修改生产者线程里的run()方法呢?
如果我们修改生产方法,让它里边加一个while(count > 10){wait()}
,当然这样也可以,但我们就会发现,这时是同一个线程在生产产品,直到count大于10,就等待,这显然是有问题的,没有体现出生产者交替的现象,这是因为在run()函数里是while(true){goods.set("杨树林");}
,当一个线程进入run()之后是出不来的(set()方法是由synchronized所修饰的,一旦一个线程进去就会获得锁,但是死循环,锁不会被释放,所以其他线程无法生产),所以一直只有那一个线程在生产。所以我们需要改的是run()方法里的代码,这样就可以出现多生产者线程交替生产的现象了。