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为啥这么神奇,无它,加锁而已,不少八股文喜欢分为两种锁,一种是对象锁,一种是类锁,还可以分为方法锁,代码块锁,静态锁,class锁,我们通过代码学习他们如何使用
(资料图片)
方法锁是用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"); }}
代码块锁就是常用的同步方法块,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"); }}
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对象,但是不能同时访问同步代码
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修饰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
虽然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头部的。
从jdk1.6开始,synchronized锁有四种状态,级别由低到高是
无锁,偏向锁,轻量锁,重量锁
锁的升级过程是一个很麻烦的事情,本质就是如果发生了锁的竞争就升级锁,直到升级到重量锁为止,期间使用到了CAS和自旋来避免线程直接进入阻塞状态。有兴趣的同学可以看看《阿里巴巴java性能调优实战》这本书。
锁消除的概念比较容易理解,就是如果编译器认定一个锁只会被单个线程访问,那么这个锁就可以被消除。而锁粗化,简单的说就是JIT动态编译时发现相邻的同步块使用的是同一个锁实例,那么就合并他们,避免频繁加锁释放锁。
这是我们平时编程的时候,我们可以控制的,有的同学(比如我)图省事往往可以加对象锁的,直接加类锁,这样就是不地道的。
《阿里巴巴java性能调优实战》举例说明减少锁的粒度的好处,比如被抛弃的HashTable和新宠ConcurrentHashMap的转换,就是使用了减少锁的粒度方法。
Copyright @ 2015-2022 海外生活网版权所有 备案号: 沪ICP备2020036824号-21 联系邮箱:562 66 29@qq.com