- 通过共享对象通信
线程间发送信号的一个简单方式是在共享对象的变量里设置信号值。 - 忙等待(Busy Wait)
准备处理数据的线程B正在等待数据变为可用。换句话说,它在等待线程A的一个信号 - wait(),notify()和notifyAll()
忙等待没有对运行等待线程的CPU进行有效的利用,除非平均等待时间非常短。否则,让等待线程进入睡眠或者非运行状态更为明智,直到它接收到它等待的信号。
Java有一个内建的等待机制来允许线程在等待信号的时候变为非运行状态。java.lang.Object 类定义了三个方法,wait()、notify()和notifyAll()来实现这个等待机制。 - 丢失的信号(Missed Signals)
notify()和notifyAll()方法不会保存调用它们的方法,因为当这两个方法被调用时,有可能没有线程处于等待状态。通知信号过后便丢弃了。因此,如果一个线程先于被通知线程调用wait()前调用了notify(),等待的线程将错过这个信号。这可能是也可能不是个问题。不过,在某些情况下,这可能使等待线程永远在等待,不再醒来,因为线程错过了唤醒信号。 - 假唤醒
为了防止假唤醒,保存信号的成员变量将在一个while循环里接受检查,而不是在if表达式里。这样的一个while循环叫做自旋锁。 - 多个线程等待相同信号
- 不要在字符串常量或全局对象中调用wait()
所以:在wait()/notify()机制中,不要使用全局对象,字符串常量等。应该使用对应唯一的对象。
● 静态变量:线程非安全。
静态变量即类变量,位于方法区,为所有对象共享,共享一份内存,一旦静态变量被修改,其他对象均对修改可见,故线程非安全。
● 实例变量:单例模式(只有一个对象实例存在)线程非安全,非单例线程安全。
实例变量为对象实例私有,在虚拟机的堆中分配,若在系统中只存在一个此对象的实例,在多线程环境下,“犹如”静态变量那样,被某个线程修改后,其他线程对修改均可见,故线程非安全;如果每个线程执行都是在不同的对象中,那对象与对象之间的实例变量的修改将互不影响,故线程安全。
● 局部变量:线程安全。
每个线程执行时将会把局部变量放在各自栈帧的工作内存中,线程间不共享,故不存在线程安全问题。
public class ThreadShareData {
private static int data=0;
private static Map<Thread,Integer> threadData=new HashMap<Thread,Integer>();
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
data = new Random().nextInt() ;
System.out.println(Thread.currentThread().getName() + ":" + data);
threadData.put(Thread.currentThread(),data);
new A().get();
new B().get();
}
}).start();
}
}
static class A{
public void get(){
data=threadData.get(Thread.currentThread());
System.out.println("A--"+Thread.currentThread().getName()+":"+data);
}
}
static class B{
public void get(){
data=threadData.get(Thread.currentThread());
System.out.println("B--"+Thread.currentThread().getName()+":"+data);
}
}
}
输出结果:
Thread-1:308764307
Thread-0:499909925
A--Thread-1:499909925
A--Thread-0:499909925
B--Thread-0:499909925
B--Thread-1:499909925
private static int data=0;
private static Map<Thread,Integer> threadData=new HashMap<Thread,Integer>();
threadData是一个HashMap,同一个线程只能放一个data。