美文网首页java
Java-并发(synchronized)

Java-并发(synchronized)

作者: 二妹是只猫 | 来源:发表于2019-04-29 14:43 被阅读0次

    线程安全问题的主要诱因

    • 存在共享数据(也称临界数据)
    • 存在多条线程共同操作这些共享数据

    解决问题的根本方法

    同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后在对共享数据进行操作

    互斥锁的特性

    • 互斥性:即在同一时间只运行一个线程有某个对象锁,通过这种特性来实现多线程的协调机制,这样在同一时间只有一个线程对需要同步的代码块(复合操作)进行访问。互斥性也称为操作的原子性
    • 可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在你获得锁时应获得最新恭喜那个变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作,从而引起不一致

    synchronized锁的不是代码,锁的是对象

    根据获取的锁的分类:获取对象锁和获取类锁

    获取对象锁的两种方法
    • 同步代码块(synchronized(this), synchronized(类实例对象)),锁时小括号()中的实例对象
    • 同步非静态方法(synchronized method),锁是当前对象的实例
    public class SyncThread implements Runnable {
    
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            if (threadName.startsWith("A")) {
                async();
            } else if (threadName.startsWith("B")) {
                syncObjectBlock1();
            } else if (threadName.startsWith("C")) {
                syncObjectMethod1();
            } else if (threadName.startsWith("D")) {
                syncClassBlock1();
            } else if (threadName.startsWith("E")) {
                syncClassMethod1();
            }
    
        }
    
        /**
         * 异步方法
         */
        private void async() {
            try {
                System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 方法中有 synchronized(this|object) {} 同步代码块
         */
        private void syncObjectBlock1() {
            System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            synchronized (this) {
                try {
                    System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * synchronized 修饰非静态方法
         */
        private synchronized void syncObjectMethod1() {
            System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            try {
                System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        private void syncClassBlock1() {
            System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            synchronized (SyncThread.class) {
                try {
                    System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        private synchronized static void syncClassMethod1() {
            System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            try {
                System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
      public static void main(String[] args) {
          SyncThread syncThread = new SyncThread();
          Thread A_thread1 = new Thread(syncThread, "A_thread1");
          Thread A_thread2 = new Thread(syncThread, "A_thread2");
          Thread B_thread1 = new Thread(syncThread, "B_thread1");
          Thread B_thread2 = new Thread(syncThread, "B_thread2");
          Thread C_thread1 = new Thread(syncThread, "C_thread1");
          Thread C_thread2 = new Thread(syncThread, "C_thread2");
          A_thread1.start();
          A_thread2.start();
          B_thread1.start();
          B_thread2.start();
          C_thread1.start();
          C_thread2.start();
      }
    
    C_thread1_SyncObjectMethod1: 10:38:14
    B_thread2_SyncObjectBlock1: 10:38:14
    B_thread1_SyncObjectBlock1: 10:38:14
    C_thread1_SyncObjectMethod1_Start: 10:38:14
    A_thread1_Async_Start: 10:38:14
    A_thread2_Async_Start: 10:38:14
    C_thread1_SyncObjectMethod1_End: 10:38:15
    A_thread1_Async_End: 10:38:15
    A_thread2_Async_End: 10:38:15
    B_thread1_SyncObjectBlock1_Start: 10:38:15
    B_thread1_SyncObjectBlock1_End: 10:38:16
    B_thread2_SyncObjectBlock1_Start: 10:38:16
    B_thread2_SyncObjectBlock1_End: 10:38:17
    C_thread2_SyncObjectMethod1: 10:38:17
    C_thread2_SyncObjectMethod1_Start: 10:38:17
    C_thread2_SyncObjectMethod1_End: 10:38:18
    
    Process finished with exit code 0
    
    

    通过以上代码可以看到,A线程异步执行不受影响,B和C被上对象锁需要等待被上锁的线程执行完毕后,其他线程才竞争去获取对象锁然后执行。
    当把new Thread(syncThread,..)改为new Thread(new syncThread(),..)后会发现,由于不在是同一对象导致对象锁不一致,将不在同步。

    获取类锁的两种方法:
    • 同步代码块(synchronized(类.class)),锁时小括号()中的类对象(Class对象)
    • 同步非静态方法(synchronized static method),锁是当前的类对象(Class对象)
      public static void main(String[] args) {
         SyncThread syncThread = new SyncThread();
          Thread A_thread1 = new Thread(syncThread, "A_thread1");
          Thread A_thread2 = new Thread(syncThread, "A_thread2");
          Thread D_thread1 = new Thread(syncThread, "D_thread1");
          Thread D_thread2 = new Thread(syncThread, "D_thread2");
          Thread E_thread1 = new Thread(syncThread, "E_thread1");
          Thread E_thread2 = new Thread(syncThread, "E_thread2");
          A_thread1.start();
          A_thread2.start();
          D_thread1.start();
          D_thread2.start();
          E_thread1.start();
          E_thread2.start();
      }
    
    A_thread1_Async_Start: 10:52:24
    D_thread1_SyncClassBlock1: 10:52:24
    D_thread2_SyncClassBlock1: 10:52:24
    E_thread1_SyncClassMethod1: 10:52:24
    A_thread2_Async_Start: 10:52:24
    E_thread1_SyncClassMethod1_Start: 10:52:24
    A_thread1_Async_End: 10:52:25
    A_thread2_Async_End: 10:52:25
    E_thread1_SyncClassMethod1_End: 10:52:25
    D_thread2_SyncClassBlock1_Start: 10:52:25
    D_thread2_SyncClassBlock1_End: 10:52:26
    D_thread1_SyncClassBlock1_Start: 10:52:26
    D_thread1_SyncClassBlock1_End: 10:52:27
    E_thread2_SyncClassMethod1: 10:52:27
    E_thread2_SyncClassMethod1_Start: 10:52:27
    E_thread2_SyncClassMethod1_End: 10:52:28
    
    Process finished with exit code 0
    
    

    这里类锁和上面的对象锁效果一致,但是当我们将new Thread(syncThread,..)改为new Thread(new syncThread(),..)后,发现锁的限制依然有效。其实从名字都能看出对象锁的作用域是对象实例,而类锁的作用域是类

    对象锁和类锁的总结:
    • 有线程访问对象的同步代码块时,另外的线程可以访问该对象的非同步代码块
    • 若锁住的是同一个对象,一个线程在访问对象的同步代码块时,另一个访问对象的同步代码块的线程会被阻塞
    • 若锁住的是同一个对象,一个线程在访问对象的同步方法时,另一个访问对象的同步方法的线程会被阻塞
    • 若锁住的是同一个对象,一个线程在访问对象的同步代码块时,另一个访问对象的同步方法的线程会被阻塞,反之同理
    • 同一个类的不同对象的对象锁互不干扰
    • 类锁由于也是一种特殊的对象锁,因此表现和上诉的前4点一致,而由于一类只有一把对象锁,所以同一个类的不同对象使用类锁将会是同步的
    • 类锁和对象锁互不干扰

    synchronized底层实现原理

    实现synchronized的基础
    • Java对象头


      对象头结构
    • Monitor
      存在于对象头中,其具体实现为ObjectMonitor位于虚拟机底层源码:
      ObjectMonitor
      可以看到它里面有多线程那篇中介绍过的waitSet(等待池)和EntryList(锁池),它们就是用来保存ObjectMonitor对象列表。owner指向持有ObjectMonitor的线程。
      当多个线程访问对象时,首先会进入到EntryList获取到锁,然后owen指向当前线程,count加1。调用wait()后,owner设置为null,count减1,当前对象进入到waitSet区

    通过字节码文件来看看monitor的运行
    java:

    public class SyncBlockAndMethod {
        public void syncsTask() {
            //同步代码库
            synchronized (this) {
                System.out.println("Hello");
                synchronized (this){
                    System.out.println("World");
                }
            }
        }
    
        public synchronized void syncTask() {
            System.out.println("Hello Again");
        }
    
    }
    
    同步代码块字节码文件
    同步代码块分析synchronized (this):
    通过字节码文件3-42行能够看出同步语句块的实现是有monitorenter开始到monitorexit结速,第一次在3行执行synchronized (this)调用monitorenter执行同步,然后4-6行调用PrintStream进行打印,在15行时再次执行synchronized (this)调用monitorenter执行重入,但是我们在后面看到多出了两个monitorexit,这是因为编译器会自动产生一个异常处理器防止在异常情况下monitor能正常退出
    重入(补充)

    从互斥锁的设计来说,当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入

    同步方法分析

    同步方法字节码文件
    同步方法并没有显示的持有monitor,而是通过ACC_SYNCHRONIZED标志位来隐式的持有monitor,进入到同步状态
    锁的分类

    偏向锁->轻量级锁->重量级锁(由小到大)

    优点 缺点 使用场景
    偏向锁 加锁和解锁不需要CAS操作,没有额外的性能消耗,和执行非同步方法相比存在纳秒级差距 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 只有一个线程访问同步块或者同步方法的场景
    轻量级锁 竞争的线程不会阻塞,提高响应速度 若线程长时间抢不到锁,自旋会消耗CPU性能 线程交替执行同步块或者同步方法的场景
    重量级锁 线程竞争不使用自旋,不会消耗CPU性能 线程阻塞,响应时间缓慢,在多线程下,频繁的获取释放锁,会带来巨大的性能消耗 追求吞吐量,同步块或者同步方法执行时间较长的场景

    相关文章

      网友评论

        本文标题:Java-并发(synchronized)

        本文链接:https://www.haomeiwen.com/subject/xbmenqtx.html