synchronized的理解和应用

synchronized是什么

  • java内置锁,可以保证方法或者代码块在运行时候,同一时刻只有一个线程进入临界区,同时它还保证了共享变量的可见性(限定同一个临界区,等同于volatile)

synchronized几种用法

  • 修饰普通方法,锁的是当前实例
  • 修饰代码块,锁的括号里面的对象
  • 修改静态方法,锁的是当前类的class对象

举个例子

1. 修饰普通方法
点击查看代码
public class SynchNoStaticClass {
    public synchronized void test2() {
        int i = 5;
        while (i-- > 0) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
    public static void main(String[] args) {
        SynchNoStaticClass synchNoStaticClass = new SynchNoStaticClass();
        new Thread(()->{
            synchNoStaticClass.test2();
        },"thread--1").start();
        new Thread(()->{
            synchNoStaticClass.test2();
        },"thread--2").start();
    }
}

结果:
image
分析:不同线程访问同一个对象的临界方法,需要等前一个线程执行完毕后把锁释放才能执行(执行结果是顺序的),如果是多个线程作用的是不同的对象,那么执行结果就可能不是顺序的,因为不同的对象锁,互不影响。

2. 修饰静态方法
点击查看代码
public class TestSynchronized {
    public static  synchronized void test2() {
        int i = 5;
        while (i-- > 0) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
            }
        }
    }
    public void test1(){
        test2();
    }
    public static void main(String[] args) {

        new Thread(()-> {
            TestSynchronized testSynchronizedOne = new TestSynchronized();
            testSynchronizedOne.test1();
            }, "thread--1").start();

        new Thread(()->{
            TestSynchronized testSynchronizedTwo = new TestSynchronized();
            testSynchronizedTwo.test1();
            },"thread--2").start();
    }
}

结果:
image
分析:不同线程访问不同的对象,但是访问方法是静态的,当前class的对象锁生效,结果就是顺序执行(可以把synchronized去掉,结果是交替输出的)

3.修饰代码块

为啥需要修饰代码块呢,因为修饰方法的范围粒度太大了,我们往往需要方法中的某段代码互斥进行同步操作

点击查看代码
public class SynchNoStaticClass {
    public static void main(String[] args) {
        SynchNoStaticClass synchNoStaticClass = new SynchNoStaticClass();
        new Thread(() -> {
            synchNoStaticClass.test3();
        }, "test3").start();
        new Thread(() -> {
            synchNoStaticClass.test3();
        }, "test4").start();
    }
    public void test3() {
        synchronized (SynchNoStaticClass.class) {
            int i = 5;
            while (i-- > 0) {
               System.out.println(Thread.currentThread().getName() + " : " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ie) {
                }
            }
        }
    }
}

结果:
image
分析:每次当线程进入synchronized包裹的代码块时就会,就会获取SynchNoStaticClass.class对象锁,那么其他线程在进来需要等待

synchronized的可重入性

什么是可重入锁呢?当一个线程再次请求由自己持有的对象锁时候是可以获取成功的,看下面代码

点击查看代码
public class SynchDoubleClass {

    public synchronized void test2() {
        int i = 5;
        while (i-- > 0) {
            System.out.println(Thread.currentThread().getName() + "test2:" + i);
        }
        test3();
    }
    public synchronized void test1() {
        int i = 5;
        while (i-- > 0) {
            System.out.println(Thread.currentThread().getName() + "test1:" + i);
        }
        test2();
    }
    public synchronized void test3() {
        int i = 5;
        while (i-- > 0) {
            System.out.println(Thread.currentThread().getName() + "test3:" + i);
        }
    }
    public static void main(String[] args) {
        new Thread(()->{
            SynchDoubleClass synchDoubleClass = new SynchDoubleClass();
            synchDoubleClass.test1();
        }).start();

    }
}
结果:

image

分析:因为synchronized 锁是可重入的,所以在test1方法没执行完,在去调用test2方法,因为是同一个对象锁,它依然会获得锁。


synchronized与awit/notify/notifyAll使用(等待通知机制)

  • awit阻塞当前线程,就是释放当前线程的锁
  • notify通知某个阻塞线程,notifyall 通知所有的阻塞线程

synchronized的底层实现

  • 字节码反编译结果
    image
    分析:
  • 先说结论,修饰方法JVM通过ACC_SYNCHRONIZED标识实现,修饰代码块,JVM通过 monitorenter和monitorexit两个指令实现同步,而monitor(对象监视器)的本质依赖于底层操作系统的 互斥锁(Mutex Lock) 实现,
  • 关于class文件结构以及更底层的c++文件源码解读,当前笔者还是小白,无法更深入了解,留待后续补充
原文地址:https://www.cnblogs.com/tycellwu/p/15816041.html