java并发学习笔记

1 线程基础、线程之间的共享与协作

1.1 cpu时间片轮询机制

主要用于分时系统的进程调度;系统将所有进程按照先入先出的方式排成一个队列,并分配给队首一个时间片(一个时间片在10ms到100ms数量级);若进程需要小于一个时间片,那么会在进程结束后自动释放cpu,若大于一个时间片,则系统的计时器会发出时钟中断,使进程终止,并重新加入队列,然后再把cpu分配给就绪的队首进程,如此往复。

1.2 进程与线程

  • 进程,程序运行分配的最小单位,进程内部可以存在多个线程,多个线程共享进程内资源
  • 线程,cpu调度的最小单位

1.3 并行与并发

  • 并行(parallel),同一时刻有多条指令在处理器上同时执行,多处理器可以实现
  • 并发(concurrence),在同一时间只有一条指令在处理器上执行;多个进程可以被快速的轮换运行,使得在宏观上有“同时”的感觉

1.4 启动线程的三种方式

  1. 继承Thread并重写run()方法
  2. 实现Runnable接口
  3. 利用CallableFutureTask,本质上和实现Runnable接口相同
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    public class StartThread {
    
        private static class UseCallable implements Callable<String> {
            @Override
            public String call() {
                System.out.println("call()...");
                return "callable";
            }	
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            // FutureTask继承自Runnable接口,并重写了run()方法,
            // 在重写的run()方法中,调用了Callable.call()作为返回值
            FutureTask<String> futureTask = new FutureTask<>(new UseCallable());
            new Thread(futureTask).start();
            System.out.println(futureTask.get());
        }
    
    }
    

1.5 停止线程

注意,下述三个方法都是对标志位的操作,具体逻辑需要开发者根据标志位自行处理

  • interrupt(),会将线程的中断状态标志位修改为true,表示是中断状态
  • isInterrupt(),返回线程的中断状态标志位,对标志位无影响
  • interrupted(),返回线程的中断状态标志位,但会对标志位进行复位操作,即修改标志位为false
public class StopThread {

    private static class DemoT extends Thread {
        @Override
        public void run() {
            while (!isInterrupted()) {
                try {
                    sleep(3000);
                    interrupt(); // flag -> true
                    System.out.println("after interrupt(), flag is " + interrupted()); // flag -> false
                    System.out.println("after interrupted(), flag is " + isInterrupted()); // flag -> false
                    interrupt(); // flag -> true
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("out of while loop, flag : " + isInterrupted());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new DemoT();
        t.start();
    }

}

1.6 线程的生命周期

线程生命周期分为:新建、就绪(可运行状态), 运行、阻塞和死亡
在这里插入图片描述

1.7 守护线程

注意,守护线程的finally代码块不能保证一定执行,因为当主线程执行完毕后,守护线程会立即结束,不会处理后续工作

public class DaemonThreadDemo {

    private static class DaemonThread extends Thread {

        @Override
        public void run() {
            try {
                sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("finally ing ...");
            }
        }

    }

    public static void main(String[] args) throws InterruptedException {
        DaemonThread daemonThread = new DaemonThread();
        daemonThread.setDaemon(true);
        daemonThread.start();
        Thread.sleep(2000);
    }

}

1.8 synchronized关键字

该关键字解决多个线程访问共享资源时的同步性问题,被该关键字修饰的方法或代码块,可以被保证任意时刻都只有一个线程在执行。

详细解释:synchronized关键字学习

主要用三种用法:

  1. 修饰实例方法,作用于当前类的对象,进入同步代码块前需要获得当前对象实例的锁
  2. 修饰静态方法,作用于当前类对象,进入同步代码块前需要获得当前类对象的锁,该锁作用于类的所有对象实例
  3. 修饰代码块,包括静态代码块和非静态代码块,作用域与上述两类对应

底层原理:

  1. 修饰代码块时,通过monitorentermonitorexit指令来标识代码块的开始和结束位置;当执行monitorenter指令时,线程会试图获取锁,也就是monitor对象,获取成功后,对象头的mark word区域会存储指向monitor对象的指针,这也解释了为何java中任何对象都可以作为锁;在获取锁后,计数器从原本的0变为1,而在执行monitorexit指令后,计数器又恢复为0,表示锁被释放。
  2. 修饰方法时,jvm通过ACC_SYNCHRONIZED标识来表明方法是同步的,从而执行相应的同步调用。

1.9 volatile关键字

该关键字只能修饰变量,通过保证代码的有序性可见性从而尽量保证线程安全;但是该关键字不能保证原子性

可见性:某个共享变量被一个线程修改后,其它使用该共享变量的线程会立刻知晓该变量被修改,在使用这个共享变量时,不是从自己的工作内存中读取,而是去主内存中读取

有序性:由于虚拟机在运行期会对代码进行优化,代码执行的顺序有可能被修改;有序性保证在优化代码时,指令的位置不变

不保证原子性示例:

public class VolatileDemo {

    private static volatile int count = 0;

    public static void main(String[] args) throws InterruptedException {
        // 创建10个线程,每个线程执行的任务是1000次对静态变量t加1操作
        // 期望最终得到count值为10*1000,但会出现很多小于期望值的结果
        for (int i = 0; i < 10; i++)
            new Thread(() -> {
                for (int j = 0; j < 1000; j++)
                    count++;
            }).start();
        Thread.sleep(5000);
        System.out.println(count);
    }

}

volatile实现原理:每个线程都有独立的工作内存,而包括共享变量在内的所有变量都存在于主内存中。在对该关键字修饰的变量进行写操作时,会使用到cpu提供的Lock前缀指令,该指令具有如下作用:

  1. 将当前处理器缓存行的数据刷新到主内存中
  2. 1中的操作会使其它缓存了该内存地址的缓存行的相应数据失效

1.10 ThreadLocal

ThreadLocal用于线程之间的数据隔离

Thread类中有属性threadLocals,如下:

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

由注释可知ThreadLocal中的值是从属于当前Thread的,而所谓的值是由ThreadLocal中的内部类ThreadLocalMap来维护的;在ThreadLocalMap中通过Entry来维护上述的值,其声明如下:

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

由此可知,绑定在Thread中的threadLocals属性中维护的值,是以键值对形式存储的,键值为ThreadLocal类型,值为Object类型。
下面列举一下ThreadLocal中的主要方法:

// 构造参数,没有任何操作 
public ThreadLocal() { }

// 方法用protected修饰,用于重写,设置ThreadLocal的初始值 
protected T initialValue() {
	return null; 
}

// get(),获取当前ThreadLocal保存的值
public T get() {
	// 获取当前线程
	Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    	// threadLocals属性不为空则获取当前值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 如果线程绑定的threadLocals属性为空,则初始化这个属性
    // 可知第一次操作get()方法时,才会对threadLocals属性初始化
    return setInitialValue();
}

// setInitialValue(),初始化Thread类的threadLocals属性
private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    // 返回线程t的threadLocals属性
    ThreadLocalMap map = getMap(t);
    if (map != null)
    	// key是ThreadLocal对象
        map.set(this, value);
    else
    	// 初始化threadLocals属性
        createMap(t, value);
    return value;
}

void createMap(Thread t, T firstValue) {
	// 这里对Thread实例对象中的threadLocals属性进行赋值
	// this指当前ThreadLocal对象,作为key
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

// set()和get()大体相似
public void set(T value) {
  Thread t = Thread.currentThread();
   ThreadLocalMap map = getMap(t);
   if (map != null)
       map.set(this, value);
   else
       createMap(t, value);
}

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

1.11 wait()、notify()和notifyAll()方法

1.11.1 锁池和等待池

  • 锁池,当某个对象锁正在被线程占用,其它想要获得该对象锁的线程就会进入该对象的锁池内等待;持有该对象锁的线程释放该锁后,锁池内的线程便可以竞争该对象锁;锁池内的线程都处于Runnable状态
  • 等待池,一个线程调用了某个对象的wait()方法后,那么该线程则进入该对象的等待池等待;等待池内的线程处于Blocked状态

1.11.2 wait()方法

调用该方法的线程会释放正在持有的同步锁并进入到该锁对象的等待池内,线程状态变为Blocked;当持有该同步锁的其它线程调用notify()notifyAll()方法时,该线程才可被唤醒,从等待池进入锁池,线程状态由Blocked变为Runnable

1.11.3 notify()和notifyAll()方法

notify()方法唤醒等待池中的一个线程进入锁池,notifyAll()方法唤醒等待池中的所有线程都进入到锁池

1.11.4 wait()和notify()配合产生死锁的问题

import java.util.LinkedList;
import java.util.List;

/**
 * 模拟产生死锁demo
 */
public class PCDeadLockDemo {

    // 作为对象锁
    private static final List<Integer> list = new LinkedList<>();

    // 生产者
    private static class Producer implements Runnable {
        @Override
        public void run() {
            while (true) {
                synchronized (list) {
                    while (list.size() == 1) {
                        try {
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    list.add(1);
                    System.out.println("add 1");
                    list.notify();
                }
            }
        }
    }

    // 消费者
    private static class Consumer implements Runnable {
        @Override
        public void run() {
            while (true) {
                synchronized (list) {
                    while (list.size() == 0) {
                        try {
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(list.remove(0));
                    list.notify();
                }
            }
        }
    }

    public static void main(String[] args) {
        // 两个消费者,一个生产者
        new Thread(new Producer()).start();
        new Thread(new Consumer()).start();
        new Thread(new Consumer()).start();
    }

}

上述代码产生死锁分析:

  1. 消费者P获取到同步锁,并向容器内添加一个元素,调用list.notify()唤醒了list对象锁的等待池中的其中一个消费者线程,使被唤醒的线程进入list对象锁的锁池
  2. 容器size为1,消费者P调用list.wait()方法,进入list对象锁的等待池,释放list对象锁
  3. list对象锁的锁池内只有步骤1中被唤醒的消费者线程,因此该消费者线程获得list对象锁,容器size不为0,消费者消费掉容器内的元素,并调用list.notify()方法唤醒list对象等待池中的某一个线程,假设被唤醒的是另一个消费者线程,该消费者线程进入list对象锁的锁池
  4. 容器size为0,调用list.wait()方法,进入list对象锁的等待池,释放list对象锁
  5. 此时list对象锁的锁池内只有步骤3中被唤醒的消费者线程,因此该消费者线程获得list对象锁
  6. 此时容器size为0,因此调用list.wait()方法,该消费者线程进入list对象锁的等待池,释放list对象锁
  7. 1个生产者线程和两个消费者线程都进入到了list对象的等待池,死锁产生

对于上述情况,消费者调用list.notifyAll()可以解决,在上述步骤3中会将list对象等待池中的所有线程都唤醒,进入list对象的锁池,在当前消费者释放锁后,即使在下一轮的竞争中,另一个消费者获得了锁并再次进入list对象的等待池,此时锁池内仍然后生产者线程可以继续工作,保证了程序不会产生死锁

1.11.4 lost wake up problem

这节暂时没理解,需要在书中深入学习一下,候补

2 线程并发工具类

2.1 fork-join框架

2.1.0 基本思想

分而治之的思想;fork操作的作用是将一个大问题划分成若干个较小的问题,这个划分过程一般是递归进行,划分的子问题可以直接进行计算。要恰当选取子问题的大小,太大的子问题不利于并行提高性能,而划分太小又会造成额外的开销。每个子问题计算完成后,可以得到关于整个问题的部分解,而join得作用就是把这些解组织起来得到完整解。
在这里插入图片描述

2.1.1 fork-join基本用法

实现1到10000累加任务,代码如下:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

public class ForkJoinDemo {

    private static final Integer MAX = 200; // 阈值

    private static class MyForkJoinTask extends RecursiveTask<Integer> {

        private Integer startV; // 子任务开始计算的值
        private Integer endV; // 子任务结束计算的值

        // 构造方法
        MyForkJoinTask(Integer startV, Integer endV) {
            this.startV = startV;
            this.endV = endV;
        }

        @Override
        protected Integer compute() {
            if (endV - startV <= MAX) {
                // 小于阈值,执行该区间累加任务
                int totalV = 0;
                for(int i = startV; i <= endV; i++)
                    totalV += i;
                return totalV;
            }
            else {
                // 划分子任务
                MyForkJoinTask sub1 = new MyForkJoinTask(startV, (startV + endV) / 2);
                sub1.fork();
                MyForkJoinTask sub2 = new MyForkJoinTask((startV + endV) / 2 + 1, endV);
                sub2.fork();
                // 返回汇总值
                return sub1.join() + sub2.join();
            }
        }

    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // fork-join线程池实例
        ForkJoinPool pool = new ForkJoinPool();
        // 总任务
        ForkJoinTask<Integer> task = new MyForkJoinTask(1, 10000);
        // 执行任务
        pool.submit(task);
        Integer result = task.get();
        System.out.println(result);
    }

}

2.1.2 fork-join框架核心类

  • ForkJoinPool
    ForkJoinPool继承自AbstractExecutorService,其本质上也是线程池的一类。该类中维护着属性volatile WorkQueue[] workQueues,该属性是WorkQueue的数组类型;WorkQueueForkJoinPool的一个内部类,是一个双端队列,负责存储工作队列,在这个内部类中,有属性ForkJoinTask<?>[] array,即在每一个工作队列中又维护着多个ForkJoinTask任务。

  • ForkJoinTask

    • RecursiveTask,有返回值
    • RecursiveAction,无返回值
  • ForkJoinWorkerThread
    该类是框架的工作线程,类中维护属性final ForkJoinPool.WorkQueue workQueue,即存放每个线程执行任务的双端队列;

2.1.3 work-stealing

指当前线程的任务全部执行完毕后,该线程会自动取得其它线程得task池中得未完成任务,从而减少线程阻塞或闲置时间,提高cpu利用率。由上可知,存储任务的容器为双端队列,一般会在其它线程的任务队列的尾端获取任务并执行。
在这里插入图片描述

2.2 CountLatch

类说明:A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes,即,使得一个或多个线程等待在其它线程内的一组操作完成后,再继续执行

该类的使用可以想象成如下场景:

  • 公交车(主线程)等待所有乘客(多个其它线程)全部上车后,才可以出发
  • dota2游戏(主线程)等待10名玩家(多个其它线程)全部连接加载成功后,才能开始游戏

基本使用:

import java.util.Random;
import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch latch = new CountDownLatch(5);

        for (int i = 0; i < latch.getCount(); i++)
            new Thread(new Task(latch), "t_" + i).start();

        System.out.println("等待大家准备好 ~");
        latch.await();

        System.out.println("全部准备完毕,main继续前进 ~");

    }

    private static class Task implements Runnable {

        private CountDownLatch latch;

        Task(CountDownLatch latch) {
            this.latch = latch;
        }

        @Override
        public void run() {
            // 随机生成1000-3000之间的正数
            Random rand = new Random();
            int randNum = rand.nextInt(2001) + 1000;
            try {
                Thread.sleep(randNum);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            latch.countDown();
            System.out.println(Thread.currentThread().getName() + " 准备完毕 ~ 用时 " + randNum / 1000 + " s.");
        }
    }

}

2.3 CyclicBarrier

类说明:A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point,即,一组线程互相等待并达到同一目标后,再继续执行

该类的使用可以想象成如下场景:

  • 小组翻墙游戏,即一个小组内的所有成员全部翻过一面墙后才可以开始翻越下一面墙

基本使用:

import java.util.Random;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {

    public static void main(String[] args) {

        CyclicBarrier barrier = new CyclicBarrier(5); // 组内5个成员
        for (int i = 0; i < barrier.getParties(); i ++)
            new Thread(new Task(barrier), "组员_" + i).start();

        System.out.println("主线程执行完毕");

    }

    private static class Task implements Runnable {

        private CyclicBarrier barrier;

        Task(CyclicBarrier barrier) {
            this.barrier = barrier;
        }

        @Override
        public void run() {
            // 随机生成1000-3000之间的正数
            Random rand = new Random();
            int randNum = rand.nextInt(2001) + 1000;
            try {
                Thread.sleep(randNum); // 模拟当前组员正在翻墙,用时randNum秒
                System.out.println(Thread.currentThread().getName() + " 翻墙完毕,开始等待其他人 ~");
                barrier.await(); // 当前组员翻越后开始等待其他人
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("所有人翻墙完毕,继续执行");
        }
    }

}

2.4 Semaphore

类说明:A counting semaphore. Conceptually, a semaphore maintains a set of permits,一个计数信号量,概念上来讲,其维护了一组许可证

使用场景:

  • 限流

基本用法:

import java.util.concurrent.Semaphore;

/**
 * 模拟 10辆车,3个停车位
 */
public class SemaphoreDemo {

    private static class Task implements Runnable {

        private Semaphore semaphore;

        Task(Semaphore semaphore) {
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire(); // 获取令牌
                System.out.println(Thread.currentThread().getName() + " 正在停车 ~");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semaphore.release();
                System.out.println(Thread.currentThread().getName() + " 离开 ~");
            }
        }

    }

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 10; i++)
            new Thread(new Task(semaphore), "车_" + i).start();
    }

}

值得注意的是该类的release()方法,其方法说明如下:

/**
 * Releases a permit, returning it to the semaphore.
 *
 * <p>Releases a permit, increasing the number of available permits by
 * one.  If any threads are trying to acquire a permit, then one is
 * selected and given the permit that was just released.  That thread
 * is (re)enabled for thread scheduling purposes.
 *
 * <p>There is no requirement that a thread that releases a permit must
 * have acquired that permit by calling {@link #acquire}.
 * Correct usage of a semaphore is established by programming convention
 * in the application.
 */
public void release() {
	sync.releaseShared(1);
}

可知其明确声明了:在调用release()方法前不要求必须调用过acquired()方法,因此在程序执行中若调用release()方法次数比调用acquired()方法的次数多,那么最终令牌数将大于初始化令牌数。示例如下:

import java.util.concurrent.Semaphore;

public class SemaphoreDemo {

    private static class Task implements Runnable {

        private Integer releaseTimes; // 释放令牌次数
        private Integer acquireTimes; // 获取令牌次数
        private Semaphore semaphore; // 信号量

        Task(Integer releaseTimes, Integer acquireTimes, Semaphore semaphore) {
            this.releaseTimes = releaseTimes;
            this.acquireTimes = acquireTimes;
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire(acquireTimes);
                System.err.println(Thread.currentThread().getName() + " is running...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semaphore.release(releaseTimes);
            }
        }

    }

    public static void main(String[] args) throws InterruptedException {
        // 初始化1个令牌
        Semaphore semaphore = new Semaphore(1);
        // 获取1个令牌,释放2个令牌
        Thread t1 = new Thread(new Task(3, 1, semaphore), "t1");
        t1.start();
        Thread.sleep(1000);
        // 获取3个令牌,释放3个令牌
        Thread t2 = new Thread(new Task(3, 3, semaphore), "t2");
        t2.start();
        Thread.sleep(1000);
        System.err.println(semaphore.availablePermits()); // 3,说明最终有3个可被获取的令牌
    }

}

3 原子操作

3.1 cas简介

cas是一种无锁操作,通过cpu指令级别的操作得到保证;它包含3个操作数,分别是内存地址v、旧的预期值a和要修改的新值b,当且仅当旧的预期值a和内存地址v相同时,才将内存地址v修改为b,否做什么也不做。

3.2 通过自旋cas实现原子操作

public class AtomicDemo {
	// 原子变量
	private AtomicInteger atomicI = new AtomicInteger(0);
	// 线程安全计数
	public void safeCount() {
		for(;;) {
			int i = atomicI.get();
			boolean flag = atomicI.compareAndSet(i, ++i);
			// cas操作成功,退出自旋
			if(flag)
				break;
		}
	}
}

3.3 cas带来的问题及解决

3.3.1 aba问题

问题描述:cas操作需要比较原始值和期望值,但若在比较过程中,原始值由A被其它线程修改变为了B,然后又被修改变为了A,这是,比较的结果是原始值和期望值相同,但是实际情况是原始值发生了变化。

解决方法:这种问题可以通过版本号来解决,在atomic包中有AtomicStampedReference类,该类提供了方法compareAndSet(V expectReference, V newReference, int expectStamp, int newStamp),在比较原始值的同时,还需要比较原始版本号。

示例:

import java.util.concurrent.atomic.AtomicStampedReference;

public class UseAtomicStampedReference {

    static AtomicStampedReference<String> asr
            = new AtomicStampedReference<>("old", 0);

    public static void main(String[] args) throws InterruptedException {

        final int oldStamp = asr.getStamp();
        final String oldRef = asr.getReference();
        System.out.println("oldRef:" + oldRef);
        System.out.println("oldStamp:" + oldStamp);

        Thread rightStampT = new Thread(() -> {
            asr.compareAndSet(oldRef, "new", 0, 1);
        });

        rightStampT.start();
        rightStampT.join();

        System.out.println("newRef:" + asr.getReference());
        System.out.println("newStamp:" + asr.getStamp());

    }

}

3.3.2 cpu开销问题

问题描述:多个线程反复尝试更新变量值,使得cpu开销变大,因此cas更适合线程竞争不那么激烈的情况

4 显式锁和AbstractQueueSynchronizer(AQS)

4.1 显式锁Lock

显式锁在程序层面更加复杂,但是比起synchronized关键字要更加灵活,支持更多的定制功能

4.1.1 显式锁与synchronized的区别

  1. synchronized修饰代码块或方法,在被修饰的代码块执行完毕后会自动释放锁,而Lock可以通过unlock()方法手动释放锁,更加灵活
  2. Lock是一个接口,synchronized关键字是内置的语言特性
  3. synchronized代码块内发生异常时,会自动释放锁,而Lock锁定的代码块发生异常时,仍需要手动释放锁
  4. Lock可以让等待锁的线程响应中断,但执行synchronized代码块的线程会一直等待,无法响应中断
  5. synchronized是互斥锁,而Lock接口的读写锁可以实现多个读锁的相容

4.1.2 Lock框架体系

Lock框架体系图
显式锁的实现类主要有三个:

  1. ReentrantLock,可重入锁
  2. ReadLock,读锁,是ReentrantReadWriteLock的内部类
  3. WriteLock,写锁,是ReentrantReadWriteLock的内部类

LockSupport:

Condition:

AbstractQueuedSynchronizer:

4.1.3 ReentrantLock

实现公平锁与非公平锁

公平锁:选择下一个得到锁的线程时,参考先到先得原则,但维护线程队列也需要消耗资源

非公平锁:无视先到先得原则,可以提高吞吐量,但有可能

内部主要结构,其中...表示没列举出来的代码:

public class ReentrantLock implements Lock, java.io.Serializable {
	...
	
	/** Synchronizer providing all implementation mechanics */
    private final Sync sync;
    
	abstract static class Sync extends AbstractQueuedSynchronizer {
		...
		// 用于被FairSync和NonfairSync重写,实现公平锁与非公平锁的不同细节
		abstract void lock();
		...
	}

	// 公平锁
	static final class FairSync extends Sync { ... }

	// 非公平锁
	static final class NonfairSync extends Sync { ... }

	...
}

4.1.4 Condition

接口说明:Where a Lock replaces the use of synchronized methods and statements, a Condition replaces the use of the Object monitor methods. 即Lock代替synchronized代码块,Condition的作用是代替对象指针方法,如wait()、notify()和notifyAll()

4.1.5 Lock与Condition配合实现等待通知

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 当数组为空,获取元素的线程阻塞
 * 当数组已满,添加元素的线程阻塞
 */
public class LockAndConditionDemo<T> {

    private Object[] items; // 存储元素数组

    private int addIdx, removeIdx, count; // 添加、移除下标及当前元素数

    private Lock lock = new ReentrantLock(); // 默认非公平锁

    private Condition insertLock = lock.newCondition(); // 插入条件

    private Condition removeLock = lock.newCondition(); // 移除锁

    LockAndConditionDemo(int size) {
        items = new Object[size];
    }

    public void insert(T t) { // 添加元素
        String name = Thread.currentThread().getName();
        // 加锁
        lock.lock();
        try {
            // 数组已满,插入线程等待
            if (count == items.length) {
                System.err.println(name + " 等待 ~");
                insertLock.await();
            }
            // 插入线程被唤醒,等待结束
            System.err.println(name + "被唤醒,插入元素");
            items[addIdx] = t;
            if (++addIdx == items.length)
                addIdx = 0;
            count++;
            // 唤醒取元素的线程
            removeLock.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    public T remove() {
        String name = Thread.currentThread().getName();
        // 加锁
        lock.lock();
        // 返回结果
        T result = null;
        try {
            // 数组已空,移除线程等待
            if (count == 0) {
                System.err.println(name + " 等待 ~");
                removeLock.await();
            }
            // 移除线程被唤醒,等待结束
            System.err.println(name + "被唤醒,插入元素");
            result = (T) items[removeIdx];
            if (++removeIdx == items.length) {
                removeIdx = 0;
            }
            count--;
            insertLock.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return result;
    }

    public static void main(String[] args) {
        LockAndConditionDemo<Integer> demo = new LockAndConditionDemo<>(3);
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                demo.insert(i);
                System.out.println(i + " is inserted ~");
            }
        }, "插入线程").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                    System.out.println(demo.remove() + " is removed ~");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "移除线程").start();

    }

}

4.1 AQS

对AQS的总结和学习,都记录在aqs框架学习

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值