解决问题的根本方法:
同一时刻只有一个线程操作共享数据,其他线程必须等待该线程操作完数据后再对共享资源进行操作。
syncronized 是满足互斥锁的特性。
注意:syncronized锁的都不是代码,而是对象。
根据获取的锁来分类:对象锁和类锁。
RunnaleDemo runnaleDemo = new RunnaleDemo();
// 该场景,第二个线程会阻塞
new Thread( runnaleDemo ,"a").start(); //该线程访问这个对象同步代码块
new Thread(runnaleDemo ,"b").start() //该线程访问这个对象同步代码块
// 该场景,两个线程不会阻塞
new Thread( new RunnaleDemo(),"a").start(); //该线程访问同步代码块
new Thread( new RunnaleDemo(),"b").start() //该线程访问同步代码块
// 该场景,第二个线程会阻塞
new Thread( new RunnaleDemo(),"a").start(); //该线程访问静态同步代码块
new Thread( new RunnaleDemo(),"b").start() //该线程访问静态同步代码块
// 该场景,两个线程不会阻塞
new Thread( new RunnaleDemo(),"a").start(); //该线程访问同步代码块
new Thread( new RunnaleDemo(),"b").start() //该线程访问静态同步代码块
// 该场景,两个线程不会阻塞
RunnaleDemo runnaleDemo = new RunnaleDemo();
new Thread(runnaleDemo,"a").start(); //该线程访问同步代码块
new Thread(runnaleDemo,"b").start(); //该线程访问静态同步代码块
syncronized实现有两个基础
对象在内存的布局:
- 对象头
- 实例数据
- 对其填充
对象头分两部分
对象头结构 | 说明 |
---|---|
Mark Work | 存储对象的hashcode,分代年龄,锁状态标识,线程持有的锁、偏向线程ID、是否偏向等信息。 |
Class Metadata Address | 类型指针指向对象的类元数据,JVM通过这个确定这个对象属于哪个类。 |
每个Java对象都有一个Monitor。
Monitor包含 锁池、等待池、owner、count 。
owner默认为NULL,当有线程获取该对象锁时,owner指向获取该对象锁的线程。
count默认0,用来计数。执行monitorenter获取则+1,释放锁monitorexit则-1。
syncronized关键字解析只会,会在同步代码块前后形成monitorenter和monitorexit这两个字节码。
Java线程是映射到操作系统的原生线程上的,如果要阻塞或唤起一个线程,都需要操作系统来帮忙,这就需要从用户态转换到核心态,因此线程状态转换需要消耗很多处理器时间。所以,syncronized是一个重量级锁。不过JDK6以后,JVM已对其做了优化,譬如自旋锁、自适应自旋锁、锁粗化、偏向锁、锁消除、轻量级锁等,使其性能得到很大的提升。
锁优化
通过让线程执行忙循环等待锁释放,不让出CPU资源,避免线程切换。
缺点:若锁被其他线程占用太长时间,会带来更大的性能开销,白白消耗CPU资源。
JIT编译时,对运行上下文扫描,去除不可能存在竞争的锁。
通过扩大加锁范围,避免反复加锁和解锁。