同步——synchronized机制、Lock和Conditon机制和关于线程中断

      Java在java SE 5.0引入了不同与synchronized的另一种加锁方式,java.util.concurrent.Locks包中的两个锁类:ReetrantLock类,为可重入锁。

还有读写锁ReentrantReadWriteLock.


首先介绍synchronized关键字和内部锁。

       首先理解每个对象都有一个内部锁,如果一个方法用synchronized声明,那么该对象的锁将保护整个方法,也就是说线程要调用该方法,必须获得内部的对象锁。与Lock和Condition相比,内部对象锁只有一个相关条件和一个等待集,Object的wait方法添加一个线程到等待集,notify/notifyAll解除等待线程的阻塞状态。

由锁来管理进入synchronized方法的线程,由条件来管理那些调用了wait的线程。

       将static方法声明为synchronized也是合法的,该方法会调用相关的类对象(该类的class对象)的内部锁。

注意:wait,notify,notifyAll是Object类的final方法。Condition方法必须被命名为await、signal、signalAll以便不发生冲突。

java.util.concurrent.locks 

接口Lock

Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的Condition 对象(一个锁可以拥有一个或多个相关的条件对象,用newCondition方法获得一个条件对象)。 

接口 Condition

ConditionObject 监视器方法(waitnotifynotifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock 实现组合使用,每个条件有自己的等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用(await、signal 和 signalAll)。  

Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。

作为一个示例,假定有一个绑定的缓冲区,它支持 puttake 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存put 线程和take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个Condition 实例来做到这一点。

 class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length) 
         notFull.await();
       items[putptr] = x; 
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0) 
         notEmpty.await();
       Object x = items[takeptr]; 
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }


ReetrantLock类,为可重入锁

锁是可重入的,锁保持一个持有计数器(holdcount)来跟踪一个线程对lock方法的嵌套调用,线程应保证每一次调用lock都要调用unlock方法来释放锁。由于可重入特性,被一个锁保护的对象可以使用另一个使用了相同的锁的方法。

此类的构造方法接受一个可选的公平 参数。当设置为 true 时,在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程。否则此锁将无法保证任何特定访问顺序。与采用默认设置(使用不公平锁)相比,使用公平锁的程序在许多线程访问时表现为很低的总体吞吐量(即速度很慢,常常极其慢),但是在获得锁和保证锁分配的均衡性时差异较小。不过要注意的是,公平锁不能保证线程调度的公平性。


ReentrantReadWriteLock读写锁

      很多线程从一个数据结构读取数据而很少修改其中的数据,那么允许对读者线程共享访问是合适的,但是写者线程依然必须是互斥访问的。

      下面时使用读/写锁的必要步骤:

      1.构造一个读写锁ReentrantReadWriteLock对象

        private ReentrantReadWriteLock rwl=new ReentrantReadWriteLock();

      2.抽取读锁和写锁:读写锁为ReentrantReadWriteLock的内部类

       private Lock readLock=rwl.readLock();
       private Lock writeLock=rwl.writeLock();

      readLock():得到一个可以被多个读操作共用的读锁,排斥所有写操作。

      writeLock()得到一个写锁,排斥所有其他读操作和写操作。

3.对所有的获取方法加读锁或写锁:

public double read()
{
	readLock.lock();
	try{   do something ...}
	finally{readLock.unlock();}
	}
public double write()
{
	writeLock.lock();
	try{   do something ...}
	finally{writeLock.unlock();}
	}

把解锁操作放在finally子句之内是至关重要的,如果在临界区的代码抛出异常,锁必须被释放。否则,其他线程将永远阻塞。


读写锁应用的场合

我们有时会遇到对同一个内存区域如数组或者链表进行多线程读写的情况,一般来说有以下几种处理方式: 1.不加任何限制,多见于读取写入都很快的情况,但有时也会出现问题. 2.对读写函数都加以同步互斥,这下问题是没了,但效率也下去了,比如说两个读取线程不是非要排队进入不可. 3.使用读写锁,安全和效率都得到了解决,特别合适读线程多于写线程的情况.也就是下面将要展现的模式.

读写锁的意图

读写锁的本意是分别对读写状态进行互斥区分,有互斥时才加锁,否则放行.互斥的情况有: 1.读写互斥. 2.写写互斥. 不互斥的情况是:读读,这种情况不该加以限制. 程序就是要让锁对象知道当前读写状态,再根据情况对读写的线程进行锁定和解锁。


小结

当多个线程试图对同一内容进行读写操作时适合使用读写锁。
请理解并记住ReadWriteLock类读写锁的写法.
读写锁相对于线程互斥的优势在于高效,它不会对两个读线程进行盲目的互斥处理,当读线程数量多于写线程尤其如此,当全是写线程时两者等效。


Stop,suspend,resume被弃用的原因

stop终止所有未结束的方法,包括run方法。当线程被终止时,立即释放被他锁住的所有对象锁。这会导致对象处于不一致状态。

在希望停止线程的时候应该中断线程,被中断的线程会在安全的时候停止。

suspend挂起线程不释放持有的锁,容易造成死锁。


关于线程中断

http://blog.csdn.net/sunxing007/article/details/9123363

一个线程在未正常结束之前, 被强制终止是很危险的事情. 因为它可能带来完全预料不到的严重后果. 所以你看到Thread.suspend, Thread.stop等方法都被Deprecated了.
那么不能直接把一个线程搞挂掉, 但有时候又有必要让一个线程死掉, 或者让它结束某种等待的状态 该怎么办呢? 优雅的方法就是, 给那个线程一个中断信号, 让它自己决定该怎么办. 比如说, 在某个子线程中为了等待一些特定条件的到来, 你调用了Thread.sleep(10000), 预期线程睡10秒之后自己醒来, 但是如果这个特定条件提前到来的话, 你怎么通知一个在睡觉的线程呢? 又比如说, 主线程通过调用子线程的join方法阻塞自己以等待子线程结束, 但是子线程运行过程中发现自己没办法在短时间内结束, 于是它需要想办法告诉主线程别等我了. 这些情况下, 就需要中断. 
中断是通过调用Thread.interrupt()方法来做的. 这个方法通过修改了被调用线程的中断状态来告知那个线程, 说它被中断了. 对于非阻塞中的线程, 只是改变了中断状态, 即Thread.isInterrupted()将返回true; 对于可取消的阻塞状态中的线程, 比如等待在这些函数上的线程, Thread.sleep(), Object.wait(), Thread.join(), 这个线程收到中断信号后, 会抛出InterruptedException, 同时会把中断状态置回为false.
下面的程序会演示对非阻塞中的线程中断:

[java]  view plain  copy
  1. public class Thread3 extends Thread{  
  2.     public void run(){  
  3.         while(true){  
  4.             if(Thread.interrupted()){  
  5.                 System.out.println("Someone interrupted me.");  
  6.             }  
  7.             else{  
  8.                 System.out.println("Going...");  
  9.             }  
  10.             long now = System.currentTimeMillis();  
  11.             while(System.currentTimeMillis()-now<1000){  
  12.                 // 为了避免Thread.sleep()而需要捕获InterruptedException而带来的理解上的困惑,  
  13.                 // 此处用这种方法空转1秒  
  14.             }  
  15.         }  
  16.     }  
  17.       
  18.     public static void main(String[] args) throws InterruptedException {  
  19.         Thread3 t = new Thread3();  
  20.         t.start();  
  21.         Thread.sleep(3000);  
  22.         t.interrupt();  
  23.     }  
  24. }  
下面的程序演示的是子线程通知父线程别等它了:
[java]  view plain  copy
  1. public class Thread4 extends Thread {  
  2.     private Thread parent;  
  3.     public Thread4(Thread parent){  
  4.         this.parent = parent;  
  5.     }  
  6.       
  7.     public void run() {  
  8.         while (true) {  
  9.             System.out.println("sub thread is running...");  
  10.             long now = System.currentTimeMillis();  
  11.             while (System.currentTimeMillis() - now < 2000) {  
  12.                 // 为了避免Thread.sleep()而需要捕获InterruptedException而带来的理解上的困惑,  
  13.                 // 此处用这种方法空转2秒  
  14.             }  
  15.             parent.interrupt();  
  16.         }  
  17.     }  
  18.       
  19.     public static void main(String[] args){  
  20.         Thread4 t = new Thread4(Thread.currentThread());  
  21.         t.start();  
  22.         try {  
  23.             t.join();  
  24.         } catch (InterruptedException e) {  
  25.             System.out.println("Parent thread will die...");  
  26.         }  
  27.     }  
  28. }  
中断状态可以通过 Thread.isInterrupted()来读取,并且可以通过一个名为 Thread.interrupted()的静态方法读取和清除状态(即调用该方法结束之后, 中断状态会变成false)。
由于 处于阻塞状态的线程 被中断后抛出exception并置回中断状态, 有时候是不利的, 因为这个中断状态可能会作为别的线程的判断条件, 所以稳妥的办法是在处理exception的地方把状态复位:
[java]  view plain  copy
  1. boolean interrupted = false;  
  2. try {  
  3.     while (true) {  
  4.         try {  
  5.             return blockingQueue.take();  
  6.         } catch (InterruptedException e) {  
  7.             interrupted = true;  
  8.         }  
  9.     }  
  10. finally {  
  11.     if (interrupted){  
  12.         Thread.currentThread().interrupt();  
  13.     }  
  14. }  

当代码调用中须要抛出一个InterruptedException, 你可以选择把中断状态复位, 也可以选择向外抛出InterruptedException, 由外层的调用者来决定.

不是所有的阻塞方法收到中断后都可以取消阻塞状态, 输入和输出流类会阻塞等待 I/O 完成,但是它们不抛出 InterruptedException,而且在被中断的情况下也不会退出阻塞状态. 

尝试获取一个内部锁的操作(进入一个 synchronized 块)是不能被中断的,但是 ReentrantLock 支持可中断的获取模式即 tryLock(long time, TimeUnit unit)。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值