什么是线程通信?
线程之间本来就是共享数据的,所以天然地就可以互通数据。因为不同线程共享对象堆里的数据。
所以,剩下来的唯一问题就是,如何解决同步(确保数据一致性)的问题。
怎么解决同步问题?
有2种方法
Object的等待wait/唤醒notify机制
应用场景
使用wait/notify来控制线程执行的顺序,以及对共享数据的访问。
注:如果是解决线程通信的问题,jdk也不推荐使用wait/notify,等待/唤醒机制主要用于控制多个线程的执行顺序。
同步机制
1.jvm同步关键字synchronized //隐式锁
1)同步方法
2)同步代码块/语句块
2.jdk并发包-显式锁/可重入锁RetrantLock //显式锁
优点
1)更好的控制锁的粒度
sychronized也可以控制锁的粒度,即尽量让上锁的代码块最小化。但是显式锁可以更好的控制锁的粒度。具体是怎么更好的控制锁的粒度呢?
2)控制锁的流程
超时;
中断。
锁
有2种情况,
对象的内置多
每个对象有一个内置锁/监视器。
每个对象还有一个等待线程集合。线程集合只能通过wait/notify来操作。
应用场景
非static 方法。//不同线程只有在同一个对象的锁上面,才会互斥访问这个方法;否则,不同线程可以同时访问这个方法。
代码
synchronized(this或其他对象){
...
}
类的内置锁
Locks In Synchronized Methods
When a thread invokes a synchronized method, it automatically acquires the intrinsic lock for that method's object and releases it when the method returns. The lock release occurs even if the return was caused by an uncaught exception.
You might wonder what happens when a static synchronized method is invoked, since a static method is associated with a class, not an object. In this case, the thread acquires the intrinsic lock for the Class object associated with the class. Thus access to class's static fields is controlled by a lock that's distinct from the lock for any instance of the class.
应用场景
static方法。//类的内置锁和类的对象的内置锁,都是互斥访问static方法!
代码
//启动服务的入口类
package com.xxx.instruction;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.gzh.commons.context.AppContext;
import com.gzh.commons.http.HttpClientUtils;
public class MainCSPI {
private static Log log = LogFactory.getLog(MainCSPI.class);
private static volatile boolean running = true;
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
try {
HttpClientUtils.getInstance().shutdown();
AppContext.stop();
log.info(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Main server stopped!");
}catch (Throwable t) {
log.error("Main stop error", t);
}
synchronized (MainCSPI.class) {
running = false;
MainCSPI.class.notify();
}
}
});
try {
AppContext.start();
//初始化HttpClient连接
HttpClientUtils httpClient = HttpClientUtils.getInstance();
httpClient.setConnectionTimeout(8*1000);
httpClient.setReadTimeout(15*1000);
} catch (RuntimeException e) {
log.error(e.getMessage(), e);
throw e;
}
log.info(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Main server started!");
synchronized (MainCSPI.class) { //类的内置锁
while (running) {
try {
MainCSPI.class.wait(); //让当然线程(即主线程/main线程)等待:目的是让当前服务一直处于运行的状态
} catch (Throwable e) {
}
}
}
}
}
锁与阻塞
1.多线程
2.获取同一个锁
多线程获取同一个对象的内置锁,只有当某个占有对象的内置锁的线程执行完成之后(即1.获取锁 2.释放锁),其他线程才可以获取这个对象的内置锁,否则将会一直阻塞。
怎么用
1.当前线程必须获取/拥有锁对象
哪个线程要调用锁对象的wait/notify,它必须要先获取锁对象。
线程对象调用锁对象的wait/notify之前,必须确保当前线程拥有锁对象。 所以,这就导致必须在同步代码块里调用锁对象的wait/notify。因为只有在同步代码块里调用锁对象的wait/notify,当前线程才会拥有锁对象,也才可能调用锁对象的wait/notify。
2.同一个对象
锁对象和锁对象.wait/notify,必须是同一个对象。否则会报错-非法监视器状态异常。
3.锁对象.wait/notify,必须在同步代码块/方法里
因为锁对象/监视器 和 锁对象.wait/notify 里的锁对象,必须是同一个对象。即2里说的。
//调用wait
synchronized (obj) { //当前线程必须获取锁对象
while (<condition does not hold>)
obj.wait(timeout); //调用wait/notify的对象和锁对象是同一个对象 //调用wait/notify的代码必须在同步代码块里
... // Perform action appropriate to condition //读线程:读共享数据
}
//调用notify
synchronized(对象){
改变条件 //写线程:写共享数据
对象.notifyAll(); //激活读线程
}
wait方法
wait()等同于wait(0)。
参数为0,表示无限等待,即一直等待下去,直到被另外一个线程通知。
what is the difference between wait and sleep、yield?
共同点
都是停止当前线程执行。
不同点
1.是否放弃锁
当前锁对象.wait() //线程A会放弃锁。什么时候恢复?1.在指定时间到之前,有另外一个线程B修改数据之后即完成任务之后调用notify,线程A重新获取锁重新执行。2.一直没有别的线程调用notify,时间到,线程A继续获取锁继续执行。
当前线程对象.sleep //线程A不会放弃锁。yield也是,只是交出CPU。在是否放弃锁方面,sleep和yield是一样的,都不放弃锁,而是只放弃CPU。
2.停止时间是否确定 sleep的停止时间是确定的,就是参数指定的时间。什么时候恢复?时间到了就恢复。
yield的停止时间是不确定的,它只是交出CPU,正在等待执行的线程集合里取一个同样优先级的线程来执行。也有可能是再次取的自己。什么时候恢复?时间不确定,依赖CPU和线程调度器。
javarevisited.blogspot.com/2011/12/dif… www.jianshu.com/p/25e959037…
wait和join的区别
1.当前锁对象.wait/notify
2.线程对象.join——》wait:当前执行线程等待
作用?让当前主线程等待,直到对象锁线程1( 指的是主线程调用线程1.join() )执行完毕。接着启动线程2。这样就确保了线程1和线程2按顺序执行。
参考
www.importnew.com/14958.html
www.java67.com/2017/11/dif…
线程执行完了,意味着什么?
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()) { //对象锁线程1执行完毕,接着执行线程2——这是怎么实现的?看这里代码知道,线程1执行完毕,线程死亡,循环结束,线程1.join()方法执行完毕,继续执行主线程里的代码,即启动线程2。 //这里的问题是主线程在线程1对象锁上等待,而且是无限等待,除非有另外一个线程调用线程1.notify(),否则主线程应该还是等待。线程死了,join()方法执行完毕,这个可以理解——但是为什么线程1死了,join()执行完毕了,为什么主线程重新开始执行了?没有另一个线程去调用线程1.notify(),主线程怎么会活过来呢?线程1退出的时候即exit()的时候会notifyAll()。https://juejin.im/post/6844903624842149895 //还有一个问题,调1次wait()不就行了吗?为什么要循环调用?因为调1次也是让主线程等待,调n次还是让主线程等待,所以循环调用的作用是什么?防止虚假唤醒,看API-Object.wait()。至于什么是虚假唤醒,还需要再看维基百科。
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
线程通信的各种场景分析
工作应用
支付-微服务
很多服务都不是web程序,而是java程序,怎么启动?当然是main方法启动。但是这个程序启动之后是不能关闭的,它要一直提供服务。怎么办?while(true)循环。
基于while(true)循环,改善为while(true)的情况下,还要让当前主线程wait,目的是不让程序关闭,一直向外提供服务,最重要的是,让程序wait,避免了循环执行无谓的代码去消耗和占用计算机的资源——说白了,就是不让CPU执行while(true)里的的代码,因为这是纯粹的浪费。
代码
package gzh.spring;
/* */ import java.text.SimpleDateFormat;
/* */ import java.util.Date;
/* */ import org.apache.commons.logging.Log;
/* */ import org.apache.commons.logging.LogFactory;
/* */
/* */
/* */ public class Main
/* */ {
/* 12 */ private static Log log = LogFactory.getLog(Main.class);
/* */
/* 14 */ private static volatile boolean running = true;
/* */
/* */ public static void main(String[] args) {
/* 17 */ Runtime.getRuntime().addShutdownHook(new Thread() {
/* */ public void run() {
/* */ try {
/* 20 */ AppContext.stop();
/* 21 */ Main.log.info(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Main server stopped!");
/* */ } catch (Throwable t) {
/* 23 */ Main.log.error("Main stop error:" + t);
/* */ }
/* 25 */ synchronized (Main.class) {
///* 26 */ Main.access$102(false);
/* 27 */ Main.class.notify();
/* */ }
/* */ }
/* */ });
/* */ try
/* */ {
/* 33 */ AppContext.start();
/* */ } catch (RuntimeException e) {
/* 35 */ log.error(e.getMessage(), e);
/* 36 */ throw e;
/* */ }
/* */
/* 39 */ log.info(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Main server started!");
/* */
/* 41 */ synchronized (Main.class) {
/* 42 */ while (running) {
/* */ try {
/* 44 */ Main.class.wait(); //让主线程wait
/* */ }
/* */ catch (Throwable e) {}
/* */ }
/* */ }
/* */ }
/* */ }