wait-notify模型面试题
一道面试题:
启动两个线程, 一个输出 1,3,5,7…99, 另一个输出 2,4,6,8…100 最后 STDOUT 中按序输出 1,2,3,4,5…100
错误实现1:
public class NotifyErrorTest {
private int i = 1;
Thread t1 = new Thread(){
@Override
public void run() {
while (true) {
synchronized (this) {
notify();
if (i <= 100) {
System.out.println(currentThread().getName() + ":" + i);
i++;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
};
Thread t2 = new Thread(){
@Override
public void run() {
while (true) {
synchronized (this) {
notify();
if (i <= 100) {
System.out.println(currentThread().getName() + ":" + i);
i++;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
};
public static void main(String[] args){
NotifyErrorTest test = new NotifyErrorTest();
test.t1.start();
test.t2.start();
}
}
结果:
Thread-0:1
Thread-1:1
打印出这两个后,线程就一直被挂起了。为什么会这样呢。
先不考虑这种难看的重复代码需不需要重构,本身代码就有问题,虽然看起来都用了this,但是其实两个this所表示的含义不同,我们两个线程里面加上如下代码
System.out.println(this.getClass());
会发现打印出
class pers.marscheng.thread.NotifyErrorTest$1
class pers.marscheng.thread.NotifyErrorTest$2
原来两个this不是同一个对象,匿名类会生成新的对象,所以导致两个线程获取的monitor锁是不同的。这就导致wait()方法调用之后,两个线程都被挂起,但是再也没人能把他们唤醒,而且由于锁不同,两个线程都同时执行了,打印出的都是1。
正确实现:
public class NotifyTest implements Runnable {
int i = 1;
public static void main(String[] args) {
NotifyTest test = new NotifyTest();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.start();
t2.start();
}
@Override
public void run() {
while (true) {
synchronized (this) {
this.notify();
if (i <= 100) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + ":" + i);
i++;
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
通过condition实现:
public class ConditionTest implements Runnable{
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
int i = 1;
@Override
public void run() {
try {
lock.lock();
while (true) {
condition.signal();
if (i <= 100) {
System.out.println(Thread.currentThread().getName() + ":" + i);
i++;
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ConditionTest test = new ConditionTest();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.start();
t2.start();
}
}
拓展:
启动三个线程, 一个输出 1,4,7,10…100, 一个输出 2,5,8,11…101,最后一个暑促3,6,9,12...102 最后 STDOUT 中按序输出 1,2,3,4,5…102
实现:
public class NotifyTest2 implements Runnable {
private Object prev;
private Object self;
AtomicInteger i;
private NotifyTest2(AtomicInteger num,Object prev, Object self) {
this.i = num;
this.prev = prev;
this.self = self;
}
@Override
public void run() {
while (true) {
synchronized (prev) {
synchronized (self) {
if (i.get() <= 102) {
System.out.println(Thread.currentThread().getName() + ":" + i.get());
i.getAndIncrement();
self.notify();
}
}
try {
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
Object c = new Object();
AtomicInteger num = new AtomicInteger(1);
NotifyTest2 testA = new NotifyTest2(num,c,a);
NotifyTest2 testB = new NotifyTest2(num,a,b);
NotifyTest2 testC = new NotifyTest2(num,b,c);
new Thread(testA).start();
new Thread(testB).start();
new Thread(testC).start();
}
}
利用AtomicInteger做为共享变量。
wait-notify模型面试题的更多相关文章
- JVM内存模型和面试题解析
一.JVM运行时区域 其中, 线程私有的:程序计数器,虚拟机栈,本地方法栈 线程共享的:堆,方法区,直接内存 1 程序计数器 程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示 ...
- 不止面试02-JVM内存模型面试题详解
第一部分:面试题 本篇文章我们将尝试回答以下问题: 描述一下jvm的内存结构 描述一下jvm的内存模型 谈一下你对常量池的理解 什么情况下会发生栈内存溢出?和内存溢出有什么不同? String str ...
- Java多线程与并发基础
CS-LogN思维导图:记录专业基础 面试题 开源地址:https://github.com/FISHers6/CS-LogN 多线程与并发基础 实现多线程 面试题1:有几种实现线程的方法,分别是什么 ...
- CCS+C6678LE开发记录11:多核协作(IPC)入门
为更好地发挥C6678的多核性能,需要用到多核协作.幸运的是,我们可以使用官方提供的IPC模块. IPC=Inter-Processor Communication, 核间通信,粗略来说就是多核之间进 ...
- 详解CurrentHashMap之预习篇
CurrentHashMap的出现时为了解决HashMap的高并发导致OOM的缺陷,并且能够保证高性能读取.那么解读CurrentHashMap需要具备哪些知识的呢? HashMap 解读 Java ...
- 关于Java高并发编程你需要知道的“升段攻略”
关于Java高并发编程你需要知道的"升段攻略" 基础 Thread对象调用start()方法包含的步骤 通过jvm告诉操作系统创建Thread 操作系统开辟内存并使用Windows ...
- Java基础(补充)
为什么 Java 中只有值传递? 开始之前,我们先来搞懂下面这两个概念: 形参&实参 值传递&引用传递 形参&实参 方法的定义可能会用到 参数(有参的方法),参数在程序语言中分 ...
- 如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例
wait, notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视.本文对这些关键字的使用进行了描述. 在 Java 中可以用 wait ...
- 【java线程系列】java线程系列之线程间的交互wait()/notify()/notifyAll()及生产者与消费者模型
关于线程,博主写过java线程详解基本上把java线程的基础知识都讲解到位了,但是那还远远不够,多线程的存在就是为了让多个线程去协作来完成某一具体任务,比如生产者与消费者模型,因此了解线程间的协作是非 ...
随机推荐
- 判断人员js
var allchooseEmpID = "";var allchooseEmpName = "";//自选经办人 function getJbrWinForM ...
- Codeforces 769D
太久没写搜索因为递归边界问题卡了很久.. 题意:定义k-interesting:如果两个数的二进制形式有k位不相同,则称之为k-interesting.给出n和k,输入n个大小在[0,10000]之间 ...
- 【转】linux下passwd命令设置修改用户密码
1.passwd 简单说明: 我们已经学会如何添加用户了,所以我们还要学习设置或修改用户的密码:passwd命令的用法也很多,我们只选如下的几个参数加以说明:想了解更多,请参考man passwd或p ...
- HTTP协议头部字段释义
1. Accept:告诉WEB服务器自己接受什么介质类型,*/* 表示任何类型,type/* 表示该类型下的所有子类型,type/sub-type. 2. Accept-Charset: 浏览器申明自 ...
- linux设置库文件加载包含路径
第一种方式vim /etc/ld.so.conf 将要包含的路径添加到此文件中退出重新登录使配置生效或者执行命令source /etc/ld.so.conf 另一种方式利用LIBRARY_PATH和L ...
- 【转】Java 集合系列10之 HashMap详细介绍(源码解析)和使用示例
概要 这一章,我们对HashMap进行学习.我们先对HashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashMap.内容包括:第1部分 HashMap介绍第2部分 HashMa ...
- AP聚类
基于代表点的聚类算法可以说是聚类算法中"最经典的,最流行的,也是最前沿的". "最经典"是因为K均值是最早出现的聚类算法之一; "最流行"是 ...
- Git的使用及托管代码到GitHub
首先Git是一个开源的分布式版本控制工具,用git创建代码仓库. 仓库(Repository)是用于保存版本管理所需信息的地方,本地代码 提交到 代码仓库中,如果需要还可以 再推送到 远程仓库中. 所 ...
- js 学习笔记---BOM
window对象 1. window 对象是Global对象,在全局作用域中声明的变量和函数都可以通过window.来访问.跟直接在window上添加属性效果一样.唯一的区别就是delete时,如果是 ...
- 4星|《JAC写给外贸公司老板的企管书》:善总结爱学习、有业绩的老外贸的经验谈
作者从事外贸10余年,作出了业绩,也善总结.爱学习.爱分享.本书是作者在外贸行业的从业经验集.有一些战略方面的,比如开发小语种市场,大部分都是战术方面的操作细节(比如如何做营销),应该是非常适合从业者 ...