一、JUC 常用类
1、ConcurrentHashMap2、CopyOnWriteArrayList3、CopyOnWriteArraySet4、辅助类 CountDownLatch5、辅助类 CyclicBarrier 二、Java中的锁分类
1. 乐观锁2. 悲观锁3. 可重入锁4.读写锁( ReadWriteLock )5.分段锁6.自旋锁( SpinLock )7.共享锁8.独占锁9.AQS(AbstractQueuedSynchronizer)10.公平锁(Fair Lock)11.非公平锁(Nonfair Lock)12.偏向锁/轻量级锁/重量级锁
偏向锁轻量级锁重量级锁 三、Java对象头四、Synchronized五、ReentrantLock
一、JUC 常用类Java 5.0 在 java.utilconcurrent 包中提供了多种并发容器类来改进同步容器的性能.
ConcurrentHashMap 采用锁分段机制,并没有将整个hash表锁住,jdk8之后没有使用分段锁(给每个位置创建一个锁标志对象)。采用CAS思想 + synchronized来实现插入元素时,检测hash表对应位置是否是第一个节点,如果采用CAS机制(循环检查)向第一个位置插入数据。如果此位置已经有值,那么就以第一个Node对象为锁标志进行加锁,使用的是synchronized实现。
package com.ffyc.database.juc; import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class HashMapDemo { public static void main(String[] args) { // Hashtable2、CopyonWriteArrayListmap=new Hashtable<>(); ConcurrentHashMap map=new ConcurrentHashMap<>(); //模拟多个线程对其 *** 作 for (int i = 0; i < 20; i++) { new Thread(()->{ map.put(Thread.currentThread().getName(),new Random().nextInt()); System.out.println(map); }).start(); } } }
ArraayList 是线程不安全的,在高并发情况下可能会出现问题, Vector 是线程安全的.CopyonWriteArrayList 将读取的性能发挥到极致,取是完全不用加锁的,写入也不会阻塞读取 *** 作,只有写入和写入之间需要进行同步等待,读 *** 作的性能得到大幅度提升。
package com.ffyc.database.juc; import java.util.ArrayList; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; public class ArrayListDemo { public static void main(String[] args) { // ArrayList list=new ArrayList<>(); CopyOnWriteArrayList list =new CopyOnWriteArrayList(); //模拟多个线程对其 *** 作 for (int i = 0; i < 20; i++) { new Thread(()->{ list.add(new Random().nextInt()); System.out.println(list); }).start(); } } }3、CopyonWriteArraySet
package com.ffyc.database.juc; import java.util.Random; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; public class ArraySetDemo { public static void main(String[] args) { CopyOnWriteArraySet list =new CopyOnWriteArraySet(); //模拟多个线程对其 *** 作 for (int i = 0; i < 20; i++) { new Thread(()->{ list.add(new Random().nextInt()); System.out.println(list); }).start(); } } }4、辅助类 CountDownLatch
CountDownLatch 这个类使一个线程等待其他线程各自执行完毕后再执行。是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为 0 时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了
package com.ffyc.database.juc; import java.util.concurrent.CountDownLatch; public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch =new CountDownLatch(4);//计数 for (int i = 0; i < 4; i++) { new Thread(()->{ System.out.println(Thread.currentThread().getName()); countDownLatch.countDown(); }).start(); } countDownLatch.await();//关闭计数 System.out.println("main线程执行"); } }5、辅助类 CyclicBarrier
package com.ffyc.database.juc; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierDemo { public static void main(String[] args) { CyclicBarrier c =new CyclicBarrier(5,()->{ System.out.println("大家都到期了"); }); for (int i = 0; i < 7; i++) { new Thread(()->{ System.out.println(Thread.currentThread().getName()); try { c.await(); //加1 } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } }二、Java中的锁分类
有许多锁的名词 :有的指锁的特性,有的指锁的设计,有的指锁的状态 1. 乐观锁
乐观锁就是不加锁,认为并发的修改就是没有问题的在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发 *** 作是没有事情的。适合读 ***
作非常多的场景,不加锁会带来大量的性能提升。
例如 CAS机制,是一种无锁的方式实现。典型的例子就是原子类,通过 CAS 自旋实现原子 *** 作的更新。
认为并发 *** 作会出现问题,需要通过加锁保证安全,适合写 *** 作多。
3. 可重入锁当一个同步方法中,调用另一个和他使用同一把锁的方法时,在外层方法中,即使锁没有释放的情况下,也可以进入另一个同步方法。
ReentrantLock 和 Synchronized 都是可重入锁
上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB 不会被当前线程执行,造成死锁。
4.读写锁( ReadWriteLock )
读和写是两把锁,进行分离使用。
特点
- 多个读者可以同时进行读写者必须互斥(只允许一个写者写,也不能读者写者同时进行)写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)
不是一种锁实现,是一种加锁的思想,采用分段加锁降低锁的粒度,从而提高效率。
6.自旋锁( SpinLock )不是一种锁实现,采用自旋(循环重试)的方式进行尝试获取执行权,不会让线程进入到阻塞的状态,适用于锁的时间较短的情况。
7.共享锁可以被多个线程共享的锁,ReadWriteLock的读锁是共享的,多个线程可以同时读数据
8.独占锁一次只能有一个线程获取锁ReentrantLock Synchronized ReadWriteLock 写锁都是独占锁,ReadWriteLock里面实现方式,其读锁是共享锁,其写锁是独享锁。使用一个同步队列,例如读线程获取资源,将标准state设置为已经被使用,然后将其他写线程加入到一个队列中等待。 9.AQS(AbstractQueuedSynchronizer)
类如其名,抽象的队列式的同步器,这个类在 java.util.concurrent.locks 包,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的 ReentrantLock…
维护一个队列,让等待线程排队
10.公平锁(Fair Lock)
就是会维护一个线程的等待队列,依次去执行线程。ReentrantLock 默认是非公平的,可以在创建时通过构造方法为其指定是公平锁还是非公平锁。
11.非公平锁(Nonfair Lock)
没有队列,一旦锁释放,线程开始抢占,谁抢到执行权,谁先执行。Synchronized 就是非公平的。 12.偏向锁/轻量级锁/重量级锁
锁的状态:
- 无锁状态偏向锁状态轻量级锁状态重量级锁状态
锁的状态是通过对象监视器在对象头中的字段来表明的。四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级。这四种状态都不是 Java 语言中的锁,而是 Jvm 为了提高锁的获取与释放效率而做的优化(使用 synchronized 时)。 偏向锁
指的是一直只有一个线程在不断的获取锁,可以更方便的获取到锁
轻量级锁当是偏向锁时,再有一个线程来进行访问,那么锁状态升级为轻量级锁,如果是轻量级锁,那么等待的线程不会进入阻塞状态,采用自旋方式重新尝试获得锁,效率提高。
重量级锁当锁状态为轻量级锁时,如果有的线程自旋次数过多,或者有大量的线程访问,那么锁状态升级为重量级锁,那么未获得锁的线程不再自旋,进入到阻塞状态。
三、Java对象头在 Hotspot 虚拟机中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充;Java 对象头是实现 synchronized 的锁对象的基础,一般而言,synchronized 使用的锁对象是存储在 Java 对象头里。它是轻量级锁和偏向锁的关键。
Mawrk Word:
Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。Java对象头一般占有两个机器码(在 32 位虚拟机中,1 个机器码等于 4 字节,也就是 32bit),下面就是对象头的一些信息:
是可重入锁,非公平锁,是关键字,可以修饰代码块,也可以修饰方法。是隐式的,自动获取,释放锁,Synchronized实现加锁,释放锁,是指令级别的,有一个进入监视器 +1,对象头锁标记被使用,执行任务,退出监视器 -1 ,0 对象头锁标记被改为无锁。Java 中 synchronized 通过在对象头设置标记,达到了获取锁和释放锁的目的。
同步方法使用 ACC_SYNCHRonIZED 标记是否为同步方法.当方法调用时,调用指令将会检查方法的 ACC_SYNCHRonIZED 访问标志是否被设置,如果设置了,该标记表明线程进入该方法时,需要 monitorenter,退出该方法时需要 monitorexit。
使用 javap -verbose SynchronizedDemo 反编译后得到:
当前线程拥有了这个对象的锁,把锁的计数器+1;当执行 monitorexit 指令时将模计数器-1;当计数器为 0 时,锁就被释放了.
ReentrantLock 是一个类,只能修饰代码块,是显示的,手动添加,手动释放,是类层面是实现控制的,采用CAS+AQS 是可重入锁,可以是公平锁,也可以是不公平锁。在类的内部自己维护了一个锁的状态,一旦有线程抢占到了,将状态改为1,其他线程进入到队列中等待锁的释放,锁一旦释放,那么就唤醒头结点,开始尝试去获得锁。
eg:
lock();
假设当前有三个线程去竞争锁,假设线程 A 的 CAS *** 作成功了,获得了锁,将锁状态 state 改为 1,那么线程 B 和 C 则设置状态失败。
由于线程 A 已经占用了锁,所以 B 和 C 失败,并且入等待队列。如果线程 A 拿着锁死死不放,那么 B 和 C 就会被挂起。
B 和 C 相继入队尝试获取锁。
若当前线程的节点的前驱节点是 head,就有资格尝试获取锁。
unlock();
尝试释放锁,若释放成功,那么查看头结点的状态,如果是则唤醒头结点的下个节点关联的线程。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)