初探并发一

Posted hetangyuese

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初探并发一相关的知识,希望对你有一定的参考价值。

线程并发-synchronized和Lock简单认知

前几天刚加深了线程的了解,期间在验证各种方法及多线程时遇到一些疑问,在高并发的情况下,怎么做才能保证程序还能按照我们预期的正常运行下去,这就是我们接下来探究下关于并发中的常用的一些线程安全方法、类等,仅个人见解, 忘广大学友纠正。

synchronized同步锁

  1. 特性

    1.1 类、方法和代码块正确的使用synchronized可以保证并发情况下互斥线程的代码同步(原子性);

    1.2 保证共享的资源可见性(类似volaite),每个线程都有自己的缓存区域,如果被锁的资源发生了变化,每个线程会弃从线程缓存中获取而去系统主内存中重新获取最新的(可见性

    1.3 在并发情况下,可以保障线程的有序执行 (有序

  2. 原理

    同步锁可以针对类、代码块和对象进行加锁,每个被修饰的对象,在线程调用获取时都会经过监视器monitor进行操作;

    当线程调用对象时,如果该对象的监视器中进入的线程数为0时,则当前线程为对象锁的拥有者,数值+1,其他的线程调用时则会阻塞进行等待;

    如果当前线程再次获取该对象时,监视器进入的线程数值再+1;

    在线程执行完成释放资源后,监视器中进入的线程数为0时,阻塞的线程按序获取对象。

    当我们在对对象或者类(代码块)加锁时:

    public class Test1 
    
     private static Boolean flag = true;
    
     public void add() 
         synchronized(flag) 
             if (flag) 
                 flag = false;
             
         
     
    
     public static void main(String args[]) 
         Test1 t1 = new Test1();
         t1.add();
         System.out.println(flag);
     
    
    // 查看编译字节文件
    public Test1();
      descriptor: ()V
      flags: ACC_PUBLIC
      Code:
        stack=1, locals=1, args_size=1
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
        LineNumberTable:
          line 1: 0
    
    public void add();
      descriptor: ()V
      flags: ACC_PUBLIC
      Code:
        stack=2, locals=3, args_size=1
           0: getstatic     #2                  // Field flag:Ljava/lang/Boolean;
           3: dup
           4: astore_1
           5: monitorenter
           6: getstatic     #2                  // Field flag:Ljava/lang/Boolean;
           9: invokevirtual #3                  // Method java/lang/Boolean.booleanValue:()Z
          12: ifeq          22
          15: iconst_0
          16: invokestatic  #4                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
          19: putstatic     #2                  // Field flag:Ljava/lang/Boolean;
          22: aload_1
          23: monitorexit
          24: goto          32
          27: astore_2
          28: aload_1
          29: monitorexit
          30: aload_2
          31: athrow
          32: return
        Exception table:
           from    to  target type
               6    24    27   any
              27    30    27   any
    
    

    通过查看字节码(使用javap -v/verbose命令查看编码文件)来看,jvm通过monitorenter和monitorexit进入和退出监视器,为了确保释放对象锁,存在多次monitorexit;

    当我们在方法体上加锁时:

    public class Test 
    
     private int num;
    
     public synchronized int add() 
         return num ++;
     
    
     public static void main(String[] args) 
         Test t = new Test();
         System.out.println(t.add());
     
    
    // 字节码
    public Test();
      descriptor: ()V
      flags: ACC_PUBLIC
      Code:
        stack=1, locals=1, args_size=1
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
        LineNumberTable:
          line 1: 0
    
    public synchronized int add();
      descriptor: ()I
      flags: ACC_PUBLIC, ACC_SYNCHRONIZED
      Code:
        stack=4, locals=1, args_size=1
           0: aload_0
           1: dup
           2: getfield      #2                  // Field num:I
           5: dup_x1
           6: iconst_1
           7: iadd
           8: putfield      #2                  // Field num:I
          11: ireturn
        LineNumberTable:
          line 6: 0
    

    出现了ACC_SYNCHRONIZED变量,与在代码块加锁不同的是,方法体加锁是根据ACC_SYNCHRONIZED标识的true/false来判断是否同步,而代码块同步锁则是通过monitorenter和monitorexit指令进行同步操作

  3. 应用场景

    在多线程中,我们要实现资源共享时可以通过加锁来实现;同步代码块逻辑过于复杂不推荐使用

Lock

  1. 特性

    • Lock需要手动加锁、手动解锁 (lock(); unlock();)

    • tryLock非阻塞方式获取锁,判断是否已经被占有锁,有则返回false,true则占有锁;同样可以设置时间,在时间之内尝试获取锁;

      lock.tryLock(6000, TimeUnit.MILLISECONDS) // 线程AB先后获取锁,A占有锁B会在后面的6秒尝试再次获取获取不到直接进入false逻辑。
    • lock.lockInterruptibly(), 等待获取锁的线程可以中断 thread.interrupt(); 声明抛出InterruptedException异常

    • Lock释放锁如有异常捕获需在finally中释放锁

  2. 原理

    Lock常用的实现类ReentrantLock、ReadWriteLock等,其基本都是依赖AQS(AbstractQueuedSynchronizer)提供的加锁解锁方法。

总结

  1. synchronized和lock的区别
    • synchronized jvm在执行了monitorexit命令后会自动释放锁,lock需要手动释放锁,否则容易造成线程死锁
    • Lock是一个接口,synchronized是关键字
    • Lock可以判断线程是否拥有了锁、可以设置获取锁的时间
    • Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
  2. 要使用synchronized,必须要有两个以上的线程。单线程使用没有意义,还会使效率降低。
  3. 要使用synchronized,线程之间需要发生同步,不需要同步的没必要使用synchronized,例如只读数据。
  4. 使用synchronized的缺点是效率非常低,因为加锁、释放锁和释放锁后争抢CPU执行权的操作都很耗费资源。

以上是关于初探并发一的主要内容,如果未能解决你的问题,请参考以下文章

nginxswoole高并发原理初探

C++11多线程与并发初探

原创JAVA并发编程——Callable和Future源码初探

手写笔记23:初探JUC并发编程

.NET文件并发与RabbitMQ(初探RabbitMQ)

java并发:初探用户线程和守护线程