(Android多线程:这是一份全面&详细的Synchronized学习指南
synchronized常用的使用方式
- 修饰代码块,即同步语句块,其作用的范围是大括号{}扩区来的代码
- 修饰普通方法,即同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象
- 修饰静态方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象
使用规则
- 锁对象设置
a。修饰代码块时,需1个reference对象, 作为锁的对象
// 修饰代码块
public void synMethod(Object a1) {
synchronized (a1) { // 需要一个实例
}
}
b。修饰实例方法时,默认的锁对象 = 当前对象
// 修饰实例方法
public synchronized void increase1() {
//i++;
}
c。修饰类方法(静态)时,默认的锁对象 = 当前类的Class对象
// 修饰静态方法
public static synchronized void increase2() {
//i++;
}
- 根据锁对象的不同,一把锁同时最多只能被一个线程持有
- 若目标锁已被当前线程持有,其它线程只能阻塞等待当前线程释放目标锁
- 若当前线程已持有目标锁,其它线程仍然可以调用目标类中无被synchronized修饰的方法
- 当对象获取多个锁时,必须以相反的顺序释放&在锁哦有锁被获取时相同的词法范围内释放所有锁
- 若线程进入由线程已拥有的监控器保护的synchronized块,就允许线程继续进行
- 只有线程推出它进入的监控器保护的第一个synchronized块时,才释放锁
- 特别注意
- Java类中,实例对象会有多个,但只有1个Class对象
即 类的不同实例 共享该类的Class对象
实际上,Class对象也属于Java对象, 只是有点儿特殊 - 静态方法&实例方法上的锁默认不一样
若同步则需指定两把锁
静态方法加锁,能和所有其他静态方法加锁的 进行互斥
直接属于类,效果同xx.class锁
特点
- 原子性
一个操作 / 一系列操作
(要么全部执行/全部执行)
(数据库中的“事物” = 典型的原子操作) - 可见性
当一个线程修改了共享属性的值后,其它线程能立刻看到共享属性值的更改
(JMM的内存空间分为:主存&工作内存 :变量存于主存中;线程使用的是自身工作内存,更新后再同步到主存)
(共享属性的修改过程;从主存中读取&复制到工作内存中,在工作内存中修改完成后,再同步到主存、从而刷新主存中的值)
(若线程A在工作内存中修改完成单还未刷新主存中的值,线程B看到的值还是旧值。此时,可见性就无法保证) - 有序性
程序的运行顺序看起来和我们编写逻辑的顺序一致,单计算机在实际执行中却并不一定
(为了提高性能,编译器&处理器都会对代码进行重新排序)
(但有个前提:重新排序的结果和单线程执行程序顺序一致)
Synchronized 和 ReenTrantLock的对比
- 两者都是可重入锁
- synchronized依赖于JVM而ReenTrantLock依赖于API
- ReenTrantLock比synchronized增加了一些高级功能
ReenTrantLock多了三点:1. 等待可中断 2. 可实现公平锁 3. 可实现选择性通知 - 性能已不是选择标准
synchronized与ThreadLocal的对比
- synchronized关键字主要解决多线程共享数据同步问题;ThreadLocal主要解决多线程中数据因并发产生不一致问题。
- synchrozined关键字是利用锁的机制,使变量或代码块只能 被一个线程访问。而ThreadLocal为每一个线程都提供变量的副本,使得每个线程访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。
ReentrantLock原理
使用
public static void main(String[] args) {
Lock lock = new ReentrantLock();
lock.lock();
try {
System.out.println("Hello World!!");
} finally {
lock.unlock();
}
}
AQS
- 非公平锁:如果同时还有另一个线程进来尝试获取,那么有可能会让这个线程抢先获取
- 公平锁:如果同时还有另一个线程进来尝试获取,当它发现自己不是在队首的话,就会排到队尾,由队尾的线程获取到锁
NonfairSync
- 第一步。尝试去获取锁。如果尝试获取锁成功,方法直接返回。
- 第二步。入队。
- 第三部。挂起。