2.1、J.U.C和锁(中篇)
2.1.4、什么是CAS?
答:
CAS是Java中Unsafe类里面的方法,全称是CompareAndSwap,是比较并交换的意思。作用就是保证在多线程环境下,对于修改共享变量操作的原子性。
扩展:
CAS保证修改共享变量操作原子性的实现逻辑:
CAS方法里有三个参数,依次分别是共享变量的内存地址偏移量、当前值(原来的值)、新值(期望更改之后的值)。
- 当多个线程要修改同一个共享变量时,首先要读取该变量的当前值;
- 然后,将要修改的值(共享变量的内存地址偏移量对应的值)与当前值进行比较;
- 如果比较结果相等,说明当前值没有被其他线程修改过,可以进行修改操作;
- 使用原子操作将新值赋给共享变量;
- 如果比较结果不相等,说明当前值已经被其他线程修改过,此时需要重新读取当前值,并重复以上步骤。
CAS保证原子性的关键在于比较和交换操作是原子的,即在执行期间不会被其他线程干扰。说白了,就是保留了共享变量原来的值,更新之前,获取共享变量此时的值,判断是否与原来的值相等,如果相等则表示未被修改,执行更新操作;反之则放弃此次更新,重新获取共享变量当前值重试这些流程。
2.1.5、什么是乐观锁?什么是悲观锁?
答:
乐观锁和悲观锁并不是指具体的某种锁,而是一种思想。
- 乐观锁就是持乐观态度的锁,乐观锁在操作数据时认为别的线程不会同时修改数据,所以不会上锁,但是在更新数据的时候要判断在此期间数据有没有被别的线程修改过。比如说CAS就使用了乐观锁的思想。应用场景就是乐观锁适用于写少读多的情况,这样能够减少操作冲突,省去了锁竞争的开销,因为线程不需要竞争多资源。
- 悲观锁就是持悲观态度的锁,每次在操作数据时都认为别的线程也会同时修改数据,所以每次操作数据时都会上锁,这样别的线程想拿到这个数据就会阻塞,知道线程拿到锁。比如说行锁、表锁、读锁、写锁、还有Java API中的synchronized和ReentrantLock等独占锁都是用了悲观锁的思想,操作数据之前先上锁。应用场景就是悲观锁适用于写多读少的情况,因为遇到频繁操作数据的情况如果还使用乐观锁,就会经常出现操作冲突,进而导致应用层不断的重试,反而会降低系统的性能。
扩展:
其实看到这里自然也就明白了,使用并发编程其实就是要解决两个问题,其实也就是使用并发编程的逻辑:
1. 如何使用并发编程提高程序的性能?答案就是多线程的合理使用;
2. 如何解决使用并发编程所带来的并发冲突问题?答案就是锁的合理使用;
2.1.6、什么条件下会产生死锁?如何避免死锁?
答:
死锁是指两个或多个线程被永久阻塞,因为每个线程都在等待某个其他线程释放资源,而无法继续执行。这种情况发生在多个线程同时持有一些共享资源,并且每个线程还需要获取其他线程持有的资源。
产生死锁的四个必要条件:
- 互斥条件:资源不能被多个线程同时占用
- 请求和保持条件:线程持有资源并请求其他线程持有的资源
- 不可剥夺条件:线程已经获得的资源不能被其他线程强制性剥夺
- 循环等待条件:存在一个循环等待的资源链,每个线程都在等待下一个线程持有的资源
产生死锁后,就只能通过外部干预来解决问题,比如重启程序或者Kill线程;在产生死锁前,可以通过一些方式来避免死锁,上述产生死锁的四个必要条件破环其中一种就行:
- 对于互斥条件:这个没法破环,因为互斥条件是互斥锁的基本约束。
- 对于请求和保持条件:我们可以在首次执行时一次性申请所有的资源,这样就不存在等待锁的问题了。
- 对于不可剥夺条件:占用部分资源的线程在申请其他资源的时候如果申请不到,可以让该线程主动释放已占用的资源。
- 对于循环等待条件:可以通过按序申请资源来预防死锁的产生。按序申请就是给资源编号,所有线程都要按照线性化的序号申请共享资源。先申请序号小的,再申请序号大的。
2.1.7、synchronized和Lock的区别是什么?
答:
- 特性区别:synchronized是Java内置的一个关键字,而Lock是J.U.C包下的一个接口,它有很多实现类,比如ReentrantLock。
- 用法区别:synchronized可以直接在方法上或者代码块中使用;而Lock的使用需要通过Lock实现类的对象调用lock()方法和unlock()方法来获取锁和释放锁的。
示例:
// synchronized控制方法时
public synchronized void syncMethod() {
}
// synchronized控制代码块时
Object lock = new Object();
public void syncMethod() {
synchronized(lock) {
// 代码块中的逻辑...
}
}
// Lock的使用
Lock reentrantLock = new ReentrantLock();
pubilic void syncMethod() {
reentrantLock.lock(); //添加锁
// 线程安全的代码...
reentrantLock.unlock(); //释放锁
}
- 可重入性:synchronized是可重入锁,一个线程可以多次获取同一个锁。而Lock可以设置是否可重入,通过设置为不可重入可以避免死锁的发生。
- 锁的获取方式:synchronized是隐式获取锁,当线程进入synchronized代码块或方法时,会自动获取锁。而Lock需要显式地调用lock()方法来获取锁,同时还可以设置超时时间。
- 锁的释放方式:synchronized会在代码块或方法执行完毕后自动释放锁。而Lock需要显式地调用unlock()方法来释放锁,通常在finally块中使用。
- 条件变量:Lock提供了条件变量的功能,可以使用Condition对象在特定条件下挂起和唤醒线程。而synchronized没有直接提供条件变量,需要通过wait()、notify()和notifyAll()来实现。
标签:中篇,变量,synchronized,Lock,死锁,线程,2.1,多线程,共享 From: https://blog.csdn.net/weixin_61769871/article/details/141789370扩展:
synchronized是一种更简单、更方便的线程同步机制,适合大多数情况下的使用。而Lock则提供了更灵活、更强大的线程同步功能,适用于一些需要更精细控制的场景。
无论是synchronized关键字还是Lock锁,目的都是为了保证线程安全:也就是数据的一致性。当被问到多个线程之间如何共享数据并保证数据的一致性,使用synchronized关键字或Lock就是答案。