程序中的乐观锁与悲观锁,以及动手实现乐观锁

概念:

这里抛开数据库来谈乐观锁和悲观锁,扯上数据库总会以为和Java离得很远.java

悲观锁:一段执行逻辑加上悲观锁,不一样线程同时执行时,只能有一个线程执行,其余的线程在入口处等待,直到锁被释放.数据库

乐观锁:一段执行逻辑加上乐观锁,不一样线程同时执行时,能够同时进入执行,在最后更新数据的时候要检查这些数据是否被其余线程修改了(版本和执行初是否相同),没有修改则进行更新,不然放弃本次操做.安全

从解释上能够看出,悲观锁具备很强的独占性,也是最安全的.而乐观锁很开放,效率高,安全性比悲观锁低,由于在乐观锁检查数据版本一致性时也可能被其余线程修改数据.从下面的例子中能够看出来这里说的安全差异.多线程

 

乐观锁例子:

package note.com;

/**
 * 乐观锁
 * 
 * 场景:有一个对象value,须要被两个线程调用,因为是共享数据,存在脏数据的问题
 * 悲观锁能够利用synchronized实现,这里不提.
 * 如今用乐观锁来解决这个脏数据问题
 * 
 * @author lxz
 *
 */
public class OptimisticLock {
    public static int value = 0; // 多线程同时调用的操做对象

    /**
     * A线程要执行的方法
     */
    public static void invoke(int Avalue, String i)
            throws InterruptedException {
        Thread.sleep(1000L);//延长执行时间
        if (Avalue != value) {//判断value版本
            System.out.println(Avalue + ":" + value + "A版本不一致,不执行");
            value--;
        } else {
            Avalue++;//对数据操做
            value = Avalue;;//对数据操做
            System.out.println(i + ":" + value);
        }
    }

    /**
     * B线程要执行的方法
     */
    public static void invoke2(int Bvalue, String i)
            throws InterruptedException {
        Thread.sleep(1000L);//延长执行时间
        if (Bvalue != value) {//判断value版本
            System.out.println(Bvalue + ":" + value + "B版本不一致,不执行");
        } else {
            System.out.println("B:利用value运算,value="+Bvalue);
        }
    }

    /**
     * 测试,期待结果:B线程执行的时候value数据老是当前最新的
     */
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {//A线程
            public void run() {
                try {
                    for (int i = 0; i < 3; i++) {
                        int Avalue = OptimisticLock.value;//A获取的value
                        OptimisticLock.invoke(Avalue, "A");
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {//B线程
            public void run() {
                try {
                    for (int i = 0; i < 3; i++) {
                        int Bvalue = OptimisticLock.value;//B获取的value
                        OptimisticLock.invoke2(Bvalue, "B");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

}

 

测试结果:eclipse

A:1
0:1B版本不一致,不执行
B:利用value运算,value=1
A:2
B:利用value运算,value=2
A:3

函数

从结果中看出,B线程在执行的时候最后发现本身的value和执行前不一致,说明被A修改了,那么放弃了本次执行.性能

 

多运行几回发现了下面的结果:测试

A:1
B:利用value运算,value=0
A:2
1:2B版本不一致,不执行
A:3
B:利用value运算,value=2spa


从结果看A修改了value值,B却没有检查出来,利用错误的value值进行了操做. 为何会这样呢? 线程

这里就回到前面说的乐观锁是有必定的不安全性的,B在检查版本的时候A尚未修改,在B检查完版本后更新数据前(例子中的输出语句),A更改了value值,这时B执行更新数据(例子中的输出语句)就发生了与现存value不一致的现象.

 

针对这个问题,我以为乐观锁要解决这个问题还须要在检查版本与更新数据这个操做的时候可以使用悲观锁,好比加上synchronized,让它在最后一步保证数据的一致性.这样既保证多线程都能同时执行,牺牲最后一点的性能去保证数据的一致.

 

补充

感谢评论中提出的cas方式解决乐观锁最后的安全问题,之前不知道cas(比较-交换)这个在java中的存在,找了找资料才发现java的concurrent包确实使用的cas实现乐观锁的数据同步问题.

下面是我对这两种方式的一点见解:

有两种方式来保证乐观锁最后同步数据保证它原子性的方法

1,CAS方式:Java非公开API类Unsafe实现的CAS(比较-交换),由C++编写的调用硬件操做内存,保证这个操做的原子性,concurrent包下不少乐观锁实现使用到这个类,但这个类不做为公开API使用,随时可能会被更改.我在本地测试了一下,确实不可以直接调用,源码中Unsafe是私有构造函数,只能经过getUnsafe方法获取单例,首先去掉eclipse的检查(非API的调用限制)限制之后,执行发现报 java.lang.SecurityException异常,源码中getUnsafe方法中执行访问检查,看来java不容许应用程序获取Unsafe类. 值得一提的是反射是能够获得这个类对象的.
2,加锁方式:利用Java提供的现有API来实现最后数据同步的原子性(用悲观锁).看似乐观锁最后仍是用了悲观锁来保证安全,效率没有提升.实际上针对于大多数只执行不一样步数据的状况,效率比悲观加锁整个方法要高.特别注意:针对一个对象的数据同步,悲观锁对这个对象加锁和乐观锁效率差很少,若是是多个须要同步数据的对象,乐观锁就比较方便.

 

扩展:利用反射得到Unsafe对象

第一步:去掉eclipse受限制的API检查:

将Windows->Preferences->Java-Complicer->Errors/Warnings->Deprecated and restricted API,中的Forbidden references(access rules)设置为Warning,Unsafe能够编译经过。

第二步:利用反射跳过安全检查获取Unsafe对象:

    Class<Unsafe> s1  = (Class<Unsafe>) Class.forName("sun.misc.Unsafe");
    Field u1 = s1.getDeclaredField("theUnsafe");//得到Unsafe的theUnsafe属性
    u1.setAccessible(true);//得到private属性的可访问权限
    Unsafe unsafe1 = (Unsafe) u1.get(null);//得到Class中属性对应的值
    System.out.println(unsafe1.addressSize());//测试获取的Unsafe对象
    //或者
    Field u = Unsafe.class.getDeclaredField("theUnsafe");
    u.setAccessible(true);
    Unsafe unsafe = (Unsafe) u.get(null);  
    System.out.println(unsafe.addressSize());//测试获取的Unsafe对象

关于Unsafe的使用方法给个参考地址,平时用不到,我没有去深刻看.

地址:Java Magic. Part 4: sun.misc.Unsafe

相关文章
相关标签/搜索