一概述
本文属于《java并发编程的艺术》读书笔记系列,第四章java并发的基础。
4.1线程简介及线程启停
这里作者介绍了线程的相关知识,如线程、优先级,状态,daemon线程,线程的启动和停止。这里可以参照之前我整理的线程常见问题。
4.2线程间通信
4.2.1volatile跟synchronized
关于这块理解就是先看下上一篇第三章介绍的java内存模型,这里面介绍了对应的内存含义。也可以参照我之前整理的文章简单介绍下背景知识:
线程开始运行,拥有自己的栈空间,Java支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个变量的拷贝(虽然对象以及成员变量分配的内存是在共享内存中的,但是每个执行的线程还是可以拥有一份拷贝,这样做的目的是加速程序的执行,这是现代多核处理器的一个显著特性),所以程序在执行过程中,一个线程看到的变量并不一定是最新的。
关键字volatile可以用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。
关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。
同步代码就不在贴出来,对于同步块的实现使用了monitorenter和monitorexit指令,而同步方法则是依靠方法修饰符上的ACC_SYNCHRONIZED来完成的。无论采用哪种方式,其本质是对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。
任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取到该对象的监视器才能进入同步块或者同步方法,而没有获
取到监视器(执行该方法)的线程将会被阻塞在同步块和同步方法的入口处,进入BLOCKED状态。
从图4-2中可以看到,任意线程对Object(Object由synchronized保护)的访问,首先要获得Object的监视器。如果获取失败,线程进入同步队列,线程状态变为BLOCKED。当访问Object的前驱(获得了锁的线程)释放了锁,则该释放操作唤醒阻塞在同步队列中的线程,使其重新尝试对监视器的获取。
4.2.2等待/通知机制
这个机制背景就是为了解耦生产者、消费者的问题,简单的办法是使用轮询,但是轮询缺点是及时性、性能不能保证,所以采用通知机制避免轮询带来的性能损失。
等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上,方法和描述如表4-2所示。
等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class WaitNotify {
static boolean flag = true;
static Object lock = new Object();
public static void main(String[] args) throws Exception{
Thread waitThread = new Thread(new Wait(),"WaitThread");
waitThread.start();
TimeUnit.SECONDS.sleep(1);
Thread notifyThread = new Thread(new Notify(),"NotifyThread");
notifyThread.start();
}
static class Wait implements Runnable{
@Override
public void run() {
//加锁,拥有lock的monitor
synchronized (lock){
//当条件不满足时,继续wait,同时释放了lock的锁
while (flag){
try {
System.out.println(Thread.currentThread() + " flag is true. wait @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.wait();
}catch (InterruptedException e){
}
}
//条件满足时,完成工作
System.out.println(Thread.currentThread() + "flag is false. running @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class Notify implements Runnable{
@Override
public void run() {
synchronized (lock){
System.out.println(Thread.currentThread() + "hold lock. notify @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
}
synchronized (lock){
System.out.println(Thread.currentThread() + "hold lock again. sleep @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
}
}
}
}
运行结果:
Thread[WaitThread,5,main] flag is true. wait @ 13:22:12
Thread[NotifyThread,5,main]hold lock. notify @ 13:22:13
Thread[WaitThread,5,main]flag is false. running @ 13:22:18
Thread[NotifyThread,5,main]hold lock again. sleep @ 13:22:18
WaitThread首先获取了对象的锁,然后调用对象的wait()方法,从而放弃了锁并进入了对象的等待队列WaitQueue中,进入等待状态。由于WaitThread释放了对象的锁,
NotifyThread随后获取了对象的锁,并调用对象的notify()方法,将WaitThread从WaitQueue移到SynchronizedQueue中,此时WaitThread的状态变为阻塞状态。NotifyThread释放了锁之后,WaitThread再次获取到锁并从wait()方法返回继续执行。
4.2.3等待/通知的经典范式
从上节的示例中可以提取经典范式,分为等待方(消费者)和通知方(生产者)
等待方:
1)获取对象锁;
2)如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。
3)条件满足则执行对应的逻辑。
对应的伪代码如下:
synchronized(对象){
while(条件不满足){
Object.wait();
}
逻辑处理
}
通知方:
1)获得对象的锁;
2)改变条件;
3)通知所有等待在对象上的线程。
对应的伪代码如下:
synchronized(对象){
改变条件
对象.notifyAll();
}
4.2.4管道的输入、输出流主要用于线程间的数据传输,传输的媒介为内存。
这块只是淡出提了下,属于nio的范畴,需单独整理,例子就不贴了。
4.2.5Thread.join的使用
如果一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回。线程Thread除了提供join()方法之外,还提供了join(long millis)和join(longmillis,int nanos)两个具备超时特性的方法。这两个超时方法表示,如果线程thread在给定的超时时间里没有终止,那么将会从该超时方法中返回。
例子普通,这里作者贴出来jdk实现Thread.join的源码:
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {//无参数
while (isAlive()) {
wait(0);
}
} else {//等待固定时间
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
我也没找到,网上看看大神们从jvm找的吧。
作者:cao
链接:http://www.zhihu.com/question/44621343/answer/97640972
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
//一个c++函数:
void JavaThread::exit(bool destroy_vm, ExitType exit_type) ;
//这家伙是啥,就是一个线程执行完毕之后,jvm会做的事,做清理啊收尾工作,
//里面有一个贼不起眼的一行代码,眼神不好还看不到的呢,就是这个:
ensure_join(this);
//翻译成中文叫 确保_join(这个);代码如下:
static void ensure_join(JavaThread* thread) {
Handle threadObj(thread, thread->threadObj());
ObjectLocker lock(threadObj, thread);
thread->clear_pending_exception();
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
java_lang_Thread::set_thread(threadObj(), NULL);
//thread就是当前线程,是啥是啥?
lock.notify_all(thread);
thread->clear_pending_exception();
}
作者只是举例演示使用方式,这里注意使用场景,参见我之前整理的详解threadlocal。
4.4线程应用实例
这里不细写了,作者分别介绍了数据库连接池示例、线程池技术、基于线程池的简单web服务器。可以参照原书去理解。