java多线程协作object.notify和object.wait

最近学习java多线程协作,发现网上很多文章写得不清晰,甚至是错误的。所以自己研究了一下多线程协作的写法,通过例子进行说明:三个线程,A线程输出6遍A,B线程输出6遍B,C线程输出6遍C,要求按照ABC的顺序轮流唤醒进行输出。做了两种实现。

 

第一种实现:

 

public class Printer implements Runnable {

	private String name;
	private Integer times;
	private Printer next;

	public Printer(String name, Integer times) {
		super();
		this.name = name;
		this.times = times;
	}

	public void setNext(Printer next) {
		this.next = next;
	}

	@Override
	public void run() {
		while (times > 0) {
			synchronized (next) {
				synchronized (this) {
					try {
						Thread.sleep(1000);
					} catch (Exception e) {
						e.printStackTrace();
					}
					System.out.println(name + times);
					times = times - 1;
					this.notify();// 释放this的同步锁,synchronized代码块执行完释放
				}
				try {
					next.wait();// 释放next的同步锁,休眠当前Thread
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
}

 

Junit测试类

 

public class ThreadTest {

	@Test
	public void test() throws InterruptedException {
		Printer printA = new Printer("A", 6);
		Printer printB = new Printer("B", 6);
		Printer printC = new Printer("C", 6);
		printA.setNext(printB);
		printB.setNext(printC);
		printC.setNext(printA);
		Thread threadA = new Thread(printA);
		Thread threadB = new Thread(printB);
		Thread threadC = new Thread(printC);
		threadA.start();
		Thread.sleep(10);//确保A线程先运行
		threadB.start();
		threadC.start();
		threadA.join();
		threadB.join();
		synchronized (printA) {
			printA.notify();//唤醒休眠的C线程,使C线程顺利结束
		}
		threadC.join();
	}

}

 

执行结果:

 

A6
B6
C6
A5
B5
C5
A4
B4
C4
A3
B3
C3
A2
B2
C2
A1
B1
C1

 

加上一些打印日志,方便查看程序的逻辑:

 

public class Printer implements Runnable {

	private String name;
	private Integer times;
	private Printer next;

	public Printer(String name, Integer times) {
		super();
		this.name = name;
		this.times = times;
	}

	public void setNext(Printer next) {
		this.next = next;
	}

	@Override
	public void run() {
		while (times > 0) {
			System.out.println(this.name +"想要获取"+ next.name +"锁"+ this.name +"锁");
			synchronized (next) {
				System.out.println(this.name + "成功获取" + next.name + "锁");
				synchronized (this) {
					System.out.println(this.name + "成功获取" + this.name + "锁");
					try {
						Thread.sleep(1000);
					} catch (Exception e) {
						e.printStackTrace();
					}
					System.out.println(name + times);
					times = times - 1;
					System.out.println(this.name + "释放" + this.name + "锁");
					this.notify();//释放this的同步锁,synchronized代码块执行完释放
				}
				try {
					System.out.println(this.name + "释放" + next.name + "锁");
					next.wait();//释放next的同步锁,休眠当前Thread
					System.out.println(this.name + "苏醒");
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		System.out.println(this.name + "结束");
	}
}

 

执行结果:

 

A想要获取B锁A锁
A成功获取B锁
A成功获取A锁
B想要获取C锁B锁
B成功获取C锁
C想要获取A锁C锁
A6
A释放A锁
A释放B锁
C成功获取A锁
B成功获取B锁
B6
B释放B锁
B释放C锁
A苏醒
C成功获取C锁
A想要获取B锁A锁
A成功获取B锁
C6
C释放C锁
C释放A锁
B苏醒
B想要获取C锁B锁
B成功获取C锁
A成功获取A锁
A5
A释放A锁
A释放B锁
C苏醒
B成功获取B锁
C想要获取A锁C锁
C成功获取A锁
B5
B释放B锁
B释放C锁
A苏醒
A想要获取B锁A锁
A成功获取B锁
C成功获取C锁
C5
C释放C锁
C释放A锁
B苏醒
A成功获取A锁
B想要获取C锁B锁
B成功获取C锁
A4
A释放A锁
A释放B锁
C苏醒
B成功获取B锁
C想要获取A锁C锁
C成功获取A锁
B4
B释放B锁
A苏醒
A想要获取B锁A锁
A成功获取B锁
B释放C锁
C成功获取C锁
C4
C释放C锁
C释放A锁
B苏醒
B想要获取C锁B锁
B成功获取C锁
A成功获取A锁
A3
A释放A锁
A释放B锁
C苏醒
B成功获取B锁
C想要获取A锁C锁
C成功获取A锁
B3
B释放B锁
B释放C锁
A苏醒
A想要获取B锁A锁
A成功获取B锁
C成功获取C锁
C3
C释放C锁
C释放A锁
B苏醒
B想要获取C锁B锁
B成功获取C锁
A成功获取A锁
A2
A释放A锁
A释放B锁
C苏醒
B成功获取B锁
C想要获取A锁C锁
C成功获取A锁
B2
B释放B锁
B释放C锁
A苏醒
A想要获取B锁A锁
A成功获取B锁
C成功获取C锁
C2
C释放C锁
C释放A锁
B苏醒
A成功获取A锁
B想要获取C锁B锁
B成功获取C锁
A1
A释放A锁
A释放B锁
C苏醒
B成功获取B锁
C想要获取A锁C锁
C成功获取A锁
B1
B释放B锁
A苏醒
A结束
B释放C锁
C成功获取C锁
C1
C释放C锁
C释放A锁
B苏醒
B结束
C苏醒
C结束

 

 

分析一下第一种实现:

notify和wait的基础知识就不赘述了,就重申一下重点:

 

  • 这两个方法必须写在synchronized的代码块中,因为必须获取object的对象锁之后才能执行该object的notify和wait方法。
  • notify方法无论写在synchronized的代码块中的任何一行都可以,因为notify方法在所属的synchronized代码块执行完成后才会执行。notify方法执行后会解锁该object,同时唤醒另一个正在等待该object的线程继续执行,同时本线程也会继续往下执行。
  • wait方法和notify方法不同之处在于,wait方法执行时,也会解锁该object,但是,是立刻解锁该object,而不是等到wait方法所属的synchronized代码块执行完成后才执行,wait方法执行后,在解锁该object的同时,object本身所属的线程进入休眠(或者说进入等待),然后需要该object再次执行notify方法或者notifyAll方法后,该线程才会被唤醒,代码才会继续往下执行。

 

第一种实现的设计思路就是:

 

假设A线程最先执行,A线程先获取B的对象锁,然后获取A的对象锁,两个锁同时获取之后,打印A,然后A线程解锁A的对象锁,唤醒正在等待A的对象锁的另一个线程(唤醒了线程C),然后A线程解锁B的对象锁,同时A线程进入休眠(或者说进入等待)。此时B线程早就已经获取了没人要的C对象锁,然后想要获取B对象锁,就能打印B了。B成功获取了A线程释放的B对象锁,打印B,打印之后B线程解锁B,同时唤醒了进入休眠的A线程,然后B线程解锁C,B线程进入休眠。C线程之前在被A线程唤醒的时候已经获取了A的对象锁,C线程此时又获取了B线程所释放的C对象锁,然后打印C,然后释放C锁,唤醒B线程,然后再释放了A锁,C线程进入休眠。A线程之前被B线程唤醒,已经获取了B锁,现在又获取了到了A锁,又可以打印第二遍A了,一轮ABC打印已经完成。

 

仔细查看运行结果就可以理解第一种实现的设计思路了。

synchronized (printA) {
	printA.notify();//唤醒休眠的C线程,使C线程顺利结束
}

可以将测试类中的这三行代码注释掉执行看看,方便你理解,为啥需要这么做。

 

吐槽:好像在说书一样,这是在导演家庭伦理剧吗

 

 

 

第二种实现:

 

public class Printer2 implements Runnable {

	private String name;
	private Integer times;
	private Thread current;
	private Thread next;

	public Printer2(String name, Integer times) {
		super();
		this.name = name;
		this.times = times;
	}

	public void setThread(Thread current, Thread next) {
		this.current = current;
		this.next = next;
	}

	@Override
	public void run() {
		while (times > 0) {
			synchronized (current) {
				synchronized (next) {
					System.out.println("start" + name);
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(name + times);
					times = times - 1;
					if (next.isAlive()) {
						next.notify();
					} else {
						next.start();
					}
				}
				try {
					current.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
}

 

Junit测试类:

 

public class ThreadTest {

	@Test
	public void test() throws InterruptedException {
		Printer2 printA = new Printer2("A", 6);
		Printer2 printB = new Printer2("B", 6);
		Printer2 printC = new Printer2("C", 6);
		Thread threadA = new Thread(printA);
		Thread threadB = new Thread(printB);
		Thread threadC = new Thread(printC);
		printA.setThread(threadA, threadB);
		printB.setThread(threadB, threadC);
		printC.setThread(threadC, threadA);
		threadA.start();
		Thread.sleep(100000);//让子弹飞一会
	}

}

 

执行结果:

 

startA
A6
startB
B6
startC
C6
startA
A5
startB
B5
startC
C5
startA
A4
startB
B4
startC
C4
startA
A3
startB
B3
startC
C3
startA
A2
startB
B2
startC
C2
startA
A1
startB
B1
startC
C1

 

第二种实现的思路:

 

通过Thread的notify和wait方法,同样是获取ThreadA的锁和ThreadB的锁,同时获取两个锁之后打印A,然后解锁ThreadB,唤醒ThreadB,解锁ThreadA,休眠ThreadA。

 

和实现一的不同之处在于,第一种先获取获取下一个打印object的锁,再获取自身object锁,然后打印,解锁自身,解锁下一个,同时本线程休眠。第二种先获取自身的Thread锁,然后获取下一个打印的Thread锁,然后打印,然后解锁并唤醒下一个Thread,然后解锁自身Thread锁并进入休眠。

 

两种实现其他一些不同的地方就不赘述了。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值