举一个经典的例子多线程对于全局volatile 变量的累加(防盗连接本文首发自http://www.cnblogs.com/jilodream/ )代码如下1 public class Main { 2 static volatile int count 0; 3 static final int TOTAL 10000; 4 5 public static void main(String[] args) throws InterruptedException { 6 Runnable r () - { 7 for (int i 0; i TOTAL; i) { 8 count; 9 } 10 }; 11 12 Thread t1 new Thread(r); 13 Thread t2 new Thread(r); 14 t1.start(); 15 t2.start(); 16 17 t1.join(); 18 t2.join(); 19 20 System.out.println(echo : count); 21 } 22 }这个代码的执行结果如下多次执行也基本不会达到目标值200001 Connected to the target VM, address: 127.0.0.1:54088, transport: socket 2 echo :13533 3 Disconnected from the target VM, address: 127.0.0.1:54088, transport: socket产生这个问题的原因是我们在处理自增操作时它不是原子性的。虽然两个线程对于这个变量的操作变化都是实时感知的读的都是是实时值但是计算和回写时可能就会出问题了。详细说下A 线程 将变量x自增为1B线程读取1 B线程计算1时得到结果是2注意此时2存在临时变量中在计算1时A线程已经继续自增变量x到2甚至3456...B线程回写临时结果到变量x 此时覆盖了A的操作x 又变为了2。此时B线程的操作就是中间状态执行期间被其它线程并发操作了。导致回写失败。那怎么解决呢最常规的办法就是加并发锁将并发的片段同步成一个整体(防盗连接本文首发自http://www.cnblogs.com/jilodream/ )执行期间不允许其它线程同步操作。这种常规的办法就是加锁这种锁通常是指无论并发是否发生我先加锁保证我在执行期间肯定不受到干扰。我们将这种时刻防护并发保护数据安全的锁称之为悲观锁。这就像是游客进入地铁闸机不管有没有其他游客准备并行进入。闸机通道每次只限一个人操作。闸机的旋转门旋转 加锁进入人 数据操作离开闸机进入景区 解锁像传统的 synchronized ReentrantLock 等锁都是悲观锁。都有典型的加锁解锁操作。悲观锁锁常用于竞争激烈的并发场景下。除了加并发锁还有啥办法呢还可以通过状态的变化来控制。就以我们这个例子来说。因为出现问题的本质时因为发生了并发我们只要判断并发有没有发生就可以。如果并发没发生我直接操作有没有锁无所谓如果并发发生了我看下对我的影响如果对我有影响我就认为这次操作失败了重新操作试下。我们观察有没有发生并发有两个点开始和结束点1如果在开始点观察其它线程有没有也同步读取数据。细想就发现这太难了(防盗连接本文首发自http://www.cnblogs.com/jilodream/ )首先你要并发的观察所有cpu核的线程有没有读数据这个挑战太大。而且别人可能只是简单的读取一下不操作或者即使你能观察到别的线程也可能先于你观察就已经读到数据了。这显然不可行的。2其次就是观察结束点数据有没有改变。别人怎么读无所谓。这种显然是可以的。我们只要监控变量的值发生变法了没有即可判断是否发生了并发从而判断是否可以继续写。如果线程读取的是1操作回写的结果是2新值它就可以在回写时判断下回写要覆盖的值是不是1旧值。如果是则覆盖写入如果不是则认为并发失败重新尝试写入或进行其它失败策略。这种通过判断并发是否发生才进行操作的方式我们称之为乐观锁。生活中像各种检票系统就是一个典型的乐观锁控制系统只要核实票是否有效然后更改状态即可并不涉及到锁定处理解锁的并发控制乐观并发控制一般分为三个步骤1读取 read2修改 modify 计算出目标值3校验并提交/写入 Validate Commit乐观锁常常用于低并发的场景中。因为它避免了悲观锁的状态切换因此它的性能在低并发时更高高并发下由于冲突较多会导致比较次数较多从而导致性能下降。我们业务中最常见的乐观锁一般是在数据库层面通过where 语句来实现比如下边这个语句
浅谈java中的悲观锁,乐观锁以及CAS操作
举一个经典的例子多线程对于全局volatile 变量的累加(防盗连接本文首发自http://www.cnblogs.com/jilodream/ )代码如下1 public class Main { 2 static volatile int count 0; 3 static final int TOTAL 10000; 4 5 public static void main(String[] args) throws InterruptedException { 6 Runnable r () - { 7 for (int i 0; i TOTAL; i) { 8 count; 9 } 10 }; 11 12 Thread t1 new Thread(r); 13 Thread t2 new Thread(r); 14 t1.start(); 15 t2.start(); 16 17 t1.join(); 18 t2.join(); 19 20 System.out.println(echo : count); 21 } 22 }这个代码的执行结果如下多次执行也基本不会达到目标值200001 Connected to the target VM, address: 127.0.0.1:54088, transport: socket 2 echo :13533 3 Disconnected from the target VM, address: 127.0.0.1:54088, transport: socket产生这个问题的原因是我们在处理自增操作时它不是原子性的。虽然两个线程对于这个变量的操作变化都是实时感知的读的都是是实时值但是计算和回写时可能就会出问题了。详细说下A 线程 将变量x自增为1B线程读取1 B线程计算1时得到结果是2注意此时2存在临时变量中在计算1时A线程已经继续自增变量x到2甚至3456...B线程回写临时结果到变量x 此时覆盖了A的操作x 又变为了2。此时B线程的操作就是中间状态执行期间被其它线程并发操作了。导致回写失败。那怎么解决呢最常规的办法就是加并发锁将并发的片段同步成一个整体(防盗连接本文首发自http://www.cnblogs.com/jilodream/ )执行期间不允许其它线程同步操作。这种常规的办法就是加锁这种锁通常是指无论并发是否发生我先加锁保证我在执行期间肯定不受到干扰。我们将这种时刻防护并发保护数据安全的锁称之为悲观锁。这就像是游客进入地铁闸机不管有没有其他游客准备并行进入。闸机通道每次只限一个人操作。闸机的旋转门旋转 加锁进入人 数据操作离开闸机进入景区 解锁像传统的 synchronized ReentrantLock 等锁都是悲观锁。都有典型的加锁解锁操作。悲观锁锁常用于竞争激烈的并发场景下。除了加并发锁还有啥办法呢还可以通过状态的变化来控制。就以我们这个例子来说。因为出现问题的本质时因为发生了并发我们只要判断并发有没有发生就可以。如果并发没发生我直接操作有没有锁无所谓如果并发发生了我看下对我的影响如果对我有影响我就认为这次操作失败了重新操作试下。我们观察有没有发生并发有两个点开始和结束点1如果在开始点观察其它线程有没有也同步读取数据。细想就发现这太难了(防盗连接本文首发自http://www.cnblogs.com/jilodream/ )首先你要并发的观察所有cpu核的线程有没有读数据这个挑战太大。而且别人可能只是简单的读取一下不操作或者即使你能观察到别的线程也可能先于你观察就已经读到数据了。这显然不可行的。2其次就是观察结束点数据有没有改变。别人怎么读无所谓。这种显然是可以的。我们只要监控变量的值发生变法了没有即可判断是否发生了并发从而判断是否可以继续写。如果线程读取的是1操作回写的结果是2新值它就可以在回写时判断下回写要覆盖的值是不是1旧值。如果是则覆盖写入如果不是则认为并发失败重新尝试写入或进行其它失败策略。这种通过判断并发是否发生才进行操作的方式我们称之为乐观锁。生活中像各种检票系统就是一个典型的乐观锁控制系统只要核实票是否有效然后更改状态即可并不涉及到锁定处理解锁的并发控制乐观并发控制一般分为三个步骤1读取 read2修改 modify 计算出目标值3校验并提交/写入 Validate Commit乐观锁常常用于低并发的场景中。因为它避免了悲观锁的状态切换因此它的性能在低并发时更高高并发下由于冲突较多会导致比较次数较多从而导致性能下降。我们业务中最常见的乐观锁一般是在数据库层面通过where 语句来实现比如下边这个语句