Java并发(一)

来源:互联网 发布:双立人珐琅铸铁锅 知乎 编辑:程序博客网 时间:2024/06/08 01:32

概述:本文主要讲述Java并发中的常见问题,内容涵盖多线程的实现、线程安全、线程安全集合类、线程阀、线程池等内容。


第一部分 分布式计算、并行计算、并发计算

1.1 并行计算与分布式计算

  1. 级别上。并行计算借助于并行算法与并行编程语言,实现进程级并行和线程级并行;分布式计算将任务分成小块分配到各个计算机上执行,属于计算机之间的并行。
  2. 粒度上。并行计算中,处理器之间交互频繁,粒度细;分布式计算中,处理器之间交互不频繁,粒度粗。

1.2 并发计算与分布式计算

并发使用多线程技术,使有限资源达到最大利用价值;分布式在资源紧缺时增加服务器资源。

第二部分 Java中的线程

2.1 线程实现的三种方法

2.1.1 继承Thread类

  1. 实现方法:继承Thread类,重写run()方法。
  2. 优缺点:一个Java类只能继承一个父类。
  3. 代码示例
/* * 继承Thread类实现多线程 * **/public class Test {    public static void main(String[] args) {        MyThread t1 = new MyThread();        t1.start();    }}class MyThread extends Thread{    @Override    public void run() {        try {            Thread.sleep(10);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("继承Thread类实现多线程");    }}

2.1.2 实现Runnable接口

  1. 实现方法:子类实现Runnable接口,并重写run()方法。将Runnable对象作为参数构造Thread类对象。
  2. 代码示例
/** * 实现Runnable接口*/public class Test {    public static void main(String[] args) {        MyRunnable runnable = new MyRunnable();        Thread t = new Thread(runnable);        t.start();    }}class MyRunnable implements Runnable {    @Override    public void run() {        try {            Thread.sleep(300);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("实现Runnable接口");    }}

2.1.3 实现Callable接口

  1. 实现方法:

    1. 实现Callable接口,覆盖call()方法,其中call()方法有返回值;
    2. 利用Callable对象实例化FutureTask对象;
    3. 利用FutureTask对象构造Thread对象。

    FutureTask类有get()方法获取call()方法返回值,get()方法会阻塞线程,直到获取返回值。

  2. 代码示例

/** * 实现Callable接口*/public class Test {    public static void main(String[] args) {        MyCallable callable = new MyCallable();        FutureTask<String> fetureTask = new FutureTask<>(callable);        Thread t = new Thread(fetureTask);        t.start();        System.out.println("主线程start");        try {            System.out.println(fetureTask.get());        } catch (InterruptedException e) {            e.printStackTrace();        } catch (ExecutionException e) {            e.printStackTrace();        }        System.out.println("主线程end");    }}class MyCallable implements Callable<String> {    @Override    public String call() throws Exception {        Thread.sleep(3000);        System.out.println("实现Callable接口");        return "call()方法执行完毕";    }}输出:主线程start实现Callable接口call()方法执行完毕主线程end

2.2 线程中断机制

2.2.1 调用Thread.stop()方法

  该方法强制停止一个线程,并抛出ThreadDeath对象作为异常。该异常不应该被捕获,这样才能使线程真正终止。Stop()方法不安全,不建议使用。

2.2.2 调用Thread.interrupt()方法

  Java中断机制是一种协作机制,通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断。每个线程都有一个boolean类型标识,代表着是否有中断请求。当线程t1向中断线程t2时,只需将t2的中断标识设置为true,然后t2可以选择在合适的时候检测中断标志位并处理该中断请求,甚至可以不理会该请求。

Thread类提供了三个中断方法:

(1)boolean interrupted():测试当前线程是否已经中断。线程的中断状态由该方法清除,即调用一次该方法后将清除线程的中断转态。
(2)boolean isInterrupted():测试线程是否已经中断,线程的中断状态不受该方法的影响。
(3)void interrupt():中断线程,将线程额中断状态设置为true

代码示例

public class Test {    public static void main(String[] args) {        System.out.println("主线程开始");        MyThread t = new MyThread();        t.start();        try {            Thread.sleep(300);        } catch (InterruptedException e) {            e.printStackTrace();        }        t.interrupt();// 中断线程    }}class MyThread extends Thread {    @Override    public void run() {        while (true) {            if (Thread.currentThread().isInterrupted())// 处理中断请求                break;            else {                try {                    Thread.currentThread().sleep(100);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println("thread running");            }        }    }}

2.3 线程生命周期

  线程生命周期共有5个状态:new、runnable、running、blocked、dead。

  1. 新建(new):创建Thread类的一个实例,还没有调用start()方法,线程not alive。
  2. 就绪(runnable):通过调用start()方法来启动线程,还没有被分配cpu执行权,等待获取cpu执行权,线程alive。
  3. 运行(running):线程获取到了cpu执行权,正在执行任务,线程alive。
  4. 阻塞(blocked):running状态的线程让出了cpu的执行权,并暂停自己的执行,线程alive。
    1. 调用slee(long time)方法进入阻塞状态,指定时间过去后可进入就绪状态runnable;
    2. 调用wait()方法进入阻塞状态,可以调用notify()方法使线程回到runnable状态。
  5. 死亡(dead):线程执行完毕或被其他线程杀死,进入死亡状态,线程not alive。

  生命周期图如下所示,其中sleep(long time)不会释放锁,wait()方法会释放锁。

这里写图片描述

2.4 普通线程与守护线程

  守护线程必须在start()方法前,通过setDeamon()方法设置,随着进程的结束而结束。当进程中非守护线程都结束时,进程会直接结束,而不管守护线程是否结束。

2.5 当前线程副本TheadLocal

概述

  ThreadLocal为每个使用该变量的线程提供独立的变量副本,每一个线程都可以独立地改变自己的副本,不影响其他线程。

实现原理

(1)ThreadLocal的set()方法源码实现

public void set(T value) {    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null)        map.set(this, value);    else        createMap(t, value);}

  首先通过getMap()方法获取与当前线程相关的ThreadLocalMap对象,然后将变量值存储在ThreadLocalMap对象中,ThreadLocalMap的key为当前ThreadLocal对象,value为变量值。每个线程都有一个独立的ThreadLocalMap副本,只能被当前线程读写,从而实现了变量在不同线程中的隔离。

(2)ThreadLocal的get()方法源码实现

public T get() {    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null) {        ThreadLocalMap.Entry e = map.getEntry(this);        if (e != null) {            @SuppressWarnings("unchecked")            T result = (T)e.value;            return result;        }    }    return setInitialValue();}

  首先通过getMap()方法获取与当前线程相关的ThreadLocalMap对象,然后根据当前ThreadLocal对象在map中进行查找。

(3)ThreadLocal的setInitialValue()方法源码实现

private T setInitialValue() {    T value = initialValue();    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null)        map.set(this, value);    else        createMap(t, value);    return value;}

  首先调用initialValue()方法得到value,其中initialValue()是一个被覆盖的方法;根据当前Thread对象找到ThreadLocalMap对象,在map里设置value值。

  进一步,我们可以创建不同ThreadLocal对象来实现多个变量在不同线程之间的访问隔离。ThreadLocal对象在处理多线程的局部变量时比synchronized同步机制简单高效,更高的并发性。

注意事项:使用ThreadLocal对象时,一般都是将对象声明为static类型;如果不断地创建ThreadLocal而不调用其remove()方法,将导致内存泄漏。

2.6 线程异常的处理

关于Java异常的详细解析,见另一篇文章。

(1)概述
  在Java多线程中,Runnable.run()方法已经限定了所有线程都不能抛出checked exception,checked exception必须在线程内进行处理。当抛出unchecked exception时,线程会终止,且其它线程不受影响。Runnable.run()方法中的checked exception必须通过try-catch语句捕获;而unchecked exception也应该被处理,增强程序鲁棒性。

(2)unchecked exception的处理
  Thread类有setUncaughtExceptionHandler(UncaughtExceptionHandler)方法,用来处理unchecked exception。UncaughtExceptionHandler是一个接口,接口中的uncaughtException()方法需要被子类重写,实现异常处理逻辑。

public static abstract interface UncaughtExceptionHandler {    public abstract void uncaughtException(Thread paramThread, Throwable paramThrowable);}

(3)使用方法
  综上所述,处理unchecked exception的步骤为:

  1. 定义子类,实现UncaughtExceptionHandler接口,并对接口中的方法进行重写;
  2. 在start()方法启动线程之前,调用线程的setUncaughtExceptionHandler(UncaughtExceptionHandler)方法,实现处理逻辑的注册。
import java.lang.Thread.UncaughtExceptionHandler;public class Test {    public static void main(String[] args) {        Thread t = new MyThread("t1");        t.setUncaughtExceptionHandler(new MyHandler());        t.start();    }}// 线程类class MyThread extends Thread {    public MyThread(String name) {        super(name);    }    @Override    public void run() {        int num = Integer.parseInt("##");//捕获异常后,线程终止,不往下执行        System.out.println(“异常处理完毕”);    }}// 异常处理类class MyHandler implements UncaughtExceptionHandler {    @Override    public void uncaughtException(Thread t, Throwable e) {        System.out.println(t.getName());        System.out.println("------------");        System.out.println(e.getMessage());    }}输出:t1------------For input string: "##"

第三部分 线程安全

3.1 同步代码块synchronized(隐式锁)

(1)用法
  synchronized可以修饰方法,放在范围操作符和返回类型之间;也可以修饰代码块,修饰代码块时需要指定加锁对象

// 修饰方法public synchronized void method1() {    ;}// 修饰代码块public void method2() {    synchronized (this) {        ;    }}

(2)注意事项

  1. 修饰在方法体上的synchronized默认锁的对象就是当前对象本身,等同于synchronized(this)修饰的代码块;
  2. 相对显式锁,synchronized不需要显式加锁和解锁操作;
  3. 当一个线程访问object对象的synchronized代码块时,其他线程对该object对象的其他synchronized代码块的访问将被阻塞。

(3)效率分析
  性能和执行效率上,synchronized修饰方法劣于synchronized修饰代码块,因为synchronized修饰方法时会有线程排队,即使获得锁,在方法体内部获得资源也需要时间;同样修饰代码块时,锁对象越小性能越优,因为加锁和释放锁都需要此对象的资源,所以一般用字节数组作为锁对象。

// 修饰方法public synchronized void method1() {    ;}// 修饰代码块public void method2() {    synchronized (this) {        ;    }}// 更小的锁对象private byte[] lock = new byte[1];public void method3() {    synchronized (lock) {        ;    }}

3.2 java.util.concurrent.locks包

(1)接口摘要

这里写图片描述

(2)类摘要
这里写图片描述

(3)包的描述

  1. Lock接口:支持那些语义不同(重入、公平等)的锁规则,可以在非阻塞式结构的上下文(包括 hand-over-hand 和锁重排算法)中使用这些规则,主要的实现是 ReentrantLock;
  2. ReadWriteLock接口:以类似方式定义了一些读取者可以共享而写入者独占的锁。此包只提供了一个实现,即 ReentrantReadWriteLock;
  3. Condition接口:描述了可能会与锁有关联的条件变量;
  4. AbstractQueuedSynchronizer类:可用来定义锁以及依赖于排队阻塞线程的其他同步器。

(4)继承结构

这里写图片描述

3.3 显式锁Lock接口

3.2.1 接口定义

public interface Lock {}

3.2.2 接口概述

  1. synchronized强制所有锁获取和释放均要出现在一个块结构中,当获取了多个锁时,它们必须以相反的顺序释放;
  2. Lock 接口的实现允许锁在不同的作用范围内获取和释放,并允许以任何顺序获取和释放多个锁:获取节点 A 的锁,然后再获取节点 B 的锁,然后释放 A 并获取 C,然后释放 B 并获取 D,依此类推;
  3. 锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。

    Lock l = …;
      l.lock();
    try {
      // access the resource protected by this lock
    } finally {
      l.unlock();
    }

  4. Lock实现提供了使用 synchronized 方法和语句所没有的其他功能,包括tryLock()lockInterruptibly()tryLock(long, TimeUnit)

3.2.3 方法概述

(1)void lock()

获取锁。
如果锁不可用,该线程将一直处于休眠状态,直到获得锁。

(2)void lockInterruptibly() throws InterruptedException

(1)如果当前线程未被中断,则尝试获取锁。
如果锁可用,则获取锁;如果锁不可用,线程将一直处于休眠状态,直到获取锁,或其他某个线程中断当前线程。
(2)如果在进入此方法时已经设置了该线程的中断状态,或者
在获取锁时被中断,则将抛出 InterruptedException,并清除当前线程的已中断状态。

(3)boolean tryLock()

仅在调用时锁为空闲状态才获取该锁。
如果锁可用,则获取锁;如果锁不可用,则立即返回值 false。

Lock lock = ...;if (lock.tryLock()) {    try {        // manipulate protected state    } finally {        lock.unlock();    }} else {    // perform alternative actions}此用法可确保如果获取了锁,则会释放锁,如果未获取锁,则不会试图将其释放。

(4)boolean tryLock(long time, TimeUnit unit) throws InterruptedException

(1)如果超时前获得了锁,则返回 true;如果超时等待未获得锁,则返回 false;如果 time 小于等于 0,该方法将完全不等待。
(2)如果锁可用,则此方法将立即返回值 true;如果锁不可用,该线程将一直处于休眠状态,直到当前线程获得锁,或其他某个线程中断当前线程,或超过指定的等待时间
(3)如果在进入此方法时已经设置了该线程的中断状态,或在获取锁时被中断,则将抛出 InterruptedException,并会清除当前线程的已中断状态。

(5)void unlock()

释放锁。

(6)Condition newCondition()

返回绑定到此Lock实例的新 Condition 实例。

3.4 ReentrantLock类

3.4.1 类的定义

public class ReentrantLock extends Object implements Lock, Serializable {}

3.4.2 类的概述

  1. ReentrantLock 将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁并返回。如果当前线程已经拥有该锁,此方法将立即返回。
  2. 此类的构造方法接受一个可选的公平参数。当设置为 true 时,在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程;否则此锁将无法保证任何特定访问顺序。

    公平锁不能保证线程调度的公平性。

  3. 要注意的是,未定时的 tryLock() 方法并没有使用公平设置。因为即使其他线程正在等待,只要该锁是可用的,此方法就可以获得成功。

  4. 该类的序列化与内置锁的行为方式相同:一个反序列化的锁处于解除锁定状态,不管它被序列化时的状态是怎样的。

3.4.3 方法概述

构造方法
(1)public ReentrantLock(boolean fair)

创建一个具有给定公平策略的 ReentrantLock。

(2)public ReentrantLock()

创建一个ReentrantLock的实例,等同于使用 ReentrantLock(false)。

从Lock接口继承的方法
(3)public void lock()

获取锁。
(1)如果该锁没有被另一个线程保持,则获取该锁并立即返回,将锁的保持计数设置为 1;如果当前线程已经保持该锁,则将保持计数加 1。
(2)如果该锁被另一个线程保持,该线程将一直处于休眠状态,直到获取锁。

(4)public void lockInterruptibly() throws InterruptedException

如果当前线程未被中断,则尝试获取锁。
(1)如果该锁没有被另一个线程保持,则获取该锁并立即返回,将锁的保持计数设置为 1;如果当前线程已经保持此锁,则将保持计数加 1,并且该方法立即返回;
(2)如果锁被另一个线程保持,该线程将一直处于休眠状态,直到锁获取锁,或其他某个线程中断当前线程;
(3)如果在进入此方法时已经设置了该线程的中断状态,或在等待获取锁的同时被中断,则抛出 InterruptedException,并且清除当前线程的已中断状态。

(5)public boolean tryLock()

如果锁是自由的并且被当前线程获取,或者当前线程已经保持该锁,则返回 true;否则返回 false。
(1)如果该锁没有被另一个线程保持,并且立即返回 true 值,则将锁的保持计数设置为 1;如果当前线程已经保持此锁,则将保持计数加 1,该方法将返回 true;如果锁被另一个线程保持,则此方法将立即返回 false 值。

(6)public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException

如果锁是自由的并且由当前线程获取,或者当前线程已经保持该锁,则返回 true;如果在获取该锁之前已经到达等待时间,则返回 false;
(1)如果当前线程获取该锁,则将锁的保持计数设置为 1;如果当前线程已经保持此锁,则将保持计数加 1;如果锁被另一个线程保持,该线程将一直处于休眠状态,直到获得锁,或其他某个线程中断当前线程,或超过指定的等待时间
(2)如果在进入此方法时已经设置了该线程的中断状态,或在等待获取锁的同时被中断,则抛出 InterruptedException,并且清除当前线程的已中断状态。

(7)public void unlock()

试图释放此锁。
如果当前线程是此锁所有者,则将保持计数减 1;如果保持计数现在为 0,则释放该锁;如果当前线程不是此锁的持有者,则抛出IllegalMonitorStateException

(8)public Condition newCondition()

返回用来与此 Lock 实例一起使用的 Condition 实例。

新增的方法
(9)public int getHoldCount()

返回当前线程保持此锁的次数,如果此锁未被当前线程保持,则返回 0

(10)public boolean isHeldByCurrentThread()

查询当前线程是否保持此锁。

(11)public boolean isLocked()

查询此锁是否由任意线程保持,如果任意线程保持此锁,则返回 true;否则返回 false。

(12)public final boolean isFair()

如果此锁的公平设置为 true,则返回 true。

(13)protected Thread getOwner()

返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null

3.5 ReentrantReadWriteLock.ReadLock类

3.5.1 类的定义

public static class ReentrantReadWriteLock.ReadLock extends Object implements Lock, Serializable

3.5.2 方法概述

从Lock接口继承的方法
(1)public void lock()

获取读取锁。
如果另一个线程没有保持写入锁,则获取读取锁并立即返回;如果另一个线程保持该写入锁,在获取读取锁之前,该线程将一直处于休眠状态。

(2)public void lockInterruptibly() throws InterruptedException

获取读取锁,除非当前线程被中断。
(1)如果另一个线程没有保持写入锁,则获取读取锁并立即返回;如果另一个线程保持了该写入锁,该线程将一直处于休眠状态,直到读取锁由当前线程获得,或其他某个线程中断当前线程。
(2)如果在进入此方法时已经设置了该线程的中断状态,或在获取读取锁时被中断,则抛出 InterruptedException,并且清除当前线程的已中断状态。

(3)public boolean tryLock()

如果另一个线程没有保持写入锁,则获取读取锁并立即返回 true 值。

(4)public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException

如果另一个线程在给定的等待时间内没有保持写入锁,并且当前线程未被中断,则获取读取锁并立即返回 true 值。
如果写入锁被另一个线程保持,该线程将一直处于休眠状态,直到读取锁由当前线程获得,或其他某个线程中断当前线程,或已超过指定的等待时间。

(5)public void unlock()

试图释放此锁

(6)public Condition newCondition()

因为 ReadLocks 不支持条件,所以将抛出 UnsupportedOperationException

3.6 ReentrantReadWriteLock.WriteLock类

3.6.1 类的定义

public static class ReentrantReadWriteLock.WriteLock extends Object implements Lock, Serializable

3.6.2 方法概述

从Lock接口继承的方法
(1)public void lock()

获取写入锁。
如果另一个线程既没有保持读取锁也没有保持写入锁,则获取写入锁并立即返回,并将写入锁保持计数设置为 1;如果当前线程已经保持写入锁,则保持计数增加 1,该方法立即返回;如果该锁被另一个线程保持,该线程将一直处于休眠状态,直到获取写入锁。

(2)public void lockInterruptibly() throws InterruptedException

获取写入锁,除非当前线程被中断。
如果另一个线程既没有保持读取锁也没有保持写入锁,则获取写入锁并立即返回,并将写入锁保持计数设置为 1;如果当前线程已经保持此锁,则将保持计数加 1,立即返回;如果锁被另一个线程保持,该线程将一直处于休眠状态,直到写入锁由当前线程获得,或其他某个线程中断当前线程。

(3)public boolean tryLock()

如果另一个线程既没有保持读取锁也没有保持写入锁,则获取写入锁并立即返回 true 值,并将写入锁保持计数设置为 1;如果当前线程已经保持此锁,则将保持计数加 1,该方法将返回 true;如果锁被另一个线程保持,则此方法将立即返回 false 值。

(4)public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException

如果另一个线程既没有保持读取锁也没有保持写入锁,则获取写入锁并立即返回 true 值,并将写入锁保持计数设置为 1;如果当前线程已经保持此锁,则将保持计数加 1,该方法将返回 true;如果锁被另一个线程保持,该线程将一直处于休眠状态,直到写入锁由当前线程获得,或者其他某个线程中断当前线程,或者已超过指定的等待时间

(5)public void unlock()

试图释放此锁。
如果当前线程保持此锁,则将保持计数减 1;如果保持计数现在为 0,则释放该锁;如果当前线程不是此锁的持有者,则抛出 IllegalMonitorStateException。

(6)public Condition newCondition()

返回一个用来与此 Lock 实例一起使用的 Condition 实例

新增方法
(7)public boolean isHeldByCurrentThread()

查询此写入锁是否由当前线程保持

(8)public int getHoldCount()

查询当前线程保持写入锁的数量。

3.7 ReadWriteLock接口

3.7.1 接口定义

public interface ReadWriteLock

3.7.2 接口概述

  1. ReadWriteLock维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。
  2. 与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程)
  3. 如果读取操作所用时间太短,则读-写锁实现(它本身就比互斥锁复杂)的开销将成为主要的执行成本。
  4. 在 writer 释放写入锁时,reader和writer都处于等待状态,在这时要确定是授予读取锁还是授予写入锁。Writer优先比较普遍,因为预期写入所需的时间较短并且不那么频繁;Reader优先不太普遍,因为如果 reader 正如预期的那样频繁和持久,那么它将导致对于写入操作来说较长的时延。Reader 优先会无限期地延迟 writer,而 writer 优先会减少可能的并发。

3.7.3 方法概述

(1)Lock readLock()

返回用于读取操作的锁。
(2)Lock writeLock()
返回用于写入操作的锁。

3.8 ReentrantReadWriteLock类

3.8.1 类的定义

public class ReentrantReadWriteLock extends Object implements ReadWriteLock, Serializable

3.8.2 类的概述

此类具有以下属性:
(1)非公平模式(默认)。当非公平地(默认)构造时,未指定进入读写锁的顺序,连续竞争的非公平锁可能无限期地推迟一个或多个 reader 或 writer 线程,但吞吐量通常要高于公平锁。
(2)公平模式。当释放当前保持的锁时,可以为等待时间最长的单个 writer 线程分配写入锁,如果有一组等待时间大于所有正在等待的 writer 线程 的 reader 线程,将为该组分配写入锁。如果保持写入锁,或者有一个等待的 writer 线程,则试图获得公平读取锁(非重入地)的线程将会阻塞。直到当前最旧的等待 writer 线程已获得并释放了写入锁之后,该线程才会获得读取锁。
(3)重入。此锁允许 reader 和 writer 按照 ReentrantLock 的样式重新获取读取锁或写入锁。在写入线程保持的所有写入锁都已经释放后,才允许重入 reader 使用它们。
(4)锁降级。重入还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不可能的。
(5)不支持锁升级。读取锁不能直接升级为写入锁,因为获取一个写入锁需要释放所有读取锁。如果有两个读取锁试图获取写入锁且都不释放读取锁,就会发生死锁。
(6)锁获取的中断。读取锁和写入锁都支持锁获取期间的中断。
(7)Condition 支持。此 Condition 只能用于写入锁,读取锁不支持 Condition。

实现注意事项:此锁最多支持 65535 个递归写入锁和 65535 个读取锁。试图超出这些限制将导致锁方法抛出 Error。

3.8.3 方法概述

构造方法
(1)public ReentrantReadWriteLock(boolean fair)

使用给定的公平策略创建一个新的 ReentrantReadWriteLock。

(2)public ReentrantReadWriteLock()

使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock

从ReadWriteLock接口继承的方法
(3)public ReentrantReadWriteLock.WriteLock writeLock()

返回用于写入操作的锁。

(4)public ReentrantReadWriteLock.ReadLock readLock()

返回用于读取操作的锁。

新增加的方法
(5)public final boolean isFair()

如果此锁将公平性设置为 ture,则返回 true

(6)public int getReadLockCount()

查询为此锁保持的读取锁数量

(7)public boolean isWriteLocked()

查询是否某个线程保持了写入锁

(8)public boolean isWriteLockedByCurrentThread()

查询当前线程是否保持了写入锁

(9)public int getWriteHoldCount()

当前线程保持的写入锁数量

(10)public int getReadHoldCount()

当前线程保持的读取锁数量

3.9 Condition接口

3.9.1 接口定义

public interface Condition

3.9.2 接口概述

(1)Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
(2)Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。

3.9.3 方法说明

(1)void await() throws InterruptedException

造成当前线程在接到信号或被中断之前一直处于等待状态。在发生以下情况之一 以前,当前线程将一直处于休眠状态:

1)其他某个线程调用此 Condition 的 signal() 方法,并且碰巧将当前线程选为被唤醒的线程;
2)其他某个线程调用此 Condition 的 signalAll() 方法;
3)其他某个线程中断当前线程,且支持中断线程的挂起

在所有情况下,在此方法可以返回当前线程之前,都必须重新获取与此条件有关的锁

(2)boolean await(long time, TimeUnit unit) throws InterruptedException

造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。

(3)void signal()

唤醒一个等待线程。如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁。

(4)void signalAll()
唤醒所有等待线程。如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。

第四部分 线程安全集合类

4.1 HashTable

4.1.1 类的定义

public class Hashtable< K, V> extends Dictionary< K,V>implements Map< K,V>, Cloneable, Serializable

4.1.2 类的概述

  1. 此类实现一个哈希表,任何非null对象都可以用作键或值。
  2. 为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode 方法和 equals 方法
  3. Hashtable 的实例有两个参数影响其性能:初始容量加载因子。通常,默认加载因子(.75)在时间和空间成本上寻求一种折衷。初始容量主要控制空间消耗与执行 rehash 操作所需要的时间损耗之间的平衡。
  4. HashTable是线程安全的,每个方法都用synchronized来修饰,不会出现两个线程同时对数据进行操作的情况。

4.1.3 方法概述

省略

4.2 Vector类

4.2.1 类的定义

public class Vector extends AbstractList implements List, RandomAccess, Cloneable, Serializable

4.2.2 类的概述

  1. Vector类是矢量队列,通过数组保存数据。Vector 的大小可以根据需要增大或缩小,应用程序可以在插入大量组件前增加向量的容量,这样就减少了增加的重分配的量。
  2. Vector继承了AbstractList,实现了List,所以它是一个队列,支持添加、删除等操作;
  3. Vector实现了RandomAccess接口,支持随机访问
  4. Vector类是线程安全的,每个方法都用synchronized修饰

4.2.3 方法概述

省略

4.3 StringBuilder和StringBuffer

  1. StringBuffer是线程安全的,每个方法都有synchronized修饰;
  2. StringBuilder不是线程安全的,执行效率高。

4.4 ConcurrentHashMap类

4.4.1 类的定义

public class ConcurrentHashMap< K,V>extends AbstractMap< K,V>implements ConcurrentMap< K,V>, Serializable

4.4.2 类的概述

  1. HashMap的线程安全版,使用锁分离技术(即代码块锁),允许多个修改操作并发进行;
  2. 使用多个锁来控制对hash表的不同部分进行修改。内部使用段(Segment)来表示这些不同的部分,每个段都是一个小的hash table,有自己的锁;
  3. 不同段上的操作可以并发进行。

4.5 CopyOnWrite机制介绍

4.5.1 概述

  1. CopyOnWrite容器即写时复制的容器。当往容器添加元素时,先将当前容器进行Copy,然后往新容器里添加元素,最后将原容器的引用指向新容器。
  2. 是一种读写分离的思想,读和写不同的容器:可以对CopyOnWrite容器进行并发地读,而不需要加锁;写的时候需要对新容器加锁,否则多线程写的时候会复制多个副本;
  3. 适用于读多写少的并发场景

4.5.2 示例说明

用CopyOnWrite思想实现一个CopyOnWriteMap

import java.util.HashMap;import java.util.Map;public class CopyOnWriteMap<K, V> {    private volatile Map<K, V> internalMap;    public CopyOnWriteMap() {        internalMap = new HashMap<K, V>();    }    // 写操作需要同步    public V put(K key, V value) {        synchronized (this) {            Map<K, V> newMap = new HashMap<>(internalMap);            V v = newMap.put(key, value);            internalMap = newMap;            return v;        }    }    // 读操作不需要同步    public V get(K key) {        return internalMap.get(key);    }    public void putAll(Map<K, V> data){}    // 其他方法省略}

4.5.3 注意事项

  1. 使用批量添加。每次添加,容器都会进行复制,所以需要减少添加次数。
  2. 内存占用问题。每次写操作时,新对象和旧对象同时存在;如果这些对象过大,可能造成频繁地Minor GC和Full GC,所以需要通过压缩元素的方法来减少内存消耗;
  3. 数据一致性问题。CopyOnWrite机制只能保证数据的最终一致性,不能保证数据的实时一致性。

4.6 CopyOnWriteArrayList类、CopyOnWriteArraySet类

  Java中Set与List的区别同样适用于CopyOnWriteArrayList和CopyOnWriteArraySet,不同的是后两者采用线程安全机制。

1 0
原创粉丝点击