天天看点

12.锁杂谈

文章目录

  • ​​博客概述​​
  • ​​锁​​
  • ​​重入锁Reentrantlock​​
  • ​​锁与等待/通知​​
  • ​​多Condition​​
  • ​​Lock和Condition的其他方法和用法​​
  • ​​读写锁​​
  • ​​锁的总结​​

博客概述

在java多线程中,我们知道可以使用sync关键字实现线程间的同步互斥工作,那么其实还有一个更优秀的机制去完成这个同步互斥工作,他就是lock对象,我主要介绍2种锁,重入锁和读写锁。他们具有比sync关键字更强大的功能。父接口是lock接口。有三种锁实现-读锁,写锁,重入锁。关于segment的这个实现,没有公开使用,只是jdk内部使用的一个实现。

12.锁杂谈

重入锁Reentrantlock

重入锁,在需要进行同步的代码部分上加上锁定,但不要忘记最后一定要释放锁定,不然会造成锁永远无法释放,其他线程永远进不来的结果。对于这种特性考虑用try-catch-finally来做。之前可能是在方法是加sync关键字来实现方法的同步。现在用重入锁实现更细粒度的锁的控制。

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class UseReentrantLock {
  
  private Lock lock = new ReentrantLock();
  
  public void method1(){
    try {
      lock.lock();
      System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method1..");
      Thread.sleep(1000);
      System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method1..");
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      
      lock.unlock();
    }
  }
  
  public void method2(){
    try {
      lock.lock();
      System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method2..");
      Thread.sleep(2000);
      System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method2..");
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      
      lock.unlock();
    }
  }
  
  public static void main(String[] args) {

    final UseReentrantLock ur = new UseReentrantLock();
    Thread t1 = new Thread(new Runnable() {
      @Override
      public void run() {
        ur.method1();
        ur.method2();
      }
    }, "t1");

    t1.start();
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    //System.out.println(ur.lock.getQueueLength());
  }
  
  
}      

类似sync关键字。但是更加灵活。 一个重入锁,控制单一对象的两个方法的访问,实现同步。虽然在两个方法上加sycn关键字也能实现,jdk1.8之后,两者(sync与重入锁)性能差不多了。所以用起来不要纠结了。但是lock比sync关键字更灵活。

锁与等待/通知

我们在使用sync的时候,如果多线程之间进行协作工作则需要Object的wait/notify方法进行配合工作。那么同样,我们使用Lock的时候,可以使用使用一个新的等待/通知的类,它就是condition。这个condition一定是针对某一把具体的锁的,也就只有在锁的基础上才会产生condition。

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class UseCondition {

  private Lock lock = new ReentrantLock();
  private Condition condition = lock.newCondition();
  
  public void method1(){
    try {
      lock.lock();
      System.out.println("当前线程:" + Thread.currentThread().getName() + "进入等待状态..");
      Thread.sleep(3000);
      System.out.println("当前线程:" + Thread.currentThread().getName() + "释放锁..");
      condition.await();  // Object wait
      System.out.println("当前线程:" + Thread.currentThread().getName() +"继续执行...");
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  public void method2(){
    try {
      lock.lock();
      System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..");
      Thread.sleep(3000);
      System.out.println("当前线程:" + Thread.currentThread().getName() + "发出唤醒..");
      condition.signal();   //Object notify
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  public static void main(String[] args) {
    
    final UseCondition uc = new UseCondition();
    Thread t1 = new Thread(new Runnable() {
      @Override
      public void run() {
        uc.method1();
      }
    }, "t1");
    Thread t2 = new Thread(new Runnable() {
      @Override
      public void run() {
        uc.method2();
      }
    }, "t2");
    t1.start();

    t2.start();
  }
  
  
  
}      

signal发信号唤醒,signal不释放锁。await释放锁。得等着发出信号的这块被lock的代码执行完了,等待的线程才会继续运行。跟wait/notify的机制是一致的。但是这种方式,更加灵活,灵活的原因是因为可以多condition。

多Condition

我们可以通过一个lock对象产生多个condition进行多线程的交互,非常的灵活。可以使得部分需要唤醒的线程唤醒,其他的线程则需要继续等待通知。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class UseManyCondition {

  private ReentrantLock lock = new ReentrantLock();
  private Condition c1 = lock.newCondition();
  private Condition c2 = lock.newCondition();
  
  public void m1(){
    try {
      lock.lock();
      System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m1等待..");
      c1.await();
      System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m1继续..");
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  public void m2(){
    try {
      lock.lock();
      System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m2等待..");
      c1.await();
      System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m2继续..");
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  public void m3(){
    try {
      lock.lock();
      System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m3等待..");
      c2.await();
      System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m3继续..");
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  public void m4(){
    try {
      lock.lock();
      System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒..");
      c1.signalAll();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  public void m5(){
    try {
      lock.lock();
      System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒..");
      c2.signal();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  public static void main(String[] args) {
    
    
    final UseManyCondition umc = new UseManyCondition();
    Thread t1 = new Thread(new Runnable() {
      @Override
      public void run() {
        umc.m1();
      }
    },"t1");
    Thread t2 = new Thread(new Runnable() {
      @Override
      public void run() {
        umc.m2();
      }
    },"t2");
    Thread t3 = new Thread(new Runnable() {
      @Override
      public void run() {
        umc.m3();
      }
    },"t3");
    Thread t4 = new Thread(new Runnable() {
      @Override
      public void run() {
        umc.m4();
      }
    },"t4");
    Thread t5 = new Thread(new Runnable() {
      @Override
      public void run() {
        umc.m5();
      }
    },"t5");
    
    t1.start(); // c1
    t2.start(); // c1
    t3.start(); // c2
    

    try {
      Thread.sleep(2000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    t4.start(); // c1
    try {
      Thread.sleep(2000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    t5.start(); // c2
    
  }
  
  
  
}      

运行结果如下:m123方法分别执行,然后分别释放锁。t4唤醒1和2。最后t5唤醒t3。

Connected to the target VM, address: '127.0.0.1:55183', transport: 'socket'
当前线程:t1进入方法m1等待..
当前线程:t2进入方法m2等待..
当前线程:t3进入方法m3等待..
当前线程:t4唤醒..
当前线程:t1方法m1继续..
当前线程:t2方法m2继续..
当前线程:t5唤醒..
当前线程:t3方法m3继续..
Disconnected from the target VM, address: '127.0.0.1:55183', transport: 'socket'      

Lock和Condition的其他方法和用法

12.锁杂谈

公平锁:先调用先上锁。需要维护顺序。

非公平:按CPU的分配来上锁,不需要维护顺序,性能要好。

锁的嗅探:尝试获得锁的过程。trylock方法就是嗅探。省时间,获得不到,不急的话就先等着。

大当量的并发,并发任务有优先级。

import java.util.concurrent.locks.ReentrantLock;
/**
 * lock.getHoldCount()方法:只能在当前调用线程内部使用,不能再其他线程中使用
 * 那么我可以在m1方法里去调用m2方法,同时m1方法和m2方法都持有lock锁定即可 测试结果holdCount数递增
 *
 */
public class TestHoldCount {

  //重入锁
  private ReentrantLock lock = new ReentrantLock();
  
  public void m1(){
    try {
      lock.lock();
      System.out.println("进入m1方法,holdCount数为:" + lock.getHoldCount());
      
      //调用m2方法
      m2();
      
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  public void m2(){
    try {
      lock.lock();
      System.out.println("进入m2方法,holdCount数为:" + lock.getHoldCount());
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  
  public static void main(String[] args) {
    TestHoldCount thc = new TestHoldCount();
    thc.m1();
  }
}      

读写锁

12.锁杂谈

写多读少:用重入锁。读多写少,重人锁。

口诀:读读共享,有写互斥。

读写锁不要直接new,要实现读写互斥关系,要诞生于一把读写锁。

否则两锁之间的关系就不能建立了。

读操作,就加读锁。写操作就加写锁。

import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

public class UseReentrantReadWriteLock {

  private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
  private ReadLock readLock = rwLock.readLock();
  private WriteLock writeLock = rwLock.writeLock();
  
  public void read(){
    try {
      readLock.lock();
      System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
      Thread.sleep(3000);
      System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      readLock.unlock();
    }
  }
  
  public void write(){
    try {
      writeLock.lock();
      System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
      Thread.sleep(3000);
      System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      writeLock.unlock();
    }
  }
  
  public static void main(String[] args) {
    
    final UseReentrantReadWriteLock urrw = new UseReentrantReadWriteLock();
    
    Thread t1 = new Thread(new Runnable() {
      @Override
      public void run() {
        urrw.read();
      }
    }, "t1");
    Thread t2 = new Thread(new Runnable() {
      @Override
      public void run() {
        urrw.read();
      }
    }, "t2");
    Thread t3 = new Thread(new Runnable() {
      @Override
      public void run() {
        urrw.write();
      }
    }, "t3");
    Thread t4 = new Thread(new Runnable() {
      @Override
      public void run() {
        urrw.write();
      }
    }, "t4");   
    
//    t1.start();
//    t2.start();
    
//    t1.start(); // R 
//    t3.start(); // W
    
    t3.start();
    t4.start();
    
    
  }
}      

锁的总结

  • 避免死锁
  • 减小锁的持有时间
  • 减小锁的粒度
  • 锁的分离
  • 尽量使用无锁的操作。原子操作,volatile等。