java线程同步问题——由腾讯笔试题引发的风波
刚刚wm问我了一道线程的问题,因为自己一直是coder界里的渣渣。所以就须要恶补一下。
2016年4月2号题目例如以下。
import java.util.logging.Handler; /**
* 完SyncTask的start方法,要求
* 1,SyncTask的派生类的run方法抛到Handler所属的线程运行。
* 2。SyncTask派生类的运行线程等待返回,除非等待的超时timeout
* 3,假设timeout或出错。则返回默认值defultRet
*/
public class wm {
public abstract class SyncTask<R> {
protected abstract R run();
private R result;
private byte[] lock = new byte[0];
private boolean notified = false;
private Runnable task = new Runnable() {
@Override
public void run() {
R ret = SyncTask.this.run();
synchronized (lock) {
result = ret;
lock.notify();
notified = true;
}
}
}; /***
* 将任务抛到其它线程,同步等待其返回结果
* @param timeout 超过指定时间则直接返回ms
* @param defaultRet 默认返回值。即超时后或出错的返回值
* @param handler 运行线程handler
* @return
*/
public R start(final long timeout, final R defaultRet, Handler handler) { }
} }
见,知乎 https://www.zhihu.com/question/43416744
1。基础知识
线程的等待与唤醒
/**
* Created by xk on 2016/4/2.
*/
public class WaitTest {
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
synchronized (t1) {
try {
//启动线程
System.out.println(Thread.currentThread().getName() + " start t1");
t1.start(); System.out.println(Thread.currentThread().getName() + "wait()");
t1.wait(); System.out.println(Thread.currentThread().getName() + "continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class ThreadA extends Thread {
public ThreadA(String name) {
super(name);
}
public void run() {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + "call notify()");
notify();
}
}
}
输出
Object类中关于等待/唤醒的API具体信息例如以下:
notify() -- 唤醒在此对象监视器上等待的单个线程。
notifyAll() -- 唤醒在此对象监视器上等待的全部线程。
wait() -- 让当前线程处于“等待(堵塞)状态”,“直到其它线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout) -- 让当前线程处于“等待(堵塞)状态”,“直到其它线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”。当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos) -- 让当前线程处于“等待(堵塞)状态”。“直到其它线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其它某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
1. 在上述程序中,主线程是main。t1是main线程中启动的线程,而锁是t1对象的同步锁。
2. 主线程调用new 新建一个线程,通过synchronized(t1)来获取t1对象的同步锁。然后调用t1.start()来启动线程t1.
3. 主线程。运行wait释放t1的锁,进入等待(堵塞)状态。等待t1对象上的线程通过notify或者notifyAll将其唤醒。
4. 线程t1执行之后,通过synchronized(this)获取当前对象的锁,调用。notify唤醒当前对象上的等待的线程,即main。
5. 线程t1执行完成。释放当前对象的锁,紧接着,主线程获取t1对象的锁,接着执行。
补充,
1,t1.wait()是让“主线程main”等待。而不是t1.当前线程调用wait的时候,必须拥有该对象的同步锁,调用之后。释放该锁。直到等待的调用对象的同步锁的notify或者notifyAll方法,该线程就会获得该对象的同步锁,继续执行。
wait()的作用是让“当前线程”等待,而“当前线程”是指正在cpu上执行的线程!
这也意味着。尽管t1.wait()是通过“线程t1”调用的wait()方法,可是调用t1.wait()的地方是在“主线程main”中。而主线程必须是“当前线程”,也就是执行状态,才干够执行t1.wait()。
所以。此时的“当前线程”是“主线程main”!因此,t1.wait()是让“主线程”等待。而不是“线程t1”!
wait(long timeout)会让当前线程处于“等待(堵塞)状态”。“直到其它线程调用此对象的 notify() 方法或 notifyAll() 方法。或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout)会让当前线程处于“等待(堵塞)状态”,“直到其它线程调用此对象的 notify() 方法或 notifyAll() 方法。或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
以下的演示样例就是演示wait(long timeout)在超时情况下,线程被唤醒的情况。
// WaitTimeoutTest.java的源代码
class ThreadA extends Thread{
public ThreadA(String name) {
super(name);
}
public void run() {
System.out.println(Thread.currentThread().getName() + " run ");
// 死循环。不断执行。
while(true)
}
}
public class WaitTimeoutTest {
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
synchronized(t1) {
try {
// 启动“线程t1”
System.out.println(Thread.currentThread().getName() + " start t1");
t1.start();
// 主线程等待t1通过notify()唤醒 或 notifyAll()唤醒。或超过3000ms延时;然后才被唤醒。
System.out.println(Thread.currentThread().getName() + " call wait ");
t1.wait(3000);
System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果:
main start t1
main call wait
t1 run // 大约3秒之后...输出“main continue”
main continue
结果说明:
例如以下图。说明了“主线程”和“线程t1”的流程。
(01) 注意,图中"主线程" 代表WaitTimeoutTest主线程(即,线程main)。"线程t1" 代表WaitTest中启动的线程t1。
而“锁” 代表“t1这个对象的同步锁”。
(02) 主线程main运行t1.start()启动“线程t1”。
(03) 主线程main执行t1.wait(3000),此时,主线程进入“堵塞状态”。
须要“用于t1对象锁的线程通过notify() 或者 notifyAll()将其唤醒” 或者 “超时3000ms之后”,主线程main才进入到“就绪状态”。然后才干够执行。
(04) “线程t1”执行之后,进入了死循环,一直不断的执行。
(05) 超时3000ms之后,主线程main会进入到“就绪状态”,然后接着进入“执行状态”。
4. wait() 和 notifyAll()
通过前面的演示样例,我们知道 notify() 能够唤醒在此对象监视器上等待的单个线程。
以下,我们通过演示样例演示notifyAll()的使用方法;它的作用是唤醒在此对象监视器上等待的全部线程。
public class NotifyAllTest {
private static Object obj = new Object();
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
ThreadA t2 = new ThreadA("t2");
ThreadA t3 = new ThreadA("t3");
t1.start();
t2.start();
t3.start();
try {
System.out.println(Thread.currentThread().getName()+" sleep(3000)");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(obj) {
// 主线程等待唤醒。
System.out.println(Thread.currentThread().getName()+" notifyAll()");
obj.notifyAll();
}
}
static class ThreadA extends Thread{
public ThreadA(String name){
super(name);
}
public void run() {
synchronized (obj) {
try {
// 打印输出结果
System.out.println(Thread.currentThread().getName() + " wait");
// 唤醒当前的wait线程
obj.wait();
// 打印输出结果
System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
执行结果:
t1 wait
main sleep(3000)
t3 wait
t2 wait
main notifyAll()
t2 continue
t3 continue
t1 continue
结果说明:
參考以下的流程图。
(01) 主线程中新建而且启动了3个线程"t1", "t2"和"t3"。
(02) 主线程通过sleep(3000)休眠3秒。在主线程休眠3秒的过程中。我们如果"t1", "t2"和"t3"这3个线程都执行了。以"t1"为例,当它执行的时候,它会执行obj.wait()等待其他线程通过notify()或额nofityAll()来唤醒它;同样的道理。"t2"和"t3"也会等待其他线程通过nofity()或nofityAll()来唤醒它们。
(03) 主线程休眠3秒之后,接着执行。
执行 obj.notifyAll() 唤醒obj上的等待线程。即唤醒"t1", "t2"和"t3"这3个线程。 紧接着,主线程的synchronized(obj)执行完成之后,主线程释放“obj锁”。
这样,"t1", "t2"和"t3"就能够获取“obj锁”而继续执行了!
5. 为什么notify(), wait()等函数定义在Object中,而不是Thread中
Object中的wait(), notify()等函数。和synchronized一样,会对“对象的同步锁”进行操作。
wait()会使“当前线程”等待,由于线程进入等待状态。所以线程应该释放它锁持有的“同步锁”。否则其他线程获取不到该“同步锁”而无法执行!
OK。线程调用wait()之后,会释放它锁持有的“同步锁”。并且。根据前面的介绍,我们知道:等待线程能够被notify()或notifyAll()唤醒。
如今,请思考一个问题:notify()是根据什么唤醒等待线程的?或者说。wait()等待线程和notify()之间是通过什么关联起来的?答案是:根据“对象的同步锁”。
负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它仅仅有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),而且调用notify()或notifyAll()方法之后,才干唤醒等待线程。尽管,等待线程被唤醒;可是。它不能立马执行。由于唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才干获取到“对象的同步锁”进而继续执行。
总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,而且每一个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类。而不是Thread类中的原因。
java线程同步问题——由腾讯笔试题引发的风波的更多相关文章
- java 线程同步 原理 sleep和wait区别
java线程同步的原理java会为每个Object对象分配一个monitor, 当某个对象(实例)的同步方法(synchronized methods)被多个线程调用时,该对象的monitor将负责处 ...
- Java线程同步_1
Java线程同步_1 synchronized 该同步机制的的核心是同步监视器,任何对象都可以作为同步监视器,代码执行结束,或者程序调用了同步监视器的wait方法会导致释放同步监视器 synchron ...
- Java线程同步之一--AQS
Java线程同步之一--AQS 线程同步是指两个并发执行的线程在同一时间不同时执行某一部分的程序.同步问题在生活中也很常见,就比如在麦当劳点餐,假设只有一个服务员能够提供点餐服务.每个服务员在同一时刻 ...
- java线程 同步临界区:thinking in java4 21.3.5
java线程 同步临界区:thinking in java4 21.3.5 thinking in java 4免费下载:http://download.csdn.net/detail/liangru ...
- JAVA - 线程同步和线程调度的相关方法
JAVA - 线程同步和线程调度的相关方法 wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁:wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等 ...
- Java线程同步的四种方式详解(建议收藏)
Java线程同步属于Java多线程与并发编程的核心点,需要重点掌握,下面我就来详解Java线程同步的4种主要的实现方式@mikechen 目录 什么是线程同步 线程同步的几种方式 1.使用sync ...
- 算法题14 小Q歌单,牛客网,腾讯笔试题
算法题14 小Q歌单,牛客网,腾讯笔试题 题目: 小Q有X首长度为A的不同的歌和Y首长度为B的不同的歌,现在小Q想用这些歌组成一个总长度正好为K的歌单,每首歌最多只能在歌单中出现一次,在不考虑歌单内歌 ...
- 算法题16 贪吃的小Q 牛客网 腾讯笔试题
算法题16 贪吃的小Q 牛客网 腾讯笔试题 题目: 链接:https://www.nowcoder.com/questionTerminal/d732267e73ce4918b61d9e3d0ddd9 ...
- Java线程同步和线程通信
一.线程同步 当多个线程访问同一个数据时,非常容易出现线程安全问题.这时候就需要用线程同步. 不可变类总是线程安全的,因为它的对象状态是不可改变的,但可变类对象需要额外的方法来保证线程安全. 1.同步 ...
随机推荐
- 浅谈MVVM模式和MVP模式——Vue.js向
浅谈MVVM模式和MVP模式--Vue.js向 传统前端开发的MVP模式 MVP开发模式的理解过程 首先代码分为三层: model层(数据层), presenter层(控制层/业务逻辑相关) view ...
- 在MySQL字段中使用逗号分隔符
大多数开发者应该都遇到过在mysql字段中存储逗号分割字符串的经历,无论这些被分割的字段代表的是id还是tag,这个字段都应该具有如下几个共性. 被分割的字段一定是有限而且数量较少的,我们不可能在一个 ...
- [CSAcademy]Exponential Game
题目大意: 有n堆石子,A和B两人轮流进行操作: 取走任意一堆石子,若这堆石子的个数是x个,那么可以放入x-1堆数量为0~x-1的石子. 不能操作者负. 思路: 将每一堆石子作为一个子游戏,将石子的数 ...
- [bzoj1017][JSOI2008]魔兽地图 DotR (Tree DP)【有待优化】
Description DotR (Defense of the Robots) Allstars是一个风靡全球的魔兽地图,他的规则简单与同样流行的地图DotA (Defense of the Anc ...
- Velocity模板学习(一)
一.Velocity是什么 Velocity是一个基于Java的模板引擎,允许任何人仅仅简单地使用模板语言就可以引用由Java代码编写的对象. 二.Velocity的基本语法 1.变量 变量的定义 在 ...
- bzoj 3172: [Tjoi2013]单词 AC自动机
3172: [Tjoi2013]单词 Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://www.lydsy.com/JudgeOnline/pr ...
- 使用CefSharp在.Net程序中嵌入Chrome浏览器(二)——参数设置
在实现了.Net程序中嵌入Chrome浏览器后,下一步的个性化操作就是加入一些设置了,在前面的文章中,我们可以看到在使用Chrome控件前,有如下一个操作: var setting = new Cef ...
- Spring Bean init-method 和 destroy-method实例
在Spring中,可以使用 init-method 和 destroy-method 在bean 配置文件属性用于在bean初始化和销毁某些动作时.这是用来替代 InitializingBean和Di ...
- 搭建windows server 2008 r2 FTP 后 开启防火墙无法访问的解决办法
转自http://kkworms.blog.51cto.com/540865/558477 今天在windows server 2008 R2上安装了FTP,安装过程如下,然后添加内置防火墙设置,设置 ...
- [转载] 无所不能的“蚂蚁”--Ant
说他无所不能,好像有点夸张,但是用过Ant之后,感觉真的是只有想不到没有作不到.Ant,原作者选择他作为软件名字的意思是指"令一个简洁的工具"(Another Neat Tool) ...