Java--线程同步&线程通信

2018-03-01 11:07:24来源:oschina作者:Superheros人点击

分享
上一篇--五态模型&控制线程
线程同步:
同步监视器(synchronized):

Java多线程引出了临界区问题。当两个进程并发修改同一资源时就有可能造成异常。Java引入同步监视器来解决这个问题。使用同步监视器有两种方法:同步代码块和同步方法。


同步代码块:


synchronized(obj){
//此处代码是同步代码块
}

上述代码的obj就是同步监视器,同一时刻只能有一个线程获得同步监视器的锁定,当同步代码块执行完毕后,该线程会自动释放该同步监视器的锁定。


通常使用可能被并发访问的共享资源作为同步监视器obj,如银行取款问题就用银行账户作为同步监视器。


同步方法:


public synchronized void drow(double drawAmount){
//同步方法体
}

使用synchronized来修饰某个方法,该方法就是同步方法。同步方法无需显式指定同步监视器,它的同步监视器就是this,也就是调用该方法的对象。


不可变类总是线程安全的,因为它的对象状态不可被改变;可变类对象的方法就需要使用额外的方法(如上述方法)来保证线程安全。


synchronized关键字可以修饰方法和代码块,但不可以修饰构造器、成员变量等。


可变类的线程安全是以牺牲运行效率为代价的,所以不要对线程安全类的所有方法都进行同步。如果可变类有两种运行环境--单线程和多线程,那么应该为该可变类提供两种版本,即线程不安全版本和线程安全版本。


同步监视器的释放


下面这些情况会释放同步监视器

同步方法、同步代码块执行结束;
线程在同步代码块或同步方法中遇到break、return终止执行;
线程在同步代码块或同步方法中出现了未处理的Error或Exception;
线程在同步代码块或同步方法中执行了同步监视器对象的wait()方法。

下面这些情况不会释放同步监视器

线程执行同步代码块或同步方法时,程序调用Thread.sleep()和Thread.yield()暂停线程的执行;
线程执行同步代码块时,其他线程调用了该线程的suspend()将该线程挂起。 同步锁(lock):

同步锁是比同步监视器更强大的线程同步机制----通过显式定义同步锁对象来实现同步,同步锁由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括号里的对象,必须由这个对象调用这些方法。


wait():导致该线程等待,知道其他线程调用该同步监视器的notify()或notifyAll()唤醒该线程。调用wait()方法的当前线程会释放对该同步监视器的锁定。
notify():唤醒在该同步监视器上等待的单个线程,如果多个线程在该同步监视器上等待,随机唤醒一个。只有当前线程放弃对该同步监视器的锁定后才可以执行被唤醒的线程。
notifyAll():唤醒所有在此同步监视器上等待的线程。只有当前线程放弃对该同步监视器的锁定后才可以执行被唤醒的线程。

//取钱方法,该方法是同步方法
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类提供了如下三个方法:


await(): 类似wait()
signal():类似notify()
signalAll(): 类似notifyAll()

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();
}
}

最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台