JAVA锁机制

来源:互联网 发布:linux系统管理员招聘 编辑:程序博客网 时间:2024/05/24 23:13


多线程和并发性并不是Java的什么新内容,Java封装了与线程相关的类库,核心类库包含一个 Thread 类,可以用它来构建、启动和操纵线程。当然Java官方更推荐的是通过实现Runnable方法实现多线程,因为他更容易使用,可读性更强,而且可以复用线程池带来更高的性能。

Java 语言包括了跨线程传达并发性约束的构造—— synchronized 和 volatile 。在简化与平台无关的并发类的开发的同时,它决没有使并发类的编写工作变得更繁琐,只是使它变得更容易了。

多线程编程必然涉及到多线程线程同步问题。java提供了多种处理线程同步问题的方法。

1、synchronized关键字

synchronized关键字是最常用的一种方法,确实性能、效果最差的方法,但是其可读性很强,任然被广泛使用着。

Java中的每一个对象都可以作为锁。

对于同步方法,锁是当前实例对象。

对于静态同步方法,锁是当前对象的Class对象。

对于同步方法块,锁是synchonized括号里配置的对象。

JVM规范规定JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有详细说明,但是方法的同步同样可以使用这两个指令来实现。monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处, JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个 monitor 与之关联,当且一个monitor 被持有后,它将处于锁定状态。线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 monitor 的所有权,即尝试获得对象的锁。

比较常用的是修饰方法,使一个方法可以互斥调用:

 

/** * Created by jpbirdy on15-1-22. */ package jpbirdy.thread.lock; import java.util.concurrent.locks.ReentrantLock; /** * @author jpbirdy * @project JavaAlgorithms * @class LockTest * @date 15-1-22 13:35 * @desc */public class SynchronizedLockTest{    private int total = 0;    //修饰方法的线程同步关键字,提供方法级别的锁    public synchronized voidadd() throws InterruptedException    {        total ++ ;        Thread.sleep(1000);    }     public int get(){returntotal;}     public static class InnerRunnable implements Runnable    {        privateSynchronizedLockTest synchronizedLockTest;        private int threadId;         publicInnerRunnable(int threadId , SynchronizedLockTest synchronizedLockTest)        {           this.synchronizedLockTest = synchronizedLockTest;            this.threadId =threadId;        }         public InnerRunnablesetSynchronizedLockTest(SynchronizedLockTest synchronizedLockTest)        {           this.synchronizedLockTest = synchronizedLockTest;            return this;        }         @Override        public void run()        {            try            {               System.out.println(synchronizedLockTest.get());               synchronizedLockTest.add();            }            catch(InterruptedException e)            {               e.printStackTrace();            }           System.out.println("thread " + threadId + "addover");        }    }     public static voidmain(String[] args) throws Exception    {         SynchronizedLockTestsynchronizedLockTest = new SynchronizedLockTest();        for(int i=0 ; i<10; i++)        {            new Thread(newInnerRunnable(i, synchronizedLockTest)).start();            Thread.sleep(1);        }    }}

 

运行显示

 

0111111111thread 0add overthread 9add overthread 8add overthread 7add overthread 6add overthread 5add overthread 4add overthread 3add overthread 2add overthread 1add over

get并没有加锁,但set方法加了锁,所以每个thread都会等待每个add方法执行sleep方法结束后才能重新进入。

 

对于方法中存在多个区块,但仅想对某一个部分作为临界区参与竞争,其他部分是不会有线程同步问题的,则可以用sychronized块实现:

/** * Created by jpbirdy on 15-1-22. */package jpbirdy.thread.lock;import java.util.concurrent.locks.ReentrantLock;/** * @author jpbirdy * @project JavaAlgorithms * @class LockTest * @date 15-1-22 13:35 * @desc */public class SynchronizedLockTest{    private ReentrantLock lock = new ReentrantLock();    private int total = 0;    public int get(){return total;}    //代码块锁    public void add2() throws InterruptedException    {        System.out.println("out of synchronized");        synchronized (this)        {            total ++ ;            Thread.sleep(1000);        }    }    public static class InnerRunnable implements Runnable    {        private SynchronizedLockTest synchronizedLockTest;        private int threadId;        public InnerRunnable(int threadId , SynchronizedLockTest synchronizedLockTest)        {            this.synchronizedLockTest = synchronizedLockTest;            this.threadId = threadId;        }        public InnerRunnable setSynchronizedLockTest(SynchronizedLockTest synchronizedLockTest)        {            this.synchronizedLockTest = synchronizedLockTest;            return this;        }        @Override        public void run()        {            try            {//                System.out.println(synchronizedLockTest.get());                synchronizedLockTest.add2();            }            catch (InterruptedException e)            {                e.printStackTrace();            }            System.out.println("thread " + threadId + "add over");        }    }    public static void main(String[] args) throws Exception    {        SynchronizedLockTest synchronizedLockTest = new SynchronizedLockTest();        for(int i=0 ; i<10 ; i++)        {            new Thread(new InnerRunnable(i, synchronizedLockTest)).start();            Thread.sleep(1);        }    }}

结果为:

out of synchronizedout of synchronizedout of synchronizedout of synchronizedout of synchronizedout of synchronizedout of synchronizedout of synchronizedout of synchronizedout of synchronizedthread 0add overthread 9add overthread 8add overthread 7add overthread 6add overthread 5add overthread 4add overthread 3add overthread 2add overthread 1add over

仅有add和sleep处于线程同步块中。

当然理论上sychronized也可以修饰一个类(对象名、类名.class或this表示自己),进行对类整个类的异步调用。这样可以实现当某个修改操作进行时,获取操作可以进行等待避免读到脏数据(DBMS?在JDBC中如果要实现JAVA层的读写异步,可以对Connection对象加锁,在Hibernate中则对应对Session对象加锁)

/** * Created by jpbirdy on15-1-22. */ package jpbirdy.thread.lock; /** * @author jialou.jp * @project JavaAlgorithms * @class LockTest * @date 15-1-22 13:35 * @desc */public class ObjectLockTest{    private int total = 0;     //修饰对象的线程同步关键字,提供方法级别的锁    public void add() throwsInterruptedException    {       System.out.println("in add" + Thread.currentThread().getId());        synchronized (this)        {           System.out.println("in add" + Thread.currentThread().getId() +"get the clock");            total ++ ;           Thread.sleep(1000);           System.out.println("in add" + Thread.currentThread().getId() +"release the clock");        }    }     public int get() throwsInterruptedException    {        System.out.println("in get" +Thread.currentThread().getId());        int total = 0;        synchronized (this)        {           System.out.println("in get" + Thread.currentThread().getId() +"get the clock");//            return total;            total = this.total;           Thread.sleep(1000);           System.out.println("in get" + Thread.currentThread().getId() +"release th clock");        }        return total;    }     public static class InnerRunnableimplements Runnable    {        private ObjectLockTestsynchronizedLockTest;        private int threadId;         publicInnerRunnable(int threadId , ObjectLockTest synchronizedLockTest)        {           this.synchronizedLockTest = synchronizedLockTest;            this.threadId =threadId;        }         public InnerRunnablesetSynchronizedLockTest(ObjectLockTest synchronizedLockTest)        {           this.synchronizedLockTest = synchronizedLockTest;            return this;        }         @Override        public void run()        {            try            {               System.out.println(synchronizedLockTest.get());               synchronizedLockTest.add();            }            catch(InterruptedException e)            {               e.printStackTrace();            }           System.out.println("thread " + threadId + "addover");        }    }     public static voidmain(String[] args) throws Exception    {         ObjectLockTestsynchronizedLockTest = new ObjectLockTest();        for(int i=0 ; i<10; i++)        {            new Thread(newInnerRunnable(i, synchronizedLockTest)).start();            Thread.sleep(1);        }    }}

结果为:

in get11in get11get the clockin get12in get13in get14in get15in get16in get17in get18in get19in get20in get11release th clock0in get20get the clockin add11in get20release th clock0in get19get the clockin add20in get19release th clock0in add19in get18get the clockin get18release th clock0in add18in get17get the clockin get17release th clock0in add17in get16get the clockin get16release th clock0in add16in get15get the clockin get15release th clock0in add15in get14get the clockin get14release th clock0in add14in get13get the clockin get13release th clock0in add13in get12get the clockin get12release th clock0in add12in add13get the clockin add13release the clockthread 2add overin add14get the clockin add14release the clockthread 3add overin add15get the clockin add15release the clockthread 4add overin add16get the clockin add16release the clockthread 5add overin add17get the clockin add17release the clockthread 6add overin add18get the clockin add18release the clockthread 7add overin add19get the clockin add19release the clockthread 8add overin add20get the clockin add20release the clockthread 9add overin add11get the clockin add11release the clockthread 0add overin add12get the clockin add12release the clockthread 1add over


线程中,get和add会交替获得(实际上存在顺序,这与资源竞争调度有关)锁,并执行。

值得一提的是,sychronized是一个JVM级别的操作,所以不论同步块中的代码执行结果如何,代码块执行完成之后必然会释放对于的锁,让其他线程获得执行权力。

2、ReentrantLock锁

在JDK1.5之后,出现了一种代码级别的锁(java.util.concurrent.lock),这种方式类似于操作系统的metex操作。在对象中建立一个ReentrantLock对象,在需要进行资源竞争时,通过lock.lock()方法可以实现线程同步。

一个类中可以有多个ReentrantLock对象,以标识多个零界资源,实现多个资源多线程竞争操作。

/** *Created by jpbirdy on 15-1-22. */ package jpbirdy.thread.lock; importjava.util.concurrent.locks.ReentrantLock; /** *@author jialou.jp *@project JavaAlgorithms *@class ReentrantLockTest *@date 15-1-22 14:38 *@desc */public class ReentrantLockTest{   private ReentrantLock lock = new ReentrantLock();    private int total = 0;    public void add() throws InterruptedException    {       lock.lock();       total ++;       Thread.sleep(1000);       lock.unlock();    }    public static class InnerRunnable implements Runnable    {       private ReentrantLockTest reentrantLockTest;        public InnerRunnable(ReentrantLockTest reentrantLockTest)       {           this.reentrantLockTest = reentrantLockTest;       }        @Override       public void run()       {           try           {                reentrantLockTest.add();           }           catch (InterruptedException e)           {                e.printStackTrace();           }           System.out.println("add over");       }    }     public static void main(String[] args) throws Exception    {       ReentrantLockTest reentrantLockTest = new ReentrantLockTest();       for(int i=0 ; i<10 ; i++)       {           new Thread(new InnerRunnable(reentrantLockTest)).start();           Thread.sleep(1);       }    }}

 

结果如下:

add overadd overadd overadd overadd overadd overadd overadd overadd overadd over


著名的ConcurrentHashMap就是通过ReentrantLock实现线程同步的。

3、区别

从灵活性上来说,ReentrantLock的灵活性必然更高。因为该锁是代码级别实现的,可以随时释放,也可以再获取。

例如在程序中,当代码执行到某个步骤(比如某个初始化完成之后),后面的步骤可以不用着急做,就可以暂时将锁释放,同时调用yield方法让线程重新进行调度。如果系统任务现在又更需要获得CPU资源的线程,则他在此时也可以获得锁进行操作,否则资源的调度就没有意义。

而在sychronized中,一旦线程获得运行权(即锁),将必须消费直到块/方法结束,也就是说在此时,无论操作系统做如何调度,其他线程都无法获得锁而去得方法的运行权。

但sychronized的好处就是可读性强。而且其实在JVM上实现的,可以理解为使一个轻量级的锁控制模式。在任务不是非常复杂的情况下应该是有限使用sychronized方法实现线程同步。同时上文也提到,sychronized是JVM级别的一种机制,所以无论sychronized块中执行的结果如何,都将释放该锁。而ReentrantLock就不同,由于其实代码级别的锁,一旦代码中抛出异常,这将导致整个代码块block掉,unlock方法就一定不会执行了,所以在使用ReentrantLock的时候,习惯上会将lock以下的内容用try-catch-finally包住,并将unlock方法放到finally中。否则容易引起死锁。

另外需要注意的就是效率。这里引用一段:

比较ReentrantLock 和 synchronized 的可伸缩性

Tim Peierls 用一个简单的线性全等伪随机数生成器(PRNG)构建了一个简单的评测,用它来测量 synchronized 和 Lock 之间相对的可伸缩性。这个示例很好,因为每次调用 nextRandom() 时,PRNG 都确实在做一些工作,所以这个基准程序实际上是在测量一个合理的、真实的 synchronized 和 Lock 应用程序,而不是测试纯粹纸上谈兵或者什么也不做的代码(就像许多所谓的基准程序一样。)

在这个基准程序中,有一个PseudoRandom 的接口,它只有一个方法 nextRandom(int bound) 。该接口与 java.util.Random类的功能非常类似。因为在生成下一个随机数时,PRNG 用最新生成的数字作为输入,而且把最后生成的数字作为一个实例变量来维护,其重点在于让更新这个状态的代码段不被其他线程抢占,所以我要用某种形式的锁定来确保这一点。( java.util.Random 类也可以做到这点。)我们为 PseudoRandom 构建了两个实现;一个使用 syncronized,另一个使用 java.util.concurrent.ReentrantLock 。驱动程序生成了大量线程,每个线程都疯狂地争夺时间片,然后计算不同版本每秒能执行多少轮。图 1 和图 2 总结了不同线程数量的结果。这个评测并不完美,而且只在两个系统上运行了(一个是双 Xeon 运行超线程 Linux,另一个是单处理器 Windows 系统),但是,应当足以表现 synchronized 与 ReentrantLock 相比所具有的伸缩性优势了。

 

图 1 和图 2 中的图表以每秒调用数为单位显示了吞吐率,把不同的实现调整到 1 线程 synchronized 的情况。每个实现都相对迅速地集中在某个稳定状态的吞吐率上,该状态通常要求处理器得到充分利用,把大多数的处理器时间都花在处理实际工作(计算机随机数)上,只有小部分时间花在了线程调度开支上。您会注意到,synchronized 版本在处理任何类型的争用时,表现都相当差,而 Lock 版本在调度的开支上花的时间相当少,从而为更高的吞吐率留下空间,实现了更有效的 CPU 利用。

综合分析,ReentrantLock拥有更高的灵活性,在多个资源竞争同一个锁时,其性能将高于sychronized块。实验也证明,在线程增大的情况下sychronized块的执行效率会急剧下降。

各方面来看,ReentrantLock是优于sychronized块的,但在资源竞争不激烈的情况下,性能并没有出现瓶颈时还是应当优先使用sychronized块,增加代码的可读性。但当我们需要一个可自由定制,可以投票获得锁或更加灵活的锁控制时,可以毫不犹豫选择ReentrantLock进行控制。

5次真言。Do It Best, Then fastest!

0 0
原创粉丝点击