刚刚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线程同步问题——由腾讯笔试题引发的风波的更多相关文章

  1. java 线程同步 原理 sleep和wait区别

    java线程同步的原理java会为每个Object对象分配一个monitor, 当某个对象(实例)的同步方法(synchronized methods)被多个线程调用时,该对象的monitor将负责处 ...

  2. Java线程同步_1

    Java线程同步_1 synchronized 该同步机制的的核心是同步监视器,任何对象都可以作为同步监视器,代码执行结束,或者程序调用了同步监视器的wait方法会导致释放同步监视器 synchron ...

  3. Java线程同步之一--AQS

    Java线程同步之一--AQS 线程同步是指两个并发执行的线程在同一时间不同时执行某一部分的程序.同步问题在生活中也很常见,就比如在麦当劳点餐,假设只有一个服务员能够提供点餐服务.每个服务员在同一时刻 ...

  4. java线程 同步临界区:thinking in java4 21.3.5

    java线程 同步临界区:thinking in java4 21.3.5 thinking in java 4免费下载:http://download.csdn.net/detail/liangru ...

  5. JAVA - 线程同步和线程调度的相关方法

    JAVA - 线程同步和线程调度的相关方法 wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁:wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等 ...

  6. Java线程同步的四种方式详解(建议收藏)

    ​ Java线程同步属于Java多线程与并发编程的核心点,需要重点掌握,下面我就来详解Java线程同步的4种主要的实现方式@mikechen 目录 什么是线程同步 线程同步的几种方式 1.使用sync ...

  7. 算法题14 小Q歌单,牛客网,腾讯笔试题

    算法题14 小Q歌单,牛客网,腾讯笔试题 题目: 小Q有X首长度为A的不同的歌和Y首长度为B的不同的歌,现在小Q想用这些歌组成一个总长度正好为K的歌单,每首歌最多只能在歌单中出现一次,在不考虑歌单内歌 ...

  8. 算法题16 贪吃的小Q 牛客网 腾讯笔试题

    算法题16 贪吃的小Q 牛客网 腾讯笔试题 题目: 链接:https://www.nowcoder.com/questionTerminal/d732267e73ce4918b61d9e3d0ddd9 ...

  9. Java线程同步和线程通信

    一.线程同步 当多个线程访问同一个数据时,非常容易出现线程安全问题.这时候就需要用线程同步. 不可变类总是线程安全的,因为它的对象状态是不可改变的,但可变类对象需要额外的方法来保证线程安全. 1.同步 ...

随机推荐

  1. hdu1028 Ignatius and the Princess III(生成函数整理占坑)upd 已咕

    先咕着 ---------------2018 5 22---------------------- 题解 生成函数处理整数拆分 code #include<cstdio> #includ ...

  2. 「APIO2018选圆圈」

    「APIO2018选圆圈」 题目描述 在平面上,有 \(n\) 个圆,记为 \(c_1, c_2, \ldots, c_n\) .我们尝试对这些圆运行这个算法: 找到这些圆中半径最大的.如果有多个半径 ...

  3. Codeforces 493 E.Devu and Birthday Celebration

    \(>Codeforces \space 493\ E.Devu\ and\ Birthday\ Celebration<\) 题目大意 : 有 \(q\) 组询问,每次有 \(n\) 小 ...

  4. hihocoder #1071 : 小玩具

    闻所未闻的$dp$神题(我不会的题) 令$f[S][i]$表示子集状态为$S$,且$S$中最大联通块恰好为$i$的方案数 考虑转移,我们枚举$S$中最小的元素$v$来转移,这样就能不重 $f[S][i ...

  5. JZYZOJ1540 BZOJ4035 [ haoi2015 上午] T3 博弈论 sg函数 分块 haoi

    http://172.20.6.3/Problem_Show.asp?id=1540 之前莫比乌斯反演也写了一道这种找规律分块计算的题,没觉得这么恶心啊. 具体解释看代码. 翻硬币的具体方法就是分别算 ...

  6. POJ 2774 Long Long Message 后缀数组模板题

    题意 给定字符串A.B,求其最长公共子串 后缀数组模板题,求出height数组,判断sa[i]与sa[i-1]是否分属字符串A.B,统计答案即可. #include <cstdio> #i ...

  7. c++string函数详解

    string,一个极为好用了函数,学好了这些函数,在模拟以及字符串问题上,回节省很多很多的写代码时间,代码复杂度以及错误率,那么这一类函数都有那些功能呢?我们来逐一介绍(让你大吃一惊,还有这种操作?) ...

  8. JDK源码(1.7) -- java.util.AbstractCollection<E>

    java.util.AbstractCollection<E> 源码分析(JDK1.7) ------------------------------------------------- ...

  9. VK Cup 2016 - Round 1 (Div. 2 Edition) B. Bear and Displayed Friends 树状数组

    B. Bear and Displayed Friends 题目连接: http://www.codeforces.com/contest/658/problem/B Description Lima ...

  10. 2015 UESTC 数据结构专题N题 秋实大哥搞算数 表达式求值/栈

    秋实大哥搞算数 Time Limit: 1 Sec  Memory Limit: 256 MB 题目连接 http://acm.uestc.edu.cn/#/problem/show/1074 Des ...