多线程中锁和集合的安全性

各种锁

  • 从线程是否需要对资源加锁可以分为 悲观锁 和 乐观锁
  • 从资源已被锁定,线程是否阻塞可以分为 自旋锁
  • 从多个线程并发访问资源,也就是 Synchronized 可以分为 无锁偏向锁、 轻量级锁 和 重量级锁
  • 从锁的公平性进行区分,可以分为公平锁 和 非公平锁
  • 从根据锁是否重复获取可以分为 可重入锁 和 不可重入锁
  • 从那个多个线程能否获取同一把锁分为 共享锁 和排他锁

锁机制:一种保护机制,在多线程的情况下,保证操作数据的正确性 / 一致性

悲观锁 认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改

悲观锁是一种悲观思想,它总认为最坏的情况可能会出现,它认为数据很可能会被其他人所修改,所以悲观锁在持有数据的时候总会把资源 或者 数据 锁住,这样其他线程想要请求这个资源的时候就会阻塞,直到等到悲观锁把资源释放为止。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。悲观锁的实现往往依靠数据库本身的锁功能实现。

Java 中的 Synchronized 和 ReentrantLock 等独占锁(排他锁)也是一种悲观锁思想的实现,因为 Synchronzied 和 ReetrantLock 不管是否持有资源,它都会尝试去加锁,生怕自己心爱的宝贝被别人拿走。

乐观锁 乐观锁的思想与悲观锁的思想相反,它总认为资源和数据不会被别人所修改,所以读取不会上锁,但是乐观锁在进行写入操作的时候会判断当前数据是否被修改过(具体如何判断我们下面再说)。乐观锁的实现方案一般来说有两种:版本号机制 和 CAS实现 。乐观锁多适用于多读的应用类型,这样可以提高吞吐量。

在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的。

可重入锁:自己可以再次获取自己的内部的锁。比如有线程A获得了某对象的锁,此时这个时候锁还没有释放,当其再次想获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。

公平锁 多个线程按照 申请锁的顺序 来获取锁

优点   等待锁的线程不会饿死

缺点   整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞

          CPU唤醒阻塞线程的开销比非公平锁大

非公平锁  多个线程获取锁的顺序 并不是按照申请锁的顺序,多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待  可以插队。

可重入锁 广义上的可重入锁指 可重复可递归调用 的锁,在外层使用锁之后,在内层仍然可以使用(前提得是同一个对象或者class)

wait与sleep的区别

  1. 来自不同的类
    wait=》Object
    sleep=》Thread
  2. wait释放锁,sleep抱着锁睡觉
  3. wait必须在同步代码快中,sleep可以在任何地方睡觉
  4. 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();
        }
    }
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LeeGaKi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值