锁 使用锁可以保证多线程的环境下同步执行,可以解决可见性/有序性/原子性问题。 CAS( CAS只能针对单个变量进行原子操作;不能操作代码块。( 悲观锁 乐观锁 当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断 锁是否能够被成功获取,直到获取到锁才会退出循环。 阻塞或唤醒一个 自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。 一个基于 定义锁 测试自定义锁 自适应意味着对于一个锁对象,线程的自旋时间是根据上一个持有该锁的线程的自旋时间以及状态来确定的。 例如:对于锁 线程1准备用 代码示例 AtomicStampedReference源码 测试代码 使用 原子性就是指一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。 当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁。就是一个线程拥有了锁仍然还可以重复申请该锁。 在没有多线程竞争的情况下,轻量级锁的获取以及释放多次 通过 判断 判断 通过 如果执行 只有当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,偏向锁的撤销由 依赖包 代码案例实现 在多线程交替执行同步块的情况下,尽量避免重量级锁引起的性能消耗,直接使用轻量级锁。但是如果多个线程在同一时刻进入临界区,会导致轻量级锁膨胀升级重量级锁,所以轻量级锁的出现并非是要替代重量级锁。 当关闭偏向锁功能,或多个线程竞争偏向锁导致锁升级为轻量级锁,会尝试获取轻量级锁,其入口位于 轻量级锁的释放通过 重量级锁通过对象内部的监视器 锁膨胀过程通过 锁消除是虚拟机对锁的优化处理,JIT即使编译器(把热点代码编译成与本地平台相关的机器码,并且进行各种层次的优化)对运行上下文进行扫描,去除不可能存在竞争的锁,比如下面两个方法的执行效率是一样的,由于object锁是私有变量,不存在竞争关系。 锁粗化湿 虚拟机对锁的另一种优化处理。通过扩大锁的范围,避免反复加锁和释放锁。比如下面两个方法经过锁粗化优化之后执行效率一样了。
Java基础-Java中常用的锁机制与使用
lock
或互斥mutex
是一种同步机制,主要用于在存在多线程的环境中强制对资源进行访问限制。锁的主要作用为强制实施互斥排他以及并发控制策略。锁一般需要硬件支持才可以有效的实施。这种支持通常采取一个或多个原子指令的形式,如test-and-set
,fetch-and-add
或者compare-and-swap
。这些指令允许单个进程测试锁是否空闲,如果空闲,则通过单个原子操作获取锁。一、锁的类型
二、CAS
Compare And Swap
),比较并交换,为乐观锁。适用场景分析
AtomicReference
原子引用类可解决多属性变量的问题),适用于资源竞争较少(线程冲突较轻)的情况,自旋(循环)时间不会很长,不会浪费cpu
太多资源。import java.util.ArrayList; import java.util.concurrent.atomic.AtomicInteger; public class AtomicityDemo { private static final AtomicInteger number = new AtomicInteger(1); public static void main(String[] args) throws InterruptedException { ArrayList<Thread> list = new ArrayList<>(); for (int i = 0; i < 5; i++) { Thread thread = new Thread(() -> { for (int j = 0; j < 1000; j++) { number.getAndIncrement(); } }); thread.start(); list.add(thread); } for (Thread thread : list) { thread.join(); } number.decrementAndGet(); System.out.println("number:" + number); } }
悲观锁与乐观锁
CAS原理
CAS
有三个操作数,即内存值 v
,旧操作数a
,新操作数b
。当需要更新v
为b
时,需要先判断v
值是否与之前的所见值a
相同,若相同则将v
赋值为b
,若不同,则什么都不做。CAS
是一种非阻塞算法。利用JNI
机制和CPU
的lock cmpxchg...
指令来完成。JNI
机制:https://www.cnblogs.com/kexinxin/p/11689641.html自旋锁/自适应自旋锁
自旋锁
优点
Java
线程需要操作系统切换 CPU
状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。所以自旋锁就可以避免 CPU
切换带来的性能、时间等影响。缺点
使用方式
CAS
实现的自旋锁:AtomicInteger
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4 return var5; }
do while
循环操作中的判断条件compareAndSwapInt
方法。整个“比较+更新”操作封装在compareAndSwapInt()
中,整个过程就是自旋的去重试这个操作。自定义自旋锁
import java.util.concurrent.atomic.AtomicReference; /** * @program: Demo * @description: 自定义自旋锁 * @author: 岚樱时 * @date: 2020-05-26 09:43 */ public class CustomSpinLock { /** * 存放当前持有锁的线程 * 当reference为null,锁没有被线程获取 */ private AtomicReference<Thread> reference = new AtomicReference<>(); /** * 获取锁 */ public void lock(){ Thread currentThread = Thread.currentThread(); while (!reference.compareAndSet(null, currentThread)) { // 自旋判断锁是否被其他线程持有 // reference为null时,当前锁没有被线程获取,compareAndSet返回true // TODO } } /** * 释放锁 */ public void unLock(){ Thread currentThread = Thread.currentThread(); if (reference.get() != currentThread) { // 锁被其他线程占用,当前线程无法释放锁 return; } // 释放锁 reference.set(null); } }
import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @program: Demo * @description: 自定义自旋锁测试类 * @author: 岚樱时 * @date: 2020-05-26 10:15 */ public class TestCustomSpinLock { static int count = 0; public static void main(String[] args) throws InterruptedException { // 创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(100); // 创建计数器 // 计数器的初始值是线程的数量,每当一个线程执行完成之后,计数器的值-1 // 当计数器的值为0,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作 CountDownLatch countDownLatch = new CountDownLatch(100); // 创建自定义自旋锁 CustomSpinLock customSpinLock = new CustomSpinLock(); for (int i = 0; i < 100; i++) { executorService.execute(() -> { customSpinLock.lock(); ++count; customSpinLock.unLock(); // 释放锁后,计数器值-1 countDownLatch.countDown(); }); } // 调用await()方法,线程被挂起,等待计数器为0后继续执行 countDownLatch.await(); System.out.println(count); executorService.shutdown(); } }
lock()
方法利用的CAS
,当第一个线程A
获取锁的时候,成功获取到锁,不会进入while
循环。A
没有释放锁,另一个线程B
想获取锁,此时由于不满足CAS
,所以就会进入while
循环,不断判断是否满足CAS
,直到线程A
调用unlock
方法释放锁,线程B
才会获取锁。启用自旋锁
参数
默认值或限制
说明
-XX:-UseSpinning
java1.4.2和1.5需要手动启用,java6默认已启用
启用多线程自旋锁优化
-XX:PreBlockSpin=10
-XX:+UseSpinning必须先启用。对于jdk6来说已经默认启用了,默认自旋10次。
控制多线程自旋锁优化的自旋次数
自适应自旋锁
A
对象来说,如果一个线程刚刚通过自旋获得了锁,并且该线程也在运行中,那么JVM
会认为此次自旋操作也是有很大的机会可以拿到锁,因此它会让自旋的时间相对延长。但是如果对于锁B
对象自旋操作很少成功的话,JVM
甚至可能直接忽略自旋操作,将线程挂起或堵塞。ABA问题
CAS
修改变量A
,在此之前,其他线程将变量的值由A
替换为B
,又由B
替换为A
,然后线程1执行CAS
时发现变量的值仍然为A
,所以CAS
成功。但是实际上这时的现场已经和最初不同。import java.util.concurrent.atomic.AtomicInteger; /** * @program: Demo * @description: 测试ABA问题案例 * @author: 岚樱时 * @date: 2020-05-26 11:00 */ public class AtomicDemo { public static AtomicInteger atomicInteger = new AtomicInteger(1); public static void main(String[] args) { Thread thread1 = new Thread(() -> { System.out.println("当前线程:" + Thread.currentThread() + "n初始值:" + atomicInteger); try { // 线程1等待,线程2对数据进行修改 Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // CAS 期望1->2 boolean CASStatus = atomicInteger.compareAndSet(1, 2); System.out.println("当前线程:" + Thread.currentThread() + "nCAS操作结果:" + CASStatus); }, "线程1"); Thread thread2 = new Thread(() -> { try { // 线程等待,确保线程1先执行 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // atomicInteger+1=>2 atomicInteger.incrementAndGet(); System.out.println("当前线程:" + Thread.currentThread() + "nincrementAndGet结果:" + atomicInteger); // atomicInteger-1=>1 atomicInteger.decrementAndGet(); System.out.println("当前线程:" + Thread.currentThread() + "ndecrementAndGet结果:" + atomicInteger); }, "线程2"); thread1.start(); thread2.start(); } }
解决方案
AtomicStampedReference
主要维护一个对象引用以及一个可以自动更新的整数stamp
的pair
对象来解决ABA
问题。package java.util.concurrent.atomic; /** * An {@code AtomicStampedReference} maintains an object reference * along with an integer "stamp", that can be updated atomically. * * <p>Implementation note: This implementation maintains stamped * references by creating internal objects representing "boxed" * [reference, integer] pairs. * * @since 1.5 * @author Doug Lea * @param <V> The type of object referred to by this reference */ public class AtomicStampedReference<V> { private static class Pair<T> { // 维护对象引用 final T reference; // 用于标志版本 final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static <T> Pair<T> of(T reference, int stamp) { return new Pair<T>(reference, stamp); } } private volatile Pair<V> pair; /****/ /** * expectedReference 更新之前的原始值 * newReference 将要更新的新值 * expectedStamp 期待更新的标志版本 * newStamp 将要更新的标志版本 */ public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); } /****/ }
import java.util.concurrent.atomic.AtomicStampedReference; /** * @program: Demo * @description: 解决ABA案例 * @author: 岚樱时 * @date: 2020-05-26 12:50 */ public class AtomicDemo1 { private static final AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 0); public static void main(String[] args) { Thread thread1 = new Thread(() -> { System.out.println("当前线程:" + Thread.currentThread() + "n初始值a:" + atomicStampedReference.getReference() + "n"); // 获取当前标识版本 int stamp = atomicStampedReference.getStamp(); try { // 线程等待3s,保证干扰线程执行 Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // 判断reference&&stamp boolean CASStatus = atomicStampedReference.compareAndSet(1, 2, stamp, stamp + 1); System.out.println("当前线程:" + Thread.currentThread() + "nCAS判断结果:" + CASStatus + "n"); }, "线程1"); Thread thread2 = new Thread(()->{ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); System.out.println("当前线程:" + Thread.currentThread() + "nincrement值:" + atomicStampedReference.getReference()+"n"); atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); System.out.println("当前线程:" + Thread.currentThread() + "ndecrement值:" + atomicStampedReference.getReference() + "n"); },"线程2"); thread1.start(); thread2.start(); } }
三、synchronized
synchronized
关键字,可以解决可见性/有序性/原子性问题。使用方式
/** * @program: Demo * @description: Synchronized关键字使用方式案例 * @author: 岚樱时 * @date: 2020-05-26 13:38 */ public class SynchronizedDemo { static int counter = 0; static final Object lock = new Object(); public static void main(String[] args) { // 锁是括号里面的对象 synchronized (lock) { counter++; } } /** * 锁是当前类对象 */ public synchronized static void m1(){ counter++; } }
特性
原子性
可见性
synchronized
对一个类或对象加锁时,一个线程如果要访问该类或对象必须先获得它的锁,而这个锁的状态对于其他任何线程都是可见的,并且在释放锁之前会将对变量的修改刷新都主存当中,保证资源变量的可见性,如果某个线程占用了该锁,其他线程就必须在锁池中等待锁的释放。有序性
synchronized
保证了每个时刻都只有一个线程访问同步代码块,也就确定了线程执行同步代码块是分先后顺序的,保证了有序性。可重入性
偏向锁
原理
CAS
原子指令,而偏向锁只依赖一次CAS
原子指令置换ThreadID
,不过一旦出现多个线程竞争时必须撤销偏向锁,所以撤销偏向锁消耗的性能必须小于之前节省下来的CAS
原子操作的性能消耗。
JDK1.6
之后默认开启偏向锁XX:BiasedLockingStartupDelay=0
参数关闭延迟-XX:-UseBiasedLocking
参数关闭偏向锁。偏向锁获取
markup mark = obj->mark()
获取对象的markOop
数据mark
,即对象头的Mark Word
mark
是否为可偏向状态,即mark
的偏向锁标志位为1,锁标志位为01
mark
中JavaThread
的吗状态
CAS
原子指令设置mark
中的JavaThread
为当前线程ID
,执行CAS
成功,执行同步代码块CAS
失败,表示当前存在多个线程竞争锁,火的偏向锁的线程达到全局安全点(safepoint
)时被挂起,撤销偏向锁,并升级为轻量级。升级完成后被阻塞在安全点的线程继续执行同步代码块偏向锁撤销
BiasedLocking::revoke_at_safepoint
方法实现。
实践
偏向锁的开启关闭
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core --> <dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.10</version> </dependency>
import org.openjdk.jol.info.ClassLayout; /** * @program: Demo * @description: 偏向锁Demo * @author: 岚樱时 * @date: 2020-05-26 14:35 */ public class BiasedLockDemo { public static void main(String[] args) throws InterruptedException { System.out.println("开启偏向锁"); biasedLockStart(); System.out.println("关闭偏向锁"); biasedLockClose(); } /** * 开启偏向锁 * @throws InterruptedException */ public static void biasedLockStart() throws InterruptedException{ // 加锁前,偏向锁生效有延迟,不会立即生效,输出001 String result = ClassLayout.parseInstance(new BiasedLockDemo.Model()).toPrintable(); System.out.println("result:" + result); Thread.sleep(5000); // 加锁前,延迟一定时间后,偏向锁生效,输出101 Model model = new BiasedLockDemo.Model(); result = ClassLayout.parseInstance(model).toPrintable(); System.out.println("result:" + result); synchronized (model) { result = ClassLayout.parseInstance(model).toPrintable(); System.out.println("result:" + result); } // 解锁后,对象头不变 result = ClassLayout.parseInstance(model).toPrintable(); System.out.println("result:" + result); } /** * 关闭偏向锁 * @throws InterruptedException */ public static void biasedLockClose() throws InterruptedException { // 加锁前,输出001 String result = ClassLayout.parseInstance(new Model()).toPrintable(); System.out.println(result); Thread.sleep(5000); System.out.println("================================================"); // 加锁前,延迟i一定时间后,仍输出001 Model model = new Model(); result = ClassLayout.parseInstance(model).toPrintable(); System.out.println(result); System.out.println("================================================"); // 加锁时,输出轻量级锁信息 线程栈中的lock record锁记录指针和00状态位 synchronized (model) { result = ClassLayout.parseInstance(model).toPrintable(); System.out.println(result); System.out.println("================================================"); } // 解锁后,输出001,无锁状态 result = ClassLayout.parseInstance(model).toPrintable(); System.out.println(result); } public static class Model{ } }
开启偏向锁 OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 90 00 0b (00000101 10010000 00000000 00001011) (184586245) 4 4 (object header) b6 7f 00 00 (10110110 01111111 00000000 00000000) (32694) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 90 00 0b (00000101 10010000 00000000 00001011) (184586245) 4 4 (object header) b6 7f 00 00 (10110110 01111111 00000000 00000000) (32694) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 关闭偏向锁 OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total ================================================ OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total ================================================ OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 90 00 0b (00000101 10010000 00000000 00001011) (184586245) 4 4 (object header) b6 7f 00 00 (10110110 01111111 00000000 00000000) (32694) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total ================================================ OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 90 00 0b (00000101 10010000 00000000 00001011) (184586245) 4 4 (object header) b6 7f 00 00 (10110110 01111111 00000000 00000000) (32694) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
轻量级锁
轻量级锁获取
ObjectSynchronizer::slow_enter
轻量级锁释放
ObjectSynchronizer::fast_exit
完成
BasicLock
对象的mark
数据dhw
CAS
尝试把dhw
替换到当前的Mark Word
,如果CAS
成功,说明成功的释放了锁,否则执行步骤3CAS
失败,说明有其他线程在尝试获取锁,这是该锁已经升级为重量级锁。执行重量级锁释放流程。重量级锁
monitor
实现,其中monitor
的本质是依赖于底层操作系统的MutexLock
实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。ObhectSynchronizer::inflate
函数实现。monitor对象
锁膨胀
各种锁比较
锁
优点
缺点
使用场景
偏向锁
加锁不需要额外消耗,执行非同步方法仅有纳秒差距。
如果线程见存在竞争,会带来额外的撤销锁的消耗。
只有一个线程访问同步块场景。
轻量级锁
竞争的线程不会堵塞,提高了程序的响应速度。
如果始终得不到锁,竞争的线程使用自旋,会消耗CPU
追求响应时间,同步块指向性速度非常快。
重量级锁
线程竞争不会自旋,不消耗CPU。
线程堵塞,响应时间慢。
追求吞吐量,同步块执行时间较长。
锁消除
public void lockElimination(){ Object o = new Object(); synchronized (o) { System.out.println("Say Hello!"); } } public void lockEliminationExt(){ Object o = new Object(); System.out.println("Sqy Hello!"); }
锁粗化
public void lockCoarse(){ for (int i = 0; i < 10000; i++) { synchronized (SynchronizedDemo.class) { System.out.println("Say Hello!"); } } } public void lockCoarseExt(){ synchronized (SynchronizedDemo.class) { for (int i = 0; i < 10000; i++) { System.out.println("Say Hello!"); } } }
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算