分享

Synchronized简介与原理

 印度阿三17 2019-10-27

一、作用

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()返回

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多