2.4、生产者消费者问题
sleep和wait的区别
1、类不同
sleep是Thread类中的方法,wait是Object中的方法。
2、会不会释放资源
sleep不会释放锁???
wait会释放锁。
3、使用范围不同
sleep是Thread的一个静态方法,在哪里都能够调用。建议通过TimeUnit枚举类来调用。
TimeUnit.SECONDS.sleep(3);//休眠三秒
wait和notify是一组,要一起使用。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yCnb3iCT-1583658683893)(C:\Users\Ant\AppData\Roaming\Typora\typora-user-images\1583636043409.png)]
(该截图内容来源于:https://www.cnblogs.com/moongeek/p/7631447.html)
- 需求:有两个线程,有一个初始值为0,实现两个线程交替执行,对变量+1,-1,交替10次。
public class Demo06 {
public static void main(String[] args) {
// 创建资源类
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
}
}
// 线程之间的通信: 判断 执行 通知
class Data {
private int number = 0;
public synchronized void increment() throws InterruptedException {
if (number != 0) {//判断
this.wait();
}
// 执行
System.out.println(Thread.currentThread().getName() + "\t" + number++);
// 通知
this.notifyAll();//唤醒所有的线程
}
public synchronized void decrement() throws InterruptedException {
if (number == 0) {//判断
this.wait();
}
// 执行
System.out.println(Thread.currentThread().getName() + "\t" + number--);
// 通知
this.notifyAll();
}
}
当有4个线程时,if判断就有问题了,notify唤醒线程后,线程会接着上次的执行继续向下执行,如果条件判断用if,则执行过wait方法的线程被唤醒后,if条件就执行完了,就失去了线程判断的意义,所以条件判断应该用while。
public class Demo06 {
public static void main(String[] args) {
// 创建资源类
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
// 线程之间的通信: 判断 执行 通知
class Data {
private int number = 0;
public synchronized void increment() throws InterruptedException {
while (number != 0) {//判断
this.wait();
}
// 执行
System.out.println(Thread.currentThread().getName() + "\t" + number++);
// 通知
this.notifyAll();//唤醒所有的线程
}
public synchronized void decrement() throws InterruptedException {
while (number == 0) {//判断
this.wait();
}
// 执行
System.out.println(Thread.currentThread().getName() + "\t" + number--);
// 通知
this.notifyAll();
}
}
以上的唤醒方式都不能精准的唤醒指定线程。
Condition
- 需求:A、B、C三个线程各执行10次方法,A线程执行方法p5,接着B线程执行方法p10,最后C线程执行方法p15,依次循环。
public class Demo07 {
public static void main(String[] args) {
// 创建资源类
Data02 data = new Data02();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.p5();
}
}, "A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.p10();
}
}, "B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.p15();
}
}, "C").start();
}
}
// 线程之间的通信: 判断 执行 通知
class Data02 {
private int status = 1;// A1 B2 C3
private Lock lock = new ReentrantLock();// 可重入锁
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void p5() {
lock.lock();
try {
// 判断
while (status != 1) {
condition1.await();
}
// 执行
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
// 通知
status = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void p10() {
lock.lock();
try {
// 判断
while ( status != 2) {
condition2.await();
}
// 执行
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
// 通知
status = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void p15() {
lock.lock();
try {
// 判断
while (status != 3) {
condition3.await();
}
// 执行
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
// 通知
status = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
Lock锁中,等待和唤醒要用Lock.newCondition()
返回的Condition方法中的await和signal。如果需要精准控制每个线程的唤醒,则需要给每个线程都创建一个Condition。
2.5、8锁现象
问题1:同一资源对象有两个被synchronized修饰的方法,在不同的线程中,哪个先执行?
public class LockDemo01 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> {
phone.sendMsg();
}, "A").start();
TimeUnit.SECONDS.sleep(2);
new Thread(()->{
phone.sendEmail();
}, "B").start();
TimeUnit.SECONDS.sleep(2);
new Thread(() -> {
phone.sendWinxin();
}, "C").start();
}
}
class Phone {
public synchronized void sendMsg() {
System.out.println(Thread.currentThread().getName() + "\t sendMsg");
}
public synchronized void sendEmail() {
System.out.println(Thread.currentThread().getName() + "\t sendEmail");
}
public synchronized void sendWinxin() {
System.out.println(Thread.currentThread().getName() + "\t sendWinxin");
}
}
结果:sendMsg先执行。
结论:被synchronized修饰的方式,锁的对象是方法的调用者,而每个线程中方法的调用对象是同一个,所以先调用的方法先执行。
问题2:在第一题的基础上,sendMsg方法中先休眠3秒,则哪个方法先执行?
public class LockDemo02 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> {
phone.sendMsg();
}, "A").start();
TimeUnit.SECONDS.sleep(2);
new Thread(()->{
phone.sendEmail();
}, "B").start();
}
}
class Phone02 {
public synchronized void sendMsg() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "\t sendMsg");
}
public synchronized void sendEmail() {
System.out.println(Thread.currentThread().getName() + "\t sendEmail");
}
}
结果:sendMsg先执行。
结论:锁的对象是同一个,哪个先调用就先执行哪个。
问题3:同一个资源对象中,一个普通方法,一个synchronized修饰的方法,哪个先执行?
public class LockDemo03 {
public static void main(String[] args) throws InterruptedException {
Phone03 phone = new Phone03();
new Thread(() -> {
try {
phone.sendMsg();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
TimeUnit.SECONDS.sleep(2);
new Thread(()->{
phone.hello();
}, "B").start();
}
}
class Phone03 {
public synchronized void sendMsg() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "\t sendMsg");
}
public void hello() {
System.out.println(Thread.currentThread().getName() + "\t hell0");
}
}
结果:sendMsg中将休眠三秒的代码屏蔽,则先执行sendMsg方法;如果不屏蔽,则先执行hello。
结论:没有synchronized修饰的方式不是同步方法,不受锁的影响。
问题4:两个资源对象,分别调用不同的synchronized方法,哪个先执行?
public class LockDemo04 {
public static void main(String[] args) throws InterruptedException {
Phone04 phone1 = new Phone04();
Phone04 phone2 = new Phone04();
new Thread(() -> {
try {
phone1.sendMsg();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
TimeUnit.SECONDS.sleep(2);
new Thread(()->{
phone2.sendEmail();
}, "B").start();
}
}
class Phone04 {
public synchronized void sendMsg() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "\t sendMsg");
}
public synchronized void sendEmail() {
System.out.println(Thread.currentThread().getName() + "\t sendEmail");
}
}
结果:sendMsg中将休眠三秒的代码屏蔽,则先执行sendMsg方法;如果不屏蔽,则先执行sendEmail方法。
结论:synchronized修饰的方式,锁的对象是调用者,两个对象调用方法就是两个锁。
问题5:一个资源对象中,有两个静态的synchronized方法,哪个先执行?
public class LockDemo05 {
public static void main(String[] args) throws InterruptedException {
Phone05 phone = new Phone05();
new Thread(() -> {
try {
phone.sendMsg();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
TimeUnit.SECONDS.sleep(2);
new Thread(()->{
phone.sendEmail();
}, "B").start();
}
}
class Phone05 {
public static synchronized void sendMsg() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "\t sendMsg");
}
public static synchronized void sendEmail() {
System.out.println(Thread.currentThread().getName() + "\t sendEmail");
}
}
结果:sendMsg先执行。
结论:用static修饰的方法,锁的对象是Class模板对象,这个是全局唯一的,所以先调用先执行。
问题6:两个资源对象,两个静态方法,哪个先执行?
public class LockDemo06 {
public static void main(String[] args) throws InterruptedException {
Phone06 phone1 = new Phone06();
Phone06 phone2 = new Phone06();
new Thread(() -> {
try {
phone1.sendMsg();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
TimeUnit.SECONDS.sleep(2);
new Thread(()->{
phone2.sendEmail();
}, "B").start();
}
}
class Phone06 {
public static synchronized void sendMsg() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "\t sendMsg");
}
public static synchronized void sendEmail() {
System.out.println(Thread.currentThread().getName() + "\t sendEmail");
}
}
结果:sendMsg先执行
结论:用static修饰的方法,锁的对象是Class模板对象,这个是全局唯一的,所以先调用先执行。
问题7:一个普通同步方法,一个静态同步方法,同一个资源对象,哪个先执行?
public class LockDemo07 {
public static void main(String[] args) throws InterruptedException {
Phone07 phone = new Phone07();
new Thread(() -> {
try {
phone.sendMsg();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
TimeUnit.SECONDS.sleep(2);
new Thread(()->{
phone.sendEmail();
}, "B").start();
}
}
class Phone07 {
public static synchronized void sendMsg() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "\t sendMsg");
}
public synchronized void sendEmail() {
System.out.println(Thread.currentThread().getName() + "\t sendEmail");
}
}
结果:sendMsg中将休眠三秒的代码屏蔽,则先执行sendMsg方法;如果不屏蔽,则先执行sendEmail方法。
结论:静态的同步方法锁的对象是Class对象,普通同步方法锁的对象是调用者,这里是两个锁。
问题8:一个普通同步方法,一个静态同步方法,两个资源对象,哪个先执行?
public class LockDemo08 {
public static void main(String[] args) throws InterruptedException {
Phone08 phone1 = new Phone08();
Phone08 phone2 = new Phone08();
new Thread(() -> {
try {
phone1.sendMsg();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
TimeUnit.SECONDS.sleep(2);
new Thread(()->{
phone2.sendEmail();
}, "B").start();
}
}
class Phone08 {
public static synchronized void sendMsg() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "\t sendMsg");
}
public synchronized void sendEmail() {
System.out.println(Thread.currentThread().getName() + "\t sendEmail");
}
}
结果:sendMsg中将休眠三秒的代码屏蔽,则先执行sendMsg方法;如果不屏蔽,则先执行sendEmail方法。
结论:静态的同步方法锁的对象是Class对象,普通同步方法锁的对象是调用者,这里是两个锁。