zoukankan      html  css  js  c++  java
  • Synchronized简介与原理

    一、作用

    1. 多线程同步代码,保证方法或者代码块在运行时,同一时刻只有一个线程可以进入到临界区(互斥性)

    2. 保证线程间共享变量的修改及时可见(可见性)

    a. 当线程获取锁时,线程的本地变量无效,需要从主存中获取共享变量的值

    b. 线程释放锁时,线程的本地变量被刷新到主存中

    3. 有效解决重排序问题(有序性)

    二、用法

    1. Java中的每个对象都可以作为锁,获取的锁都是对象

    2. 修饰函数,即普通同步方法,锁是当前类的实例对象 public void synchronized A(){}

    3. 静态同步方法,锁是当前类的class对象 public static void synchronized A(){}

    4. 修饰函数内的语句块,即同步代码块,锁是括号中的对象 synchronized(obj){}

    5. 每个对象只有一个锁(lock)与之关联

    6. 作用域

    a. 某个对象实例内的方法,不同对象的实例内的方法不相干扰,其他线程可以同时访问相同类的其他对象实例中的synchronized方法

    b. 某个类的范围,一般是静态方法,可以防止多个线程同时访问相同类中的synchronized方法

    三、原理

    1. java示例代码,同步静态方法

    public class SynchronizedTest {
        private static Object object = new Object();
        public static void main(String[] args) throws Exception{
            synchronized(object) {
                
            }
        }
        public static synchronized void m() {}
    }

    2. 代码->字节码->反编译后的代码

    public static void main(java.lang.String[]) throws java.lang.Exception;
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=3, args_size=1
             0: getstatic     #2                  // Field object:Ljava/lang/Object
    
             3: dup
             4: astore_1
             5: monitorenter            //监视器进入,获取锁
             6: aload_1
             7: monitorexit              //监视器退出,释放锁
             8: goto          16
            11: astore_2
            12: aload_1
            13: monitorexit
            14: aload_2
            15: athrow
            16: return
         
       public static synchronized void m();
       descriptor: ()V
       flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
       Code:
         stack=0, locals=0, args_size=0
            0: return
         LineNumberTable:
           line 9: 0

    3. 同步代码块使用monitorenter和monitorexit指令实现

    4. 同步方法使用修饰符ACC_SYNCHRONIZED实现

    5. 无论哪种实现,本质上都是对monitor的获取,这个过程是互斥的

    四、锁的状态

    1. 对象头:

    a. 对象在内存中分为3部分:对象头、实例数据、对齐填充

    b. 对象头可以记录对象的状态:偏向锁、轻量级锁、重量级锁

    2. monitor:

    a. 线程私有的数据结构,每个线程有一个monitor列表

    b. JVM记录获取monitor锁的线程唯一id,确保一次只有一个线程执行

    3. 偏向锁状态:

    a. 如果一个线程获取了锁,那么锁就进入了偏向状态

    b. 当这个线程再次请求锁时,不需要再做同步,就可以获取锁,避免了申请锁的操作,优化了性能

    c. 适用于没有激烈竞争锁的情况,不仅没有多线程竞争,而且总是由同一个线程获取锁

    4. 轻量级锁状态:

    a. 获取偏向锁失败后,会膨胀为轻量级锁

    b. 适用于交替执行同步块的情况

    5. 重量级锁状态:

    a. 获取轻量级锁失败后,会膨胀为重量级锁

    b. 此时所有线程都会被锁住,当前获取锁的线程释放后,其他线程才被唤醒

    五、等待通知机制,即wait(),notify(),notifyall()进行线程间通信

    1. 代码

    public class WaitNotify {
        static boolean flag = true;
        static Object lock = new Object();
    
        public static void main(String[] args) throws InterruptedException {
            Thread A = new Thread(new Wait(), "wait thread");
            A.start();
            TimeUnit.SECONDS.sleep(2);
            Thread B = new Thread(new Notify(), "notify thread");
            B.start();
        }
    
        static class Wait implements Runnable {
            @Override
            public void run() {
                synchronized (lock) {
                    while (flag) {
                        try {
                            System.out.println(Thread.currentThread() + " flag is true");
                            lock.wait();
                        } catch (InterruptedException e) {
    
                        }
                    }
                    System.out.println(Thread.currentThread() + " flag is false");
                }
            }
        }
    
        static class Notify implements Runnable {
            @Override
            public void run() {
                synchronized (lock) {
                    flag = false;
                    lock.notifyAll();
                    try {
                        TimeUnit.SECONDS.sleep(7);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    2. 使用wait(),notify(),notifyAll()时,需要先对对象加锁

    3. 调用wait()方法后,线程会释放锁,线程状态RUNNING->WAITING,将线程移动到等待队列

    4. notify()/notifyAll()时,等待线程不会立即从wait()返回,需要当前线程释放锁之后,才有机会获取锁返回

    5. notify()将一个等待队列里的线程,移动到同步队列,线程状态WAITING->BLOCKED

    6. notifyAll()将所有等待队列里的线程,移动到同步队列,线程状态WAITING->BLOCKED

    7. 线程只有获取了锁,才能从wait()返回

      

    参考:

    https://www.cnblogs.com/mingyao123/p/7424911.html

    https://www.jianshu.com/p/19f861ab749e

    https://blog.csdn.net/lengxiao1993/article/details/81568130

  • 相关阅读:
    HBase分布式集群搭建过程
    HBase基础知识
    HBase伪分布搭建
    搭建ZooKeeper集群环境
    hadoop集群环境搭建-hadoop之伪分布搭建环境
    乱序数组找出前面的都比他小后面的都比他大的数
    sikuli简介
    ZooKeeper 应用场景
    svmrank原理
    Latex汇总
  • 原文地址:https://www.cnblogs.com/june0816/p/11745455.html
Copyright © 2011-2022 走看看