java锁
简介:
锁一般来说用作资源控制,限制资源访问,防止在并发环境下造成数据错误
1.重入锁
目的:避免死锁的现象
锁作为并发共享数据,保证一致性的工具,在java平台有多种实现synchronized(重量级)和ReentrantLock(轻量级)等等,这些已经写好提供的锁为我们开发提供了便利;
重入锁:也叫作递归所,指的是同一线程外层函数获得锁之后,内层递归函数仍然有取该锁的代码,但不受影响;;
在java环境下,ReentrantLock和synchronized都是可重入锁;
不可重入锁:
public class MyLock { //标识锁是否可用 如果值为true代表当前有线程正在使用该锁,如果为false代表没有人用锁 private boolean isLocked=false; //获取锁:加锁 public synchronized void lock() throws InterruptedException { //判断当前该锁是否正在使用 while (isLocked){ wait(); } //当前没有人使用情况下就占用该锁 isLocked=true; } //释放锁 public synchronized void unLock(){ //将当前锁资源释放 isLocked=false; //唤起正在等待使用锁的线程 notify(); } }
public class MyLockTest { MyLock myLock=new MyLock(); //A业务方法 public void print() throws InterruptedException { //获取一把锁 myLock.lock(); System.out.println("print业务方法"); doAdd(); //释放锁 myLock.unLock(); } //B业务方法 public void doAdd() throws InterruptedException { //获取一把锁 myLock.lock(); System.out.println("doAdd业务方法"); //释放锁 myLock.unLock(); } public static void main(String[] args) throws InterruptedException { MyLockTest test=new MyLockTest(); test.print(); } }
synchronized可重入性:
public class MyLockTest { //A业务方法 public synchronized void print() throws InterruptedException { //获取了一把锁 System.out.println("print业务方法"); doAdd(); } //B业务方法 public synchronized void doAdd() throws InterruptedException { System.out.println("doAdd业务方法"); //释放锁 } public static void main(String[] args) throws InterruptedException { MyLockTest test=new MyLockTest(); test.print(); } }
ReentrantLock可重入性:
public class MyLockTest { //创建锁对象 Lock lock=new ReentrantLock(); //A业务方法 public void print() throws InterruptedException { //获取了一把锁 lock.lock(); System.out.println("print业务方法"); doAdd(); //释放锁 lock.unlock(); } //B业务方法 public void doAdd() throws InterruptedException { //获取了一把锁 lock.lock(); System.out.println("doAdd业务方法"); //释放锁 lock.unlock(); } public static void main(String[] args) throws InterruptedException { MyLockTest test=new MyLockTest(); test.print(); } }
ReentrantLock底层:
public ReentrantLock() { //默认非公平锁 sync = new NonfairSync(); } public ReentrantLock(boolean fair) { //如果为true代表公平锁,否则为非公平锁 sync = fair ? new FairSync() : new NonfairSync(); } public class MyReentrantLock { //标识锁是否可用 如果值为true代表当前有线程正在使用该锁,如果为false代表没有人用锁 private boolean isLocked=false; //当前线程 Thread lockedBy=null; //加锁数量计数 Integer lockedCount=0; //加锁 public synchronized void lock() throws InterruptedException { //获取当前线程 Thread thread=Thread.currentThread(); //判断当前是否正在使用锁,如果正在使用则对比当前使用要使用锁的线程和之前使用锁的线程是否一致 //如果一致代表可以重入,继续使用锁,不会发生阻塞 //如果不一致代表当前不是一个线程,则等待 while (isLocked && thread!=lockedBy){ wait(); } //占用锁 isLocked=true; //计数+1 lockedCount++; //赋值线程 lockedBy=thread; } //释放锁 public synchronized void unlock(){ //判断当前是否是用一个线程 if(Thread.currentThread()==this.lockedBy){ //锁使用计数器-1 lockedCount--; //判断计数器是否为0,如果为0则释放锁,然后唤醒正在等待的线程 if(lockedCount==0){ isLocked=false; notify(); } } } }
2.读写锁
相比java中的锁(Lock in java)里Lock实现,读写锁更复杂一些;
假设你的程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁。在没有写操作的时候,两个线程同时第一个资源没有任务问题,所以应该允许多个线程能在同时读取共享资源。但是如果有一个线程想去写这些共享资源,就不应该再有其他线程对该资源进行读或写(也就是说:读和读能共享,读和写不能共享,写和写不能共享)。这就需要一个读写锁来解决这个问题。在java5中java.util.concurrent包中已经包含读写锁;
public class ReadWriteLock { //创建一个集合 static Map<String,String> map=new HashMap<String,String>(); //创建一个读写锁 static ReentrantReadWriteLock lock=new ReentrantReadWriteLock(); //获取读锁 static Lock readLock=lock.readLock(); //获取写锁 static Lock writeLock=lock.writeLock(); //写操作 public Object put(String key,String value){ writeLock.lock(); try { System.out.println("Write正在执行写操作~"); Thread.sleep(100); String put = map.put(key, value); System.out.println("Write写操作执行完毕~"); return put; } catch (InterruptedException e) { e.printStackTrace(); }finally { writeLock.unlock(); } return null; } //写操作 public Object get(String key){ readLock.lock(); try { System.out.println("Read正在执行读操作~"); Thread.sleep(100); String value = map.get(key); System.out.println("Read读操作执行完毕~"); return value; } catch (InterruptedException e) { e.printStackTrace(); }finally { readLock.unlock(); } return null; } public static void main(String[] args) { ReadWriteLock lock=new ReadWriteLock(); for (int i = 0; i < 10; i++) { int finalI = i; new Thread(()->{ try { //写操作 lock.put(finalI +"","value"+finalI); //读操作 System.out.println(lock.get(finalI+"")); } catch (Exception e) { e.printStackTrace(); } }).start(); } } }
3.乐观锁
乐观锁总是任务不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他新城在这之前有没有对数据进行修改,一般会使用版本号或CAS操作实现;
乐观锁:本质没有锁,效率高,无阻塞,无等待,重试;
version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时夜壶读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功;
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
4.悲观锁
总是假设最坏的情况,每次取数据时,都会认为其他线程会对该数据进行修改,所以会进行加锁其他线程访问的时候会阻塞等待,例如在数据库当中可以使用行锁,表锁以及读写锁等方式实现 在Java中synchronized就是悲观锁的表现