锁做为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized(重量级) 和 ReentrantLock(轻量级)等等 ) 。这些已经写好提供的锁为咱们开发提供了便利。java
重入锁,也叫作递归锁,指的是同一线程 外层函数得到锁以后 ,内层递归函数仍然有获取该锁的代码,但不受影响。mysql
在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁算法
synchronized:sql
public class Test implements Runnable { public synchronized void get() { System.out.println("name:" + Thread.currentThread().getName() + " get();"); } public synchronized void set() { System.out.println("name:" + Thread.currentThread().getName() + " set();"); get(); } @Override public void run() { set(); } public static void main(String[] args) { Test ss = new Test(); new Thread(ss).start(); new Thread(ss).start(); new Thread(ss).start(); new Thread(ss).start(); } }
ReentrantLock:数据库
public class Test02 extends Thread { ReentrantLock lock = new ReentrantLock(); public void get() { try{ lock.lock(); System.out.println(Thread.currentThread().getId()); }catch (Exception e){ }finally { lock.unlock(); } } public void set() { try{ lock.lock(); System.out.println(Thread.currentThread().getId()); get(); }catch (Exception e){ }finally { lock.unlock(); } } @Override public void run() { set(); } public static void main(String[] args) { Test ss = new Test(); new Thread(ss).start(); new Thread(ss).start(); new Thread(ss).start(); } }
正常状况下在函数自行完毕后会释放锁,以上两种方式均在已得到锁的函数中从新调用了须要锁的函数,若是没有可重入性,那么调用新的须要锁的函数时,将会形成死锁。可重用锁在递归调用中很是重要。编程
程序中涉及到对一些共享资源的读和写操做,且写操做没有读操做那么频繁。在没有写操做的时候,两个线程同时读一个资源没有任何问题,因此应该容许多个线程能在同时读取共享资源。安全
可是若是有一个线程想去写这些共享资源,就不该该再有其它线程对该资源进行读或写(也就是说:读-读能共存,读-写不能共存,写-写不能共存)。数据结构
这就须要一个读/写锁来解决这个问题。Java5在java.util.concurrent包中已经包含了读写锁。并发
public class Cache { static Map<String, Object> map = new HashMap<String, Object>(); static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); static Lock r = rwl.readLock(); static Lock w = rwl.writeLock(); // 获取一个key对应的value public static final Object get(String key) { try { r.lock(); System.out.println("正在作读的操做,key:" + key + " 开始"); Thread.sleep(100); Object object = map.get(key); System.out.println("正在作读的操做,key:" + key + " 结束"); System.out.println(); return object; } catch (InterruptedException e) { } finally { r.unlock(); } return key; } // 设置key对应的value,并返回旧有的value public static final Object put(String key, Object value) { try { w.lock(); System.out.println("正在作写的操做,key:" + key + ",value:" + value + "开始."); Thread.sleep(100); Object object = map.put(key, value); System.out.println("正在作写的操做,key:" + key + ",value:" + value + "结束."); System.out.println(); return object; } catch (InterruptedException e) { } finally { w.unlock(); } return value; } // 清空全部的内容 public static final void clear() { try { w.lock(); map.clear(); } finally { w.unlock(); } } public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { Cache.put(i + "", i + ""); } } }).start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { Cache.get(i + ""); } } }).start(); } }
老是认为不会产生并发问题,每次去取数据的时候总认为不会有其余线程对数据进行修改,所以不会上锁,可是在更新时会判断其余线程在这以前有没有对数据进行修改,通常会使用版本号机制或CAS操做实现。ide
version方式:通常是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,不然重试更新操做,直到更新成功。
核心SQL语句:
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
CAS操做方式:即compare and swap 或者 compare and set,涉及到三个操做数,数据所在的内存值,预期值,新值。当须要更新时,判断当前内存值与以前取到的值是否相等,若相等,则用新值更新,若失败则重试,通常状况下是一个自旋操做,即不断的重试。
老是假设最坏的状况,每次取数据时都认为其余线程会修改,因此都会加锁(读锁、写锁、行锁等),当其余线程想要访问数据时,都须要阻塞挂起。能够依靠数据库实现,如行锁、读锁和写锁等,都是在操做以前加锁,在Java中,synchronized的思想也是悲观锁。
java.util.concurrent.atomic包:原子类的小工具包,支持在单个变量上解除锁的线程安全编程
原子变量类至关于一种泛化的 volatile 变量,可以支持原子的和有条件的读-改-写操做。
AtomicInteger 表示一个int类型的值,并提供了 get 和 set 方法,这些 Volatile 类型的int变量在读取和写入上有着相同的内存语义。它还提供了一个原子的 compareAndSet 方法(若是该方法成功执行,那么将实现与读取/写入一个 volatile 变量相同的内存效果),以及原子的添加、递增和递减等方法。AtomicInteger 表面上很是像一个扩展的 Counter 类,但在发生竞争的状况下能提供更高的可伸缩性,由于它直接利用了硬件对并发的支持。
为何会有原子类 CAS:Compare and Swap,即比较再交换。
jdk5增长了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。相似于mysql中的version字段的乐观锁
若是同一个变量要被多个线程访问,则可使用该包中的类
CAS:Compare and Swap,即比较再交换。 jdk5增长了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。JDK 5以前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。
CAS存在一个很明显的问题,即ABA问题。
问题:若是变量V初次读取的时候是A,而且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其余线程修改过了吗?
若是在这段期间曾经被改为B,而后又改回A,那CAS操做就会误认为它历来没有被修改过。针对这种状况,java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它能够经过控制变量值的版原本保证CAS的正确性。
我的博客 蜗牛