线程同步
1. 线程同步模式保护性暂停
1.1 简介
一个线程等待一个线程的结果,或者也可以是线程之间同步】
1.2 实现思路
synchronized
synchronized 加锁 要关联到同一个对象,使用wait和notifyAll
1.3 实现
- 先定义一个 用来在俩线程之间 传递的对象,
class Temp{
static Object lock = new Object(); // 1
Object result = null;
public void put(Object o){ // 2
synchronized (lock){
result = o;
lock.notifyAll(); // 5
}
}
public Object take() throws InterruptedException { // 3
synchronized (lock){
while (result == null){
System.out.println("等待--------");
lock.wait(); // 4
}
return result;
}
}
}
解释说明
注释1 定义一个对象,这个对象的作用是锁。
注释2 put方法,因为 temp类中的result 属性是要多个线程之间共享的,所有对于这个对象的操作要加锁,每次放一个对象,唤醒那些没有拿到值而 等待的线程,
注释3 从temp对象中取值,同样的,涉及到多个线程之间访问,就是要加锁, 如果没有值就等待,有值就返回,如果等待就等put线程把他唤醒,
上面的方法 没有 等待时间,take线程看没有值后就会一直等下去
- 带等待时间的take方法
耐心的看看下面
public Object take(int waitTime) throws InterruptedException {
synchronized (lock){
while (result == null){
System.out.println("等待--------");
lock.wait(waitTime); // 1
}
return result;
}
}
如果是上面这样, 直接将传递进来的参数传给wait方法。就会有一个 虚假唤醒 出现
比如,等待时间是 20 秒, 结果在第10 秒的时候被其他线程唤醒了,因为唤醒是 notifyAll方法,线程醒来,发现没有结果,就会再次睡眠 20秒
改进的方法
在方法开始的时间继续开始时间,在wait被唤醒之后记录 结束时间,算出经过时间,用等待时间减去 经过时间就是新的等待时间,如果等待时间<0 线程就结束,也就是break出while循环
比如,要睡20秒,在第10 秒的时候被其他线程唤醒了,经过时间就是10秒,新的等待时间就是 10 秒, 就不会有虚假唤醒的情况出现
public Object take(long time) throws InterruptedException {
long start = System.currentTimeMillis();
long passTime = 0L; //经历时间
synchronized (lock){
while (result == null){
time = time-passTime;
System.out.println("waitTime"+time);
if(time <=0 ) break;
lock.wait(time);
passTime = System.currentTimeMillis()-start;
}
return result;
}
}
- 测试
public class ThreadSyncTest1 {
public static void main(String[] args) throws InterruptedException {
Temp temp = new Temp();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
temp.put("a");
}).start();
new Thread(()->{
try {
String take = (String) temp.take(1000*25);
System.out.println(take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
temp.wakeUp();
}
}
}
1.2 jdk中提供的工具
1.2.1 Exchanger
public class ExchangerTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final Exchanger exchanger = new Exchanger();
service.execute(new Runnable(){
public void run() {
try {
String data1 = "thread-1-data";
System.out.println("线程" + Thread.currentThread().getName() +"正在把数据" + data1 +"换出去");
Thread.sleep((long)(Math.random()*10000));
String data2 = (String)exchanger.exchange(data1);
System.out.println("线程" + Thread.currentThread().getName() + "换回的数据为" + data2);
}catch(Exception e){
}
}
});
service.execute(new Runnable(){
public void run() {
try {
String data1 = "thread-2-data";
System.out.println("线程" + Thread.currentThread().getName() + "正在把数据" + data1 +"换出去");
Thread.sleep((long)(Math.random()*10000));
String data2 = (String)exchanger.exchange(data1);
System.out.println("线程" + Thread.currentThread().getName() + "换回的数据为" + data2);
}catch(Exception e){
}
}
});
}
}
但是需要注意的是 这个允许等待时间的,但是如果在时间内没有返回,等待的线程会结束,另一个线程就会一直等待下去,来看看例子
public class MyThreadTest {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
new Thread(()->{
String s = "a";
try {
exchanger.exchange(s,1,TimeUnit.SECONDS); //1
} catch (InterruptedException | TimeoutException e) {
System.out.println(Thread.currentThread().getName()+"超时了!");
}
System.out.println(Thread.currentThread().getName() +":"+s);
}).start();
new Thread(()->{
String s = "b";
try {
TimeUnit.SECONDS.sleep(10); // 2
exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +":"+s);
}).start();
}
}
注释1 只允许等一秒 可是注释2 需要睡 10 秒,10秒之后 他不会被唤醒,
找时间看看 他的源码是怎么写的