Java多线程引出了临界区问题。当两个进程并发修改同一资源时就有可能造成异常。Java引入同步监视器来解决这个问题。使用同步监视器有两种方法:同步代码块和同步方法。
同步代码块:
synchronized(obj){
//此处代码是同步代码块
}
上述代码的obj就是同步监视器,同一时刻只能有一个线程获得同步监视器的锁定,当同步代码块执行完毕后,该线程会自动释放该同步监视器的锁定。
通常使用可能被并发访问的共享资源作为同步监视器obj,如银行取款问题就用银行账户作为同步监视器。
同步方法:
public synchronized void drow(double drawAmount){
//同步方法体
}
使用synchronized来修饰某个方法,该方法就是同步方法。同步方法无需显式指定同步监视器,它的同步监视器就是this,也就是调用该方法的对象。
不可变类总是线程安全的,因为它的对象状态不可被改变;可变类对象的方法就需要使用额外的方法(如上述方法)来保证线程安全。
synchronized关键字可以修饰方法和代码块,但不可以修饰构造器、成员变量等。
可变类的线程安全是以牺牲运行效率为代价的,所以不要对线程安全类的所有方法都进行同步。如果可变类有两种运行环境--单线程和多线程,那么应该为该可变类提供两种版本,即线程不安全版本和线程安全版本。
同步监视器的释放
下面这些情况会释放同步监视器
下面这些情况不会释放同步监视器
同步锁是比同步监视器更强大的线程同步机制----通过显式定义同步锁对象来实现同步,同步锁由Lock对象充当。
Lock和ReadWriteLock是Java 5提供的两个根接口,Lock有ReentrantLock(可重入锁)实现类,ReadWriteLock有ReentrantReadWriteLock实现类。
public class Account{
...
//定义锁对象,以ReentrantLock 为例
private final ReentrantLock lock = new ReentrantLock();
...
public void draw(double drawAmount){
//加锁
lock.lock();
try{
...
}catch(InterruptedException e){
...
}finally{
//释放锁
lock.unlock();
}
}
}
ReentrantLock 锁具有可重入性,即一个线程可以对已被加锁的ReentrantLock 锁再次加锁,ReentrantLock 对象维持一个计数器来追踪lock()方法的嵌套调用,线程在调用lock()后必须调用unlock()释放锁。所以一段被锁保护的代码可以调用另一个被相同锁保护的代码。
考虑一种“生产者消费者问题”:一个银行账户,系统要求存款者和取款者不断地交替进行操作。
传统的线程通信:
为了实现这种功能,可以借助Object类的wait()、notify()、notifyAll()方法。注意这三个方法不属于Thread类,但必须由同步监视器对象调用。
对于使用synchronized修饰的同步方法,因为同步监视器就是this,因此可以直接调用这三个方法;对于使用synchronized修饰的代码块,同步监视器是synchronized括号里的对象,必须由这个对象调用这些方法。
//取钱方法,该方法是同步方法
public synchronized void draw(double drawAmount){
try{
//如果flag为假,表示还未存钱,取钱方法阻塞
if(!flag)
wait();
else{
System.out.println("取钱");
flag = false; //转换标志
notifyAll(); //唤醒其他线程
}
}catch(InterruptedException e){
ex.printStackTrace();
}
}
//存钱方法,该方法是同步方法
public synchronized void deposit(double depositAmount){
try{
if(flag)
wait();
else{
System.out.println("存钱");
flag = true;
notifyAll();
}
}catch(InterruptedException e){
ex.printStackTrace();
}
}
使用Condition控制线程通信:
如果程序不使用同步监视器而是Lock对象,那么就不能调用上述三个方法了。Java提供了Condition类来保持协调。Condition可以让那些已经得到Lock对象的线程释放Lock对象,也可以唤醒其他处于等待的线程。在这种情况下,Lock替代了同步方法或同步代码块,Condition替代了同步监视器的功能。
Condition实例被绑定在一个Lock对象上,要获得特定Lock对象实例的Condition实例,只需调用Lock实例的newCondition()方法。Condition类提供了如下三个方法:
public class Account{
...
//显式定义Lock实例
private final Lock lock = new ReentrantLock();
//获得指定Lock对象对应的Condition
private final Condition cond = lock.newCondition();
...
public void draw(double drawAmount){ //取钱方法
lock.lock(); //加锁
try{
if(!falg) //还未存钱时,取钱等待
cond.await();
else{
System.out.println("取钱");
flag = false; //转换标志
cond.signalAll(); //唤醒其他线程
}
}catch(InterruptedException e){
ex.printStackTrace();
}finally{
lock.unlock(); //释放锁
}
}
public void deposit(double drawAmount){ //存钱方法
lock.lock();
try{
if(falg)
cond.await();
else{
System.out.println("存钱");
flag = true;
cond.signalAll();
}
}catch(InterruptedException e){
ex.printStackTrace();
}finally{
lock.unlock();
}
}