各种锁
- 从线程是否需要对资源加锁可以分为
悲观锁
和乐观锁
- 从资源已被锁定,线程是否阻塞可以分为
自旋锁
- 从多个线程并发访问资源,也就是 Synchronized 可以分为
无锁
、偏向锁
、轻量级锁
和重量级锁
- 从锁的公平性进行区分,可以分为
公平锁
和非公平锁
- 从根据锁是否重复获取可以分为
可重入锁
和不可重入锁
- 从那个多个线程能否获取同一把锁分为
共享锁
和排他锁
锁机制:一种保护机制,在多线程的情况下,保证操作数据的正确性 / 一致性
悲观锁 认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改
悲观锁
是一种悲观思想,它总认为最坏的情况可能会出现,它认为数据很可能会被其他人所修改,所以悲观锁在持有数据的时候总会把资源
或者 数据
锁住,这样其他线程想要请求这个资源的时候就会阻塞,直到等到悲观锁把资源释放为止。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。悲观锁的实现往往依靠数据库本身的锁功能实现。
Java 中的 Synchronized
和 ReentrantLock
等独占锁(排他锁)也是一种悲观锁思想的实现,因为 Synchronzied 和 ReetrantLock 不管是否持有资源,它都会尝试去加锁,生怕自己心爱的宝贝被别人拿走。
乐观锁 乐观锁的思想与悲观锁的思想相反,它总认为资源和数据不会被别人所修改,所以读取不会上锁,但是乐观锁在进行写入操作的时候会判断当前数据是否被修改过(具体如何判断我们下面再说)。乐观锁的实现方案一般来说有两种:版本号机制
和 CAS实现
。乐观锁多适用于多读的应用类型,这样可以提高吞吐量。
在Java中java.util.concurrent.atomic
包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的。
可重入锁:自己可以再次获取自己的内部的锁。比如有线程A获得了某对象的锁,此时这个时候锁还没有释放,当其再次想获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。
公平锁 多个线程按照 申请锁的顺序 来获取锁
优点 等待锁的线程不会饿死
缺点 整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞
CPU唤醒阻塞线程的开销比非公平锁大
非公平锁 多个线程获取锁的顺序 并不是按照申请锁的顺序,多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待 可以插队。
可重入锁 广义上的可重入锁指 可重复可递归调用 的锁,在外层使用锁之后,在内层仍然可以使用(前提得是同一个对象或者class)
wait与sleep的区别
- 来自不同的类
wait=》Object
sleep=》Thread - wait释放锁,sleep抱着锁睡觉
- wait必须在同步代码快中,sleep可以在任何地方睡觉
- wait不需要捕获异常,sleep需要捕获异常(可能发生超时等待)
Sychronized和lock的区别
都是锁
1.Sychronized 内置的java关键字,Lock锁是一个java类
2.Sychronized 无法判断获取锁的状态,Lock锁可以判断是否获取到了锁.
3.Sychronized 会自动释放锁lock必须手动释放锁,如果不释放锁,死锁
4.Sychronized 线程一(获得锁,阻塞),线程二(等待,傻傻的等),Lock锁就不一定会等待下去.
5.Sychronized 可重入锁,不可以中断,非公平;Lock,可重入锁,可以中断锁,非公平(可以自己设置)
6.Sychronized 适合锁少量的代码的同步问题,Lock适合锁大量的代码同步问题.
Sychronized关键字
Sychronized可以理解为排队,锁上一个资源的时候别的线程排队等候。
synchronized 机制是 给共享资源上锁,只有拿到锁的线程才可以访问共享资源,这样就可以强制使得对共享资源的访问都是顺序的
Synchronized 是Java 关键字,属于Java 的内置特性
基于 JVM 来保证数据同步
无需手动释放锁,会一直等待
/**
* @author : LeeGaki
* @date : 2022/6/8 9:12
* 面向面试编程 --> 李佳琪
*/
public class SysLock {
public static void main(String[] args) {
Bank bank = new Bank();
new Thread(()->{for (int i = 0; i < 50; i++) {
bank.sale();
}
},"小李").start();
new Thread(()->{for (int i = 0; i < 50; i++) {
bank.sale();
}
},"+++小李").start();
}
}
class Bank{
private int money=100;
public synchronized void sale(){
System.out.println(Thread.currentThread().getName()+"取出了1块,剩余"+(--money));
}
}
Lock锁
Lock是一个接口实现类有三个
可重入锁和写锁 读锁
ReettrantLock类 可重入锁 实现了Lock,它拥有与synchronized相同的并发性和内存语义,显示的加锁、释放锁。
/**
* @author : LeeGaki
* @date : 2022/6/8 9:12
* 面向面试编程 --> 李佳琪
*/
public class SysLock {
public static void main(String[] args) {
Bank bank = new Bank();
new Thread(()->{for (int i = 0; i < 50; i++) {
bank.sale();
}
},"小李").start();
new Thread(()->{for (int i = 0; i < 50; i++) {
bank.sale();
}
},"+++小李").start();
}
}
class Bank{
private int money=100;
Lock lock = new ReentrantLock();
public void sale(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"取出了1块,剩余"+(--money));
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
集合的安全性
ConcurrentModificationException 并发修改异常
快速失败机制使得java的集合类不能在多线程下并发修改,也不能在迭代过程中被修改。
在使用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了增删改,就会抛出该异常。
fail-fast 机制,即快速失败机制,是java集合(Collection)中的一种错误检测机制。当在迭代集合的过程中该集合在结构上发生改变的时候,就有可能会发生fail-fast,即抛出 ConcurrentModificationException异常。fail-fast机制并不保证在不同步的修改下一定会抛出异常,它只是尽最大努力去抛出,所以这种机制一般仅用于检测bug。
产生问题的代码
/**
* @author : LeeGaki
* @date : 2022/6/8 22:07
* 面向面试编程 --> 李佳琪
*/
public class ListArray {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},"A").start();
}
}
}
解决方案3种
List<String> list = new Vector<>();中
/**
* @author : LeeGaki
* @date : 2022/6/8 22:07
* 面向面试编程 --> 李佳琪
*/
public class ListArray {
public static void main(String[] args) {
List<String> list = new Vector<>();
for (int i = 0; i < 100; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},"A").start();
}
}
}
add方法被synchronized修饰了
然后就是集合祖宗中创建
Java 中 ArrayList 和 LinkedList 都不是线程安全的,但可以通过 java.util.Collections.synchronizedList(List list) 方法,获取一个线程安全的 List 实例对象。在对原始arraylist操作时,都会增加关键字synchronized 保证线程安全(类似静态代理)。
/**
* @author : LeeGaki
* @date : 2022/6/8 22:07
* 面向面试编程 --> 李佳琪
*/
public class ListArray {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 100; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},"A").start();
}
}
}
List<String> list = new CopyOnWriteArrayList<>();
创建写入复制集合
/**
* @author : LeeGaki
* @date : 2022/6/8 22:07
* 面向面试编程 --> 李佳琪
*/
public class ListArray {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 100; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},"A").start();
}
}
}