Object类中提供了三个final方法, 无法重写和继承, 这三个方法都必须在同步代码块中执行(例如synchronized块):
wait(): 等待
notify(): 唤醒单个线程(随机一个)
notifyAll(): 唤醒所有线程
wait(): 通过锁对象来调用, “synchronized(a) {a.wait(); }”. 本质是线程调用方法后, 暂时让出同步锁(解锁), 以便其他线程能进入同步锁, 并进入等待阻塞状态.
notify() / notifyAll(): 通过锁对象调用, “synchronized(a) {a.notify(); }”. 本质是通知其他处于等待阻塞的线程, 可以开始和其他线程抢同步锁, 首先进入同步阻塞状态, 如果抢到锁, 则进入就绪状态, 获得程序执行权. 这两个方法通知的是通过该对象同步锁的线程, notify()是通知随机一个, notifyAll()是通知所有.
特别注意: notify() / notifyAll()并不代表线程可以马上开始执行,仍然需要和其他线程抢锁
如下生产消费案例, SetStudent类来生产, GetStudent类来消费输出.
先做一个学生类
public class Student {
String name;
int age;
}
生产类
public class SetStudent implements Runnable {
private Student s;
private int x = 0;
public SetStudent() {}
//s = AA;
public SetStudent(Student s) {this.s = s;}
@Override
public void run() {
while (true) {
//锁对象s和下面的ss是同一个对象
synchronized (s) {
if (x % 3 == 0) {
s.name = "aaaaaa"; s.age = 25;
} else if(x%3==1){
s.name = "bbbbbb"; s.age = 98;
} else{
s.name = "qqqqqq"; s.age = 111;
}
x++;
}
if(x==30){ System.exit(0); }
}
}
}
消费类
public class GetStudent implements Runnable {
private Student ss;
public GetStudent() { }
//ss = AA;
public GetStudent(Student s) { this.ss = s; }
@Override
public void run() {
while (true) {
synchronized (ss) {
System.out.println(ss.name + "==" + ss.age);
}
}
}
}
通过生产类依次给Student设置不同的属性, 通过消费类将Student打印输出.
其中生产和消费类的Synchronized锁对象必须一样, 不能用this, 案例中的s/ss, 其实是同一把锁, 将测试类的AA学生传递到构造中赋值给到s和ss, 这样才能保证生产学生和消费打印不会同时进行, 要么set, 要么get.
最后贴上测试类
public class StudentDemo {
public static void main(String[] args) {
Student AA = new Student();//AA是用来区分线程中的变量
GetStudent gt = new GetStudent(AA);
SetStudent st = new SetStudent(AA);
Thread th1 = new Thread(st);
Thread th2 = new Thread(gt);
th2.start();
th1.start();
}
}
打印结果
null0
null0
bbbbbb98
bbbbbb98
aaaaaa25
aaaaaa25
aaaaaa25
aaaaaa25
代码问题:
-
GetStudent在学生还没有set时就先抢到线程运行权, 输出空值.
增加判断标记, GetStudent线程获取monitor锁后判断, 如果没有学生则wait(), 只有在学生设置好后, 接到SetStudent的 notify()通知, 然后再运行输出语句, 并置换flag状态, 然后notify(), 避免死锁.
-
同一个学生连续打印多次, 没有依次打印.
增加判断标记flag, 学生如果没有设置, 则设置学生, 然后置换flag状态, 并notify() 同步锁中处于等待阻塞的线程GetStudent, 告诉他, 有学生了可以开始消费了. 如果设置的学生还未消费, 则wait(), 直至消费后被唤醒.
修改优化代码如下:
学生类
public class Student {
String name;
int age;
boolean flag; //增加flag, 标记学生是否已经生产或者消费
}
生产类
public class SetStudent implements Runnable {
private Student s;
private int x = 0;
public SetStudent() { }
public SetStudent(Student s) { this.s = s; }
@Override
public void run() {
while (true) {
synchronized (s) {
if (s.flag) {
try {
//如果学生还未消费,则等待
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (x % 3 == 0) {
s.name = "aaaaaa"; s.age = 25;
} else if(x%3==1){
s.name = "bbbbbb"; s.age = 98;
} else{
s.name = "qqqqqq"; s.age = 111;
}
x++;
s.flag = true;//标记学生已经生产完毕
s.notify();
}
if(x==30){
System.exit(0);
}
}
}
}
消费类
public class GetStudent implements Runnable {
private Student ss;
public GetStudent() {
}
public GetStudent(Student s) {
this.ss = s;
}
@Override
public void run() {
while (true) {
synchronized (ss) {
//如果学生还未生产,则等待
if(!ss.flag){
try {
ss.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(ss.name + "==" + ss.age);
ss.flag=false;//标记学生已经消费完毕
ss.notify();
}
}
}
}
结果打印
总结:
- wait() 和notify()需要在同步代码中执行, 由锁对象调用
- wait()会释放同步锁, 并进入阻塞状态.
- notify()只是通知等待线程, 可以重新抢monitor锁