sychronized是java多线程非常关键的一个知识点,这篇博客将从synchronized几个用法以及代码来学习。
sychronized的作用是能够保证同一时间只有一个线程来运行这块代码,达到并发效果,如果没有保证并发的话,在多线程编码中就会产生致命问题,比如经典的i++,这也是数据库并发中经典的案例,i++并不是原子操作,分为三步,取数,操作,写数,参考这段代码,可以运行一下看下结果

public class showUnsafe1 implements Runnable{    static int i=0;    @Override    public void run() {        for(int j=0;j<10000;j++){            i++;        }    }    public static void main(String[] args) throws InterruptedException {        Thread thread1 = new Thread(new showUnsafe1());        Thread thread2 = new Thread(new showUnsafe1());        thread1.start();  // 启动thread1,在合适的时刻运行        thread2.start();  // 启动thread2,在合适的时刻运行        thread1.join();   // 让主线程等待thread1运行完        thread2.join();   // 让主线程等待thread2运行完        System.out.println(i);    }}

一、synchronized四种用法

synchronized为啥这么神奇,无它,加锁而已,不少八股文喜欢分为两种锁,一种是对象锁,一种是类锁,还可以分为方法锁,代码块锁,静态锁,class锁,我们通过代码学习他们如何使用

1.对象锁:方法锁

方法锁是用synchronized修饰的一个类方法,作用方法即是方法作用域,除了这个方法要同步,其余不需要

public class SynchronizedObjectMethod implements Runnable{    private static SynchronizedObjectMethod instance=new SynchronizedObjectMethod();    public  synchronized void method(){        System.out.println("我是对象锁的方法修饰符形式。我叫"+Thread.currentThread().getName());        try {            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName()+"运行结束");    }    @Override    public void run() {        method();    }    public static void main(String[] args) {        Thread thread1 = new Thread(instance);        Thread thread2 = new Thread(instance);        thread1.start();        thread2.start();        while(thread1.isAlive()||thread2.isAlive()){        }        System.out.println("finish");    }}

2. 对象锁:代码块形式

代码块锁就是常用的同步方法块,synchronized锁住的是它里面的对象,作用域就是synchonized{}里面的代码

public class SynchronizedObjectCodeBlock implements Runnable{    private static SynchronizedObjectCodeBlock instance=new SynchronizedObjectCodeBlock();    Object lock1=new Object();    @Override    public void run() {        synchronized (lock1){            System.out.println("我是对象锁的代码块形式。我叫"+Thread.currentThread().getName());            try {                Thread.sleep(3000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName()+"运行结束");        }    }    public static void main(String[] args){        Thread thread1 = new Thread(instance);        Thread thread2 = new Thread(instance);        thread1.start();        thread2.start();        while(thread1.isAlive()||thread2.isAlive()){        }        System.out.println("finish");    }}

3. 类锁:class形式

class形式说的是synchronized()括号里使用的锁是class对象,所谓class对象指得是java文件对应的一个java.lang.class对象,所有该类生成的对象共有这个class对象 类加载机制,所以这个锁锁住了这个类生成的所有对象

public class SynchronizedClassClass implements Runnable{    private static SynchronizedClassClass instance1=new SynchronizedClassClass();    private static SynchronizedClassClass instance2=new SynchronizedClassClass();    public void method(){        synchronized (SynchronizedClassClass.class){            System.out.println("我是类锁的形式之一:修饰.class。我叫"+Thread.currentThread().getName());            try {                Thread.sleep(3000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName()+"运行结束");        }    }    @Override    public void run() {        method();    }    public static void main(String[] args) {        Thread thread1 = new Thread(instance1);        Thread thread2 = new Thread(instance2);        thread1.start();        thread2.start();        while(thread1.isAlive()||thread2.isAlive()){        }        System.out.println("finish");    }}

在这个案例中,存在两个SynchronizedClassClass对象,但是不能同时访问同步代码

4.类锁:static形式

static形式说的是static修饰synchronized修饰的方法,即static synchronized methodName,作用方法还是这个类的class对象

public class SynchronizedClassStatic implements Runnable{    private static SynchronizedClassStatic instance1=new SynchronizedClassStatic();    private static SynchronizedClassStatic instance2=new SynchronizedClassStatic();    public static synchronized void method(){        System.out.println("我是类锁的形式之一:加static修饰。我叫"+Thread.currentThread().getName());        try {            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName()+"运行结束");    }    public void  method2(){        System.out.println("我是非静态方法,我叫"+Thread.currentThread().getName());    }    @Override    public void run() {        method();        method2();    }    public static void main(String[] args) {        Thread thread1 = new Thread(instance1);        Thread thread2 = new Thread(instance2);        thread1.start();        thread2.start();        while(thread1.isAlive()||thread2.isAlive()){        }        System.out.println("finish");    }}

二、synchronized的原理

让我们从字节码来看看synchronized的神秘面纱, 我们反编译synchronized修饰object的java文件

public class Decompilation {    private Object object=new Object();    public void insert(Thread thread){        synchronized (object){        }    }}

反编译指令为 javap -c -v YourName.java

所以就是这个小小的Monitor Enter和monitorexit指令完成了同步操作,关于Monitor Enter和monitorexit的定义可以查看jvm文档 中的注释

先看看 Monitor Enter,注意紫色标注


再看看Monitor Exit

这里就解释清楚为什么synchronized上的锁是可重入的,对于更深入的理解

重点还是提到的Monitor这个概念

我们再反编译synchronize修饰的同步方法,其结果是

与之前不一样,这里是在方法的访问标识上添加了ACC_SYNCHRONIZED,它在jvm文档中是这么解释

大致意思就是当调用设置了 ACC_SYNCHRONIZED 的方法时,执行线程进入监视器(monitor),然后执行这个方法,方法执行完毕后退出监视器。在执行线程拥有监视器期间,没有其他线程可以进入这个方法.

同样,这里涉及了Monitor

2.1 深入Monitor

虽然HotSpot的JDK代码没有开元,但好在还有OpenJDK,大家有时间可以看看ObjectMonitor.hpp和ObjectMonitor.cpp,如果c语言功力不好的同学,看看注释,大致知道每个变量啥意思即可。

主要是_owner,_recursions,_entryList,_waitSet,此外还有header这个对象头将对象和Monitor联系起来。

_owner顾名思义就是锁的拥有者,recursions就是锁的进入次数,初始为0,而_entryList是存放Blocked状态的线程的,waitSet是存放Waiting状态的线程。

在HotSpot中,一个对象是在Heap中的存储布局有三个部分:对象头(Header),实例数据(Instance Data)以及对齐填充部分,而对象头一般由两个部分组成:MarkWord和类型指针,如果是数组对象,那么还有数组长度信息。

而MarkWord就是连接Monitor和对象的关键东西。它存储了对象运行时的一些数据,比如HashCode,GC年龄,锁的状态,线程所持有的锁等。

总之,monitor才是synchronized并发的关键,monitor是底层用cpp实现的一个对象,实现了锁的状态转换,获取释放等方法,通过markword与java对象联系一起,而markword是嵌入在java头部的。

三、synchornized的各种锁以及优化3.1 锁的升级

从jdk1.6开始,synchronized锁有四种状态,级别由低到高是

无锁,偏向锁,轻量锁,重量锁

锁的升级过程是一个很麻烦的事情,本质就是如果发生了锁的竞争就升级锁,直到升级到重量锁为止,期间使用到了CAS和自旋来避免线程直接进入阻塞状态。有兴趣的同学可以看看《阿里巴巴java性能调优实战》这本书。

3.2 JIT 实现锁消除和锁粗化

锁消除的概念比较容易理解,就是如果编译器认定一个锁只会被单个线程访问,那么这个锁就可以被消除。而锁粗化,简单的说就是JIT动态编译时发现相邻的同步块使用的是同一个锁实例,那么就合并他们,避免频繁加锁释放锁。

3.3 减少锁的粒度

这是我们平时编程的时候,我们可以控制的,有的同学(比如我)图省事往往可以加对象锁的,直接加类锁,这样就是不地道的。

《阿里巴巴java性能调优实战》举例说明减少锁的粒度的好处,比如被抛弃的HashTable和新宠ConcurrentHashMap的转换,就是使用了减少锁的粒度方法。