一、线程同步
当多个线程访问同一个数据时,非常容易出现线程安全问题。这时候就需要用线程同步。
不可变类总是线程安全的,因为它的对象状态是不可改变的,但可变类对象需要额外的方法来保证线程安全。
同步监视器
释放同步监视器的锁定
任何线程进入同步代码块、同步方法之前,必须先获得对同步监视器的锁定
同步锁(Lock)
Lock是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁
class C {
public void method() {
//上锁
lock.lock();
try {
//保证线程安全操作代码
} catch() {
} finally {
lock.unlock();//释放锁
}
}
}
synchronized
用synchronized同步关键字修饰,那么这个方法就是一个同步的方法。这样就只能有一个线程可以访问这个方法,
在当前线程调用这个方法时,此方法是被锁状态,同步监视器是this。只有当此方法修改完毕后其他线程才能调用此方法。
这样就可以保证线程的安全,处理多线程并发取钱的的安全问题。
public synchronized void drawMoney(double money) {
//取钱操作
}
注意:synchronized可以修饰方法、代码块,但不能修饰属性、构造方法
二、线程通信
线程通信的目标是使线程间能够互相发送信号
1、通过共享对象通信
线程间发送信号的一个简单方式是在共享对象的变量里设置信号值。
03 | protected boolean hasDataToProcess = false ; |
05 | public synchronized boolean hasDataToProcess(){ |
06 | return this .hasDataToProcess; |
09 | public synchronized void setHasDataToProcess( boolean hasData){ |
10 | this .hasDataToProcess = hasData; |
2、忙等待(Busy Wait)
准备处理数据的线程B正在等待数据变为可用。换句话说,它在等待线程A的一个信号,这个信号使hasDataToProcess()返回true。线程B运行在一个循环里,以等待这个信号:
1 | protected MySignal sharedSignal = ... |
5 | while (!sharedSignal.hasDataToProcess()){ |
3、wait(),notify()和notifyAll()
忙等待没有对运行等待线程的CPU进行有效的利用,除非平均等待时间非常短。否则,让等待线程进入睡眠或者非运行状态更为明智,直到它接收到它等待的信号。
Java有一个内建的等待机制来允许线程在等待信号的时候变为非运行状态。java.lang.Object 类定义了三个方法,wait()、notify()和notifyAll()来实现这个等待机制。
一个线程一旦调用了任意对象的wait()方法,就会变为非运行状态,直到另一个线程调用了同一个对象的notify()方法。为了调用wait()或者notify(),线程必须先获得那个对象的锁。也就是说,线程必须在同步块里调用wait()或者notify()。以下是MySingal的修改版本——使用了wait()和notify()的MyWaitNotify:
01 | public class MonitorObject{ |
04 | public class MyWaitNotify{ |
06 | MonitorObject myMonitorObject = new MonitorObject(); |
09 | synchronized (myMonitorObject){ |
11 | myMonitorObject.wait(); |
12 | } catch (InterruptedException e){...} |
16 | public void doNotify(){ |
17 | synchronized (myMonitorObject){ |
18 | myMonitorObject.notify(); |
等待线程将调用doWait(),而唤醒线程将调用doNotify()。当一个线程调用一个对象的notify()方法,正在等待该对象的所有线程中将有一个线程被唤醒并允许执行(校注:这个将被唤醒的线程是随机的,不可以指定唤醒哪个线程)。同时也提供了一个notifyAll()方法来唤醒正在等待一个给定对象的所有线程。
不管是等待线程还是唤醒线程都在同步块里调用wait()和notify()。一个线程如果没有持有对象锁,将不能调用wait(),notify()或者notifyAll()。否则,会抛出IllegalMonitorStateException异常。
一旦一个线程被唤醒,不能立刻就退出wait()的方法调用,直到调用notify()的线程退出了它自己的同步块。换句话说:被唤醒的线程必须重新获得监视器对象的锁,才可以退出wait()的方法调用,因为wait方法调用运行在同步块里面。如果多个线程被notifyAll()唤醒,那么在同一时刻将只有一个线程可以退出wait()方法,因为每个线程在退出wait()前必须获得监视器对象的锁。