1. synchronized 锁的分类
对象锁( 非静态) , 类锁( 静态)
2. synchronized 使用案例:
* 同步普通方法:
public synchronized void method1 ( ) {
}
* 同步静态方法:
public static synchronized void method2 ( ) {
}
* 同步代码块:
synchronized ( this ) {
}
* 同步静态代码块:
synchronized ( Demo. class ) {
}
3. 对象锁和类锁的区别:
* 同一个字节码文件, 始终只有唯一的一个Class对象; 但是却可以有多个new 出来的对象实例
即: obj. getClass ( ) == Object. class == Class. forName ( "java.lang.Object" ) ;
Object obj1 = new Object ( ) ;
Object obj2 = new Object ( ) ;
obj1 != obj2 ;
obj1. getClass ( ) == obj2. getClass ( ) ;
4. 多线程访问同步方法的7 种案例:
* 两个线程访问的是一个对象的同一个同步方法
线程安全, 线程串行
* 两个线程访问的是同一个字节码文件的两个实例对象的同一个同步方法
线程不安全, 线程并行
* 两个线程访问的是synchronized 的静态方法
线程安全, 线程串行
* 两个线程分别访问同步方法和非同步方法
线程并行
* 访问同一个对象的不同的同步方法
线程安全, 线程串行
* 同时访问静态同步方法和静态非同步方法
线程不安全, 线程并行
5. synchronized 关键字的性质
* 可重入性: 线程得到对象锁之后, 可以再次获得该对象锁, synchronized 锁可重入性是线程级别的
public synchronized void method ( ) {
synchronized ( this ) {
synchronized ( this ) {
synchronized ( this ) {
System. out. println ( "hello..." ) ;
}
}
}
}
* 不可剥夺性: 线程获得锁对象后, 只有在方法执行完毕或者抛出异常, 才会释放锁, 否则其他线程不能干扰它的锁的释放
6. synchronized 关键字的原理:
* 同步代码块: monitorenter指令, monitorexit指令, monitor计数器
每一个对象的对象头中都保存有一个monitor对象( 管程或监视器锁) , synchronized 就是依赖于monitor对象实现的; synchronized 代码块会被编译为一段monitorenter, monitorexit指令; 当程序执行到monitorenter指令时, 线程会去尝试获取锁对象所对应的monitor持有权, 如果monitor计数器为0 , 则当前线程获得monitor持有权, 并且将monitor计数器+ 1 , 进入runnable状态; 否则进入锁阻塞状态; 当同步代码块执行完毕, 再执行monitorexit指令, 将monitor计数器- 1 ; jvm为了保证每一个monitor最终都能回到0 状态, 通常一个monitorenter会对应多个monitorexit指令( 比如: 正常结束- 1 , 抛异常- 1 ) ; sunchronized锁中, 抛出异常也会执行monitorexit指令
* 同步方法:
同步方法是隐式同步, jvm根据方法的标志flags下的ACC_SYNCHRONIZED来分辨一个同步方法, 当线程调用这个方法时, 现根据ACC_SYNCHRONIZED标志判断方法是否是一个同步方法, 然后尝试获取monitor对象, 已经被获取了的monitor对象无法再获取, 直到上一个线程释放monitor对象
7. synchronized 锁保证内存可见性的原理:
线程不安全情况下: 主内存会为每一个线程分配一个本地内存, 并且将主内存中的数据在各本地内存中保存一份副本, 当多线程开始操作这个数据时, 实际上各个线程操作的都是本地内存中的副本, 只有最后执行完毕才会更新到主内存, 因此, 导致了数据不一致
volatile 保存内存可见性: volatile 修饰后的数据, 子线程在对数据副本进行操作前, 会先从主内存中更新最新数据, 操作完毕后, 也会立刻将数据更新到主内存, 但是一个count++ 的操作将不再保持原子性; 因此多线程条件下, 除非能保证单线程修改数据, 其他线程只查询数据; 否则线程不安全
synchronized 保证内存可见性: synchronized 锁在获得monitor锁后, 会立即将数据从主内存中更新, 并且在count++ 这个过程中, 操作原子性, 最后释放monitor锁后, 将数据又更新到主内存中; 因此线程安全
8. synchronized 锁注意事项:
* 锁对象不能为null, 如果锁对象为null, 那么对象头中就没有monitor, 同步方法最终效果相当于直接跳过
* 锁的范围不宜过大, 因为synchronized 锁影响效率
* 注意避免死锁
9.d ebug: evaluate expression -- > this . getState ( ) 可以查看线程的状态