多线程锁

乐观锁

无锁编程,更新数据只会判断有没有其他线程更改了这个数据。适合读操作多的场景

悲观锁

适合写操作多的场景,先加锁保证写操作时数据正确

总结

  1. 一个类中方法被synchronized修饰,整个资源被加上了悲观锁,其他被synchronized修饰的方法也被锁住。多个线程访问同个资源对象的不同方法,不能同时进行,需要第一个线程释放完锁,其他线程才能依次访问,没有被synchronized修饰的,没有加锁可以访问。

  2. 多个线程访问实例不同资源对象的方法,互不影响,悲观锁只锁定同资源对象。

  3. 都换成static synchronized修饰锁定的时class模板,无论多少实例对象,都来自一个模板,静态方法都会被锁住,即多线程不能同时访问多个对资源的静态方法, 而没有static修饰的锁的只是实例对象。

  4. 静态方法锁,类锁;普通方法锁,对象锁;能用对象锁不要用类锁;

  5. synchronized可以对代码块枷锁,锁只对括号内代码有效

synchronized底层实现

  1. 同步代码块

    javap -c 进行反编译代码

    Object object=new Object();
    public void m1(){
        synchronized (object){
            System.out.println("-----hello synchronized code block");
        }
    }
    
     public void m1();
        Code:
           0: aload_0
           1: getfield      #3  // Field object:Ljava/lang/Object;
           4: dup
           5: astore_1
           6: monitorenter
           7: getstatic     #4   // Field java/lang/System.out:Ljava/io/PrintStream;
          10: ldc           #5   // String -----hello synchronized code block
          12: invokevirtual #6   // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          15: aload_1
          16: monitorexit
          17: goto          25
          20: astore_2
          21: aload_1
          22: monitorexit
          23: aload_2
          24: athrow
          25: return
    

    通过monitorenter和monitorexit进行加锁和退出锁,加锁和退出锁1对2,为了出了异常也能退出锁

  2. 普通同步方法

    javap -v 进行反编译代码 输出附加信息

    public synchronized void m2(){
        System.out.println("-----hello synchronized code block");
    }
    
     public synchronized void m2();
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_SYNCHRONIZED
        Code:
          stack=2, locals=1, args_size=1
         0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #5                  // String -----hello synchronized code block
         5: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
    

    调用指令减去检查方法的ACC_SYNCHRONIZED访问标识是否被设置

    如何设置了,执行线程会将先持有monitor锁,然后在执行方法,最后在方法完成时是否monitor

  3. 静态同步方法

     public static synchronized void m3(){
            System.out.println("-----hello synchronized code block");
        }
    
     public static synchronized void m3();
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
        Code:
          stack=2, locals=0, args_size=0
        0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        3: ldc           #3                  // String -----hello synchronized code block
        5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        8: return
    

    通过 ACC_STATIC, ACC_SYNCHRONIZED来判断是否是静态同步方法(类锁)

公平锁和非公平锁

Lock lock = new ReentrantLock(true); 表示公平锁,先到先得

Lock lock = new ReentrantLock(false);表示非公平锁,后来的也可能获得锁,默认是非公平锁

非公平锁更能充分利用cpu的时间片,尽量减少cpu空闲状态时间,减少线程的切换

可重入锁(递归锁)

ReentrantLock和Synchronized都是可重入锁,指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁叫可重入锁。

一个线程中的多个流程可以获得同一把锁,持有这把同步锁可以再次进入,自己可以获得自己的内部锁。

static Lock lock= new ReentrantLock(); 定义个静态锁

 new Thread(()->{
     try {
         System.out.println(Thread.currentThread().getName()+"\t"+"come in 外层");
         lock.lock();
         try {
           System.out.println(Thread.currentThread().getName()+"\t"+"come in 内层");
            lock.lock();
           }finally {
              lock.unlock();
           }
       }finally {
              lock.unlock();
       }
  },"t1").start();

输出:

t1 come in 外层
t1 come in 内层

lock()和unlock()要配对,否则会导致其他线程一直等待拿不到锁,造成死锁

死锁及排查

死锁主要原因

  1. 系统资源不足
  2. 进程运行推进的顺序不合适
  3. 资源分配不当

排查方法

 final Object objectA=new Object();
 final Object objectB=new Object();
 new Thread(()->{
    synchronized (objectA){
      System.out.println(Thread.currentThread().getName()+"\t 自己持有A锁,希望获得B锁");
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
       synchronized (objectB){
           System.out.println(Thread.currentThread().getName()+"\t 成功获得B锁");
           }
        }
 },"A").start();
 new Thread(()->{
    synchronized (objectB){
      System.out.println(Thread.currentThread().getName()+"\t 自己持有B锁,希望获得A锁");
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
       synchronized (objectA){
           System.out.println(Thread.currentThread().getName()+"\t 成功获得A锁");
           }
       }
 },"B").start();

输出:

A 自己持有A锁,希望获得B锁
B 自己持有B锁,希望获得A锁

相互等待,互相持有锁

使用命令

  1. jps -l 查看进程 ,jstack 进程编号

  2. jconsole 可以打开图形化页面

posted @ 2022-06-17 11:34  让人生留下足迹  阅读(117)  评论(0编辑  收藏  举报