我是靠谱客的博主 机灵糖豆,最近开发中收集的这篇文章主要介绍Java的锁机制一、多线程下需要考虑加锁的原因二、Java的八大锁三、CAS算法,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

多线程下的锁机制

  • 一、多线程下需要考虑加锁的原因
    • 线程安全问题
    • 什么是线程阻塞
  • 二、Java的八大锁
    • 1.乐观锁 & 悲观锁
    • 2.公平锁 & 非公平锁
    • 3.独占锁 & 共享锁
    • 4.可重入锁:
    • 5.自旋锁:
  • 三、CAS算法
    • CAS简介
    • CAS的缺点
    • 什么是ABA问题

一、多线程下需要考虑加锁的原因

线程安全问题

什么是线程安全问题?
线程安全问题是指,某一线程从开始访问到结束访问某一资源期间,该资源数据被其他线程所修改,那么对于当前线程而言,该线程就发生了线程安全问题。表现形式为数据不一致,数据缺失等问题。

线程安全问题发生的条件:

  • 多线程环境下,即包括自己在内,存在多个线程。
  • 多线程环境下存在共享资源,并且多线程操作该共享资源。
  • 多线程对该资源的操作是非原子性的。

线程安全问题解决思路:
1.尽量不使用共享变量,将不必要的共享变量变成局部变量来使用。
2.使用加锁机制来确保,同一时刻只有一个线程可以获取到该共享变量。
3.使用ThreadLocal为每个线程创建共享变量的副本,各个线程独立操作,互不影响。

什么是线程阻塞

阻塞是指当一个线程占有了共享资源后,所有其他需要此资源的线程都要进入临界区挂起等待,一直不能工作,直到该线程获取到此临界资源,这种情况就是阻塞。如果一个线程一直不释放资源,那么其他线程就得一直阻塞等待。

阻塞问题的本质就是锁的性能问题。
解决方法:可以通过减少锁的持有时间,读写锁分离,减小锁粒度等方式来优化锁的性能。

二、Java的八大锁

1.乐观锁 & 悲观锁

悲观锁:
悲观锁认为,对于同一个数据的并发操作,自己在使用数据的时候,一定会有其他线程来修改数据。悲观锁悲观地认为,不加锁的并发操作一定会出问题。因此对于同一个数据的并发操作,悲观锁在获取数据的时候会先加锁,确保数据不会被别的线程修改。
synchronized 是悲观锁的实现,因为synchronized修饰的代码,每次执行时都会进行加锁操作,同时只允许一个线程进行操作,所以它是悲观锁的实现。

乐观锁:
乐观锁正好和悲观锁相反,乐观锁认为自己在使用数据时,不会有别的线程来修改数据,所以不会添加锁。只是在更新数据的时候去判断一下,之前有没有别的线程来更新过这个数据。如果这个数据没有被更新过,当前线程将自己修改后的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。
乐观锁在Java中是通过无锁编程来实现的,最常采用的就是CAS算法。Java原子类中的递增操作就通过CAS自旋实现的。CAS全称 Compare And Swap(比较和交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现的乐观锁。CAS算法的核心思想是,在更新数据的时候,通过判断现有的数据是否和原数据一致,来判断在此过程中,数据是否被其他线程修改过,如果没被其他线程修改过则进行数据更新。

2.公平锁 & 非公平锁

根据线程获取锁的抢占机制,锁又可以分为公平锁和非公平锁。非公平锁的优点在于吞吐量比公平锁大。

公平锁:
公平锁是指多个线程按照申请锁的顺序来获取锁,即先到先得,后到后得的队列策略。线程直接进入队列中排队,队列中的第一个线程才能获得锁。
公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。

非公平锁:
非公平锁是指多个线程获取锁的顺序,并不是按照申请锁的顺序,存在后申请的线程比先申请的线程优先获取锁的可能。多个线程在加锁时会直接尝试获取锁,获取不到才会到等待队列的队尾等待。
非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是有可能会造成优先级反转,处于等待队列中的线程甚至可能会被饿死,或者等很久才会获得锁。
对于Synchronized而言,也是一种非公平锁。另外,ReentrantLock 提供了公平锁和非公平锁的实现方式。如果构造函数不传任何参数的时候,默认提供的是非公平锁。带参构造时,true是公平锁,false是非公平锁。

3.独占锁 & 共享锁

根据锁能否被多个线程同时持有,可以把锁分为独占锁和共享锁。

独占锁:
独占锁是指任何时候,该锁一次只能被一个线程所持有,只有一个线程能执行资源操作。ReentrantLock和synchronized都是独占锁。

共享锁:
共享锁是指该锁可被多个线程所持有。可以同时被多个线程读取,但只能被一个线程修改。比如Java中的 ReentrantReadWriteLock ,其写锁是独占锁,它只允许一个线程进行写操作。其读锁是共享锁,允许多个线程执行读操作。读锁的共享锁可保证并发读是非常高效的,但读写、写读、写写的过程都是互斥的。

4.可重入锁:

可重入锁又名递归锁,指的是同一线程在外层方法获取锁之后,在进入内层方法时会自动获取锁。(前提锁对象得是同一个对象或者class)。也就是说,该线程获取了该锁之后,可以无限次的进入任何一个该锁锁住的代码块。Java中的ReentrantLock和synchronized就是典型的可重入锁,可重入锁最大的优点就是可在一定程度上避免死锁。

5.自旋锁:

自旋锁(spinlock)是指:当一个线程在尝试获取锁的时候,如果锁已经被其它线程获取,那么该线程不会立即阻塞,而是采用while循环的方式去尝试获取锁。总是不断的通过循环去判断锁是否能够被成功获取,直到获取到锁才会退出循环。这样做的好处是,可以减少线程上下文切换所带来的消耗。缺点是,循环会消耗大量的CPU资源。

三、CAS算法

CAS简介

CAS(Compare And Swap),即比较并交换。首先进行比较,比较当前线程工作内存中的值和主内存中的值是否相同,如果相同,则执行值替换操作。否则就获取主内存中的最新值,继续进行比较,直到主内存中的值和工作内存中的值一致为止。
CAS全称为Compare-And-Swap,它是一条CPU并发原语。它的功能是判断内存某个地址的值是否为预期值,如果是则更改为新的值,并且,这个过程是原子性的。
例如:java.util.concurrent包中的原子性整数类AtomicInteger的底层就用到了CAS。其中比较重要的方法有compareAndSet()和getAndIncrement()。

CAS的缺点

  1. 当多线程的并发情况严重时,一个地址处的值可能会被频繁的进行修改,如果CAS的比较方式一直无法比较成功,多次while循环的条件判断,可能会给CPU带来很大的开销。
  2. 当对一个共享变量执行操作时,可以通过循环CAS的方式来保证原子性操作。但是,对多个共享变量进行操作时,CAS就无法保证操作的原子性,这个时候需要用锁来保证原子性。
  3. CAS比较方式 ,会出现 ABA问题。

什么是ABA问题

ABA问题指的是,一个线程x从主内存V处取出值A,这时另外一个线程y也从主内存中取出值A,并且线程y进行了一系列操作后将值A变成了B,然后可能其他线程又将V位置处的数据变回了A,这时线程x进行CAS操作,发现内存中的值仍然是A,然后线程x操作成功。
尽管线程x的CAS操作是成功的,但并不能代表在这个过程中,其他线程没有进行过其他操作。(AtomicReference类无法避免ABA问题,但AtomicStampedReference类可以解决ABA问题,它是通过添加一个版本号来解决ABA问题的)。

最后

以上就是机灵糖豆为你收集整理的Java的锁机制一、多线程下需要考虑加锁的原因二、Java的八大锁三、CAS算法的全部内容,希望文章能够帮你解决Java的锁机制一、多线程下需要考虑加锁的原因二、Java的八大锁三、CAS算法所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(64)

评论列表共有 0 条评论

立即
投稿
返回
顶部