JUC 常用类、锁

JUC 常用类、锁,第1张

JUC 常用类、锁

目录

一、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 包中提供了多种并发容器类来改进同步容器的性能.

1、ConcurrentHashMap

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) {
       // Hashtable map=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();
        }
    }
}

2、CopyonWriteArrayList

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 自旋实现原子 *** 作的更新。

2. 悲观锁

认为并发 *** 作会出现问题,需要通过加锁保证安全,适合写 *** 作多。

3. 可重入锁

当一个同步方法中,调用另一个和他使用同一把锁的方法时,在外层方法中,即使锁没有释放的情况下,也可以进入另一个同步方法。

ReentrantLock 和 Synchronized 都是可重入锁

上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB 不会被当前线程执行,造成死锁。 4.读写锁( ReadWriteLock )

读和写是两把锁,进行分离使用。

特点

    多个读者可以同时进行读写者必须互斥(只允许一个写者写,也不能读者写者同时进行)写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)
5.分段锁

不是一种锁实现,是一种加锁的思想,采用分段加锁降低锁的粒度,从而提高效率。

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

是可重入锁,非公平锁,是关键字,可以修饰代码块,也可以修饰方法。是隐式的,自动获取,释放锁,Synchronized实现加锁,释放锁,是指令级别的,有一个进入监视器 +1,对象头锁标记被使用,执行任务,退出监视器 -1 ,0 对象头锁标记被改为无锁。Java 中 synchronized 通过在对象头设置标记,达到了获取锁和释放锁的目的。

同步方法使用 ACC_SYNCHRonIZED 标记是否为同步方法.当方法调用时,调用指令将会检查方法的 ACC_SYNCHRonIZED 访问标志是否被设置,如果设置了,该标记表明线程进入该方法时,需要 monitorenter,退出该方法时需要 monitorexit。

使用 javap -verbose SynchronizedDemo 反编译后得到:

当前线程拥有了这个对象的锁,把锁的计数器+1;当执行 monitorexit 指令时将模计数器-1;当计数器为 0 时,锁就被释放了.

五、ReentrantLock

ReentrantLock 是一个类,只能修饰代码块,是显示的,手动添加,手动释放,是类层面是实现控制的,采用CAS+AQS 是可重入锁,可以是公平锁,也可以是不公平锁。在类的内部自己维护了一个锁的状态,一旦有线程抢占到了,将状态改为1,其他线程进入到队列中等待锁的释放,锁一旦释放,那么就唤醒头结点,开始尝试去获得锁。

eg:
lock();

假设当前有三个线程去竞争锁,假设线程 A 的 CAS *** 作成功了,获得了锁,将锁状态 state 改为 1,那么线程 B 和 C 则设置状态失败。

由于线程 A 已经占用了锁,所以 B 和 C 失败,并且入等待队列。如果线程 A 拿着锁死死不放,那么 B 和 C 就会被挂起。
B 和 C 相继入队尝试获取锁。
若当前线程的节点的前驱节点是 head,就有资格尝试获取锁。

unlock();
尝试释放锁,若释放成功,那么查看头结点的状态,如果是则唤醒头结点的下个节点关联的线程。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5707259.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存