一、线程安全
多个线程同时访问 同一个共享变量 时 , 只要能保证 数据一致性 , 那么该变量是线程安全的 ; 这里的数据是指主内存中的共享变量以及各个线程中的变量副本 , 保证这些变量一致 , 就是线程安全 ;
线程安全 就是保证 线程操作的 原子性 , 可见性 , 有序性 ;
volatile 关键字可以保证 可见性 与 有序性 ;
synchronized 关键字可以保证 原子性 ;
二、锁机制 ( 类锁 | 对象锁 )
synchronized 是 Java 提供的一种锁机制 ;
在普通方法上加锁 , 相当于对 this 进行加锁 ; 下面两个类的 fun 方法的线程锁是等效的 ;
public class Student {
private synchronized void fun() {
}
}
public class Student {
private void fun() {
synchronized(this){
}
}
}
加锁的代码块 , 在同一个时间 , 只能由
1
1
1 个线程访问 ;
对象锁 : synchronized() 代码块中 , 括号中的参数是 作用范围 ; synchronized(this) 表示作用范围只针对当前对象 , 如果 创建了多个对象 , 这几个对象中的锁都是 不同的锁 , 相互之间没有任何关系 ;
Student s1 = new Student();
Student s2 = new Student();
只有当多个线程 , 访问同一个对象时 , 锁才有意义 ;
如 : 线程 A 访问 s1 对象的 fun 方法 , 线程 B 访问 s2 对象的 fun 方法 , 两个方法之间 没有互斥效果 ; 线程 A 访问 s1 对象的 fun 方法 , 线程 B 也想访问 s1 对象的 fun 方法 , 此时必须 等待线程 A 访问完毕 , 释放锁之后 , 才能由线程 B 访问 s1 ;
类锁 : 如果加锁的对象是静态方法 , 那么相当于在 Student.class 类上进行加锁 ; Student.class 对象全局只有
1
1
1 个 , 调用所有对象的 fun 方法 , 都是互斥的 ;
public class Student {
private synchronized static void fun() {
}
}
等价于
public class Student {
private static void fun() {
synchronized(Student.class){
}
}
}
三、锁分类 ( 轻量级锁 | 重量级锁 )
如果线程 A 获得锁之后 , 执行线程内容 , 其它线程等待解锁时有两种情况 :
- 轻量级锁 : 又称为 自旋锁 , 线程 盲等待 或 自旋等待 , 即 while 循环 , 没有进入阻塞状态 , 没有进入等待队列中排队 ; ( 轻量级 )
- 重量级锁 : 线程进入 等待队列 , 排队等待线程 A 执行完毕 ; 在该队列的线程 , 需要 等待 OS 进行线程调度 , 一旦涉及到操作系统 , 量级就变重 , 效率变低 ; ( 重量级 )
轻量级锁弊端 : 轻量级锁 不一定 比重量级锁 更好 ; 轻量级锁 等待过程中 , 高速执行循环代码 , 如果循环的时间很短 , 时间效率上很高 ; 但是一旦执行时间很长 , 比如连续执行十几秒甚至几分钟 , 浪费了大量的 CPU 资源 ;
使用场景 :
- 轻量级锁 : 轻量级锁只适合 线程少 , 等待时间短的 应用场景 , 如果线程很多 , 等待时间过长 , 会造成 CPU 大量浪费 ;
- 重量级锁 : 重量级锁等待过程中 , 线程处于阻塞状态 , 效率可能低一些 , 但是不会造成资源浪费 , 如果 线程很多 , 或 等待时间很长 , 适合使用重量级锁 ;
|