刚刚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. 简述HttpSession的作用、使用方法,可用代码说明

    HttpSession中可以跟踪并储存用户信息,把值设置到属性中,有2个方法:setAttribute(),getAttrribute(): 例如:在一个方法中用session.setAttribut ...

  2. Linux通过FTP上传文件到服务器

    1.如果没有安装ftp,可执行: 输入:yum -y install ftp,回车 等待安装完毕 2.连接服务器 输入:ftp 服务器IP,回车 根据提示输入用户名和密码 3.上传下载操作 1). 上 ...

  3. C和指针之学习笔记(6)

    第17章 经典数据结构类型 堆栈 堆栈接口提供三种基本的操作:push.pop 和 top. Push:把一个新值压入到堆栈的顶部. Pop: 只把顶部元素从堆栈中移除,它并不返回这个值. Top: ...

  4. [SimpleOJ238]宝藏探寻

    题目大意: 给你一棵带点权的n个结点的树,有m次询问,每次从树上删掉一条路径(u,v),问删掉每条路径后各个连通块权值和的平方之和. 每次询问是独立的. 思路: 首先对树遍历一遍求出每棵子树的权值和. ...

  5. 【8.17校内测试】【模拟】【set】【网络流】

    为什么每次想的最久的题得的分数最低!!!qwqwq 再也不在noip上尝试A*叻!! 模拟题,先把能消的消掉,双指针从两端向中间扫描,如果头尾合并可以消,就把它消掉,最后判断一下.因为消完过后num保 ...

  6. Codeforces Round #299 (Div. 1) A. Tavas and Karafs 水题

    Tavas and Karafs Time Limit: 1 Sec  Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/536/prob ...

  7. PAT甲级1014. Waiting in Line

    PAT甲级1014. Waiting in Line 题意: 假设银行有N个窗口可以开放服务.窗前有一条黄线,将等候区分为两部分.客户要排队的规则是: 每个窗口前面的黄线内的空间足以包含与M个客户的一 ...

  8. Swift 自定义打印方法

    Swift 自定义打印方法 代码如下 // MARK:- 自定义打印方法 func MLLog<T>(_ message : T, file : String = #file, funcN ...

  9. SLVA299A : Load Disconnect ( Input to Output Isolation ) for the TPS61040

    http://www.ti.com/lit/an/slva299a/slva299a.pdf Many boost converters have an external rectifier diod ...

  10. ELM327 OBD to RS232 Interpreters

    http://elmelectronics.com/DSheets/ELM327DS.pdf