并发编程学习总结

1. 线程创建

1.1 继承Thread

继承Thread类,重写run方法

public class ThreadDemo01 extends Thread{
    public ThreadDemo01(){
        //编写子类的构造方法,可缺省
    }
    public void run(){
        //编写自己的线程代码
        System.out.println(Thread.currentThread().getName());
    }
    public static void main(String[] args){ 
        ThreadDemo01 threadDemo01 = new ThreadDemo01(); 
        threadDemo01.setName("我是自定义的线程1");
        threadDemo01.start();       
        System.out.println(Thread.currentThread().toString());  
    }
}

1.2 实现Runable

实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target

public class ThreadDemo02 {

    public static void main(String[] args){ 
        System.out.println(Thread.currentThread().getName());
        Thread t1 = new Thread(new MyThread());
        t1.start(); 
    }
}

class MyThread implements Runnable{
    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println(Thread.currentThread().getName()+"-->我是通过实现接口的线程实现方式!");
    }   
}

1.3 实现Callable

通过Callable和FutureTask创建线程

public class ThreadDemo03 {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Callable<Object> oneCallable = new Tickets<Object>();
        FutureTask<Object> oneTask = new FutureTask<Object>(oneCallable);

        Thread t = new Thread(oneTask);

        System.out.println(Thread.currentThread().getName());

        t.start();

    }

}

class Tickets<Object> implements Callable<Object>{

    //重写call方法
    @Override
    public Object call() throws Exception {
        // TODO Auto-generated method stub
        System.out.println(Thread.currentThread().getName()+"-->我是通过实现Callable接口通过FutureTask包装器来实现的线程");
        return null;
    }   
}

1.4 线程池

状态名高 3 位接收新任 务处理阻塞队列任 务说明
RUNNING111YY
SHUTDOWN000NY不会接收新任务,但会处理阻塞队列剩余 任务
STOP001NN会中断正在执行的任务,并抛弃阻塞队列 任务
TIDYING010--任务全执行完毕,活动线程为 0 即将进入 终结
TERMINATED011--终结状态
1.4.0 自定义线程池

步骤1:自定义拒绝策略接口

package com.yuepengfei.java.ThreadPool;

@FunctionalInterface // 拒绝策略
interface RejectPolicy<T> {
    void reject(BlockingQueue<T> queue, T task);
}

步骤2:自定义任务队列

package com.yuepengfei.java.ThreadPool;


import org.apache.log4j.Logger;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class BlockingQueue<T> {

    private static Logger log = Logger.getLogger(BlockingQueue.class);

    // 1. 任务队列
    private Deque<T> queue = new ArrayDeque<>();
    // 2. 锁
    private ReentrantLock lock = new ReentrantLock();
    // 3. 生产者条件变量
    private Condition fullWaitSet = lock.newCondition();
    // 4. 消费者条件变量
    private Condition emptyWaitSet = lock.newCondition();
    // 5. 容量
    private int capcity;
    public BlockingQueue(int capcity) {
        this.capcity = capcity;
    }

    // 带超时阻塞获取
    public T poll(long timeout, TimeUnit unit) {
        lock.lock();
        try {
            // 将 timeout 统一转换为 纳秒
            long nanos = unit.toNanos(timeout);
            while (queue.isEmpty()) {
                try {
                    // 返回值是剩余时间
                    if (nanos <= 0) {
                        return null;
                    }
                    nanos = emptyWaitSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = queue.removeFirst();
            fullWaitSet.signal();
            return t;
        } finally {
            lock.unlock();
        }
    }


    // 阻塞获取
    public T take() {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                try {
                    emptyWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = queue.removeFirst();
            fullWaitSet.signal();
            return t;
        } finally {
            lock.unlock();
        }
    }

    // 阻塞添加
    public void put(T task) {
        lock.lock();
        try {
            while (queue.size() == capcity) {
                try {
                    log.debug("等待加入任务队列 {} ...");
                    fullWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("加入任务队列 {}");
            queue.addLast(task);
            emptyWaitSet.signal();
        } finally {
            lock.unlock();
        }
    }

    // 带超时时间阻塞添加
    public boolean offer(T task, long timeout, TimeUnit timeUnit) {
        lock.lock();
        try {
            long nanos = timeUnit.toNanos(timeout);
            while (queue.size() == capcity) {
                try {
                    if(nanos <= 0) {
                        return false;
                    }
                    log.debug("等待加入任务队列 {} ...");
                    nanos = fullWaitSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("加入任务队列 {}");
            queue.addLast(task);
            emptyWaitSet.signal();
            return true;
        } finally {
            lock.unlock();
        }
    }


    public int size() {
        lock.lock();
        try {
            return queue.size();
        } finally {
            lock.unlock();
        }
    }


    public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
        lock.lock();
        try {
            // 判断队列是否满
            if(queue.size() == capcity) {
                rejectPolicy.reject(this, task);
            } else { // 有空闲
                log.debug("加入任务队列 {}");
                queue.addLast(task);
                emptyWaitSet.signal();
            }
        } finally {
            lock.unlock();
        }
    }


}

步骤3:自定义线程池

package com.yuepengfei.java.ThreadPool;

import org.apache.log4j.Logger;

import java.util.HashSet;
import java.util.concurrent.TimeUnit;

public class ThreadPool {
    private static org.apache.log4j.Logger log = Logger.getLogger(ThreadPool.class);
    // 任务队列
    private BlockingQueue<Runnable> taskQueue;
    // 线程集合
    private HashSet<Worker> workers = new HashSet<>();
    // 核心线程数
    private int coreSize;
    // 获取任务时的超时时间
    private long timeout;
    private TimeUnit timeUnit;
    private RejectPolicy<Runnable> rejectPolicy;

    // 执行任务
    public void execute(Runnable task) {
    // 当任务数没有超过 coreSize 时,直接交给 worker 对象执行
    // 如果任务数超过 coreSize 时,加入任务队列暂存
        synchronized (workers) {
            if (workers.size() < coreSize) {
                Worker worker = new Worker(task);
                log.debug("新增 worker{}, {}");
                workers.add(worker);
                worker.start();
            } else {
            // taskQueue.put(task);
            // 1) 死等
            // 2) 带超时等待
            // 3) 让调用者放弃任务执行
            // 4) 让调用者抛出异常
            // 5) 让调用者自己执行任务
                taskQueue.tryPut(rejectPolicy, task);
            }
        }
    }

    public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapcity,
                      RejectPolicy<Runnable> rejectPolicy) {
        this.coreSize = coreSize;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        this.taskQueue = new BlockingQueue<>(queueCapcity);
        this.rejectPolicy = rejectPolicy;
    }

    class Worker extends Thread {
        private Runnable task;

        public Worker(Runnable task) {
            this.task = task;
        }

        @Override
        public void run() {
    // 执行任务
    // 1) 当 task 不为空,执行任务
    // 2) 当 task 执行完毕,再接着从任务队列获取任务并执行
    // while(task != null || (task = taskQueue.take()) != null) {
            while (task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) {
                try {
                    log.debug("正在执行...{}");
                    task.run();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    task = null;
                }
            }
            synchronized (workers) {
                log.debug("worker 被移除{}");
                workers.remove(this);
            }
        }
    }
}

1.4.1 ThreadPoolExecutor 和Schedulethreadpoolexecutor

scheduledthreadexecutor

  • 构造方法
public ThreadPoolExecutor(int corePoolSize,
	int maximumPoolSize,
	long keepAliveTime,
	TimeUnit unit,
	BlockingQueue<Runnable> workQueue,
	ThreadFactory threadFactory,
	RejectedExecutionHandler handler)

注:

corePoolSize 核心线程数目 (最多保留的线程数)
maximumPoolSize 最大线程数目
keepAliveTime 生存时间 - 针对救急线程
unit 时间单位 - 针对救急线程
workQueue 阻塞队列
threadFactory 线程工厂 - 可以为线程创建时起个好名字
handler 拒绝策略

  1. 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。
  2. 当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue 队列排
    队,直到有空闲的线程。
  3. 如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线
    程来救急。
  4. 如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 4 种实现,其它
    著名框架也提供了实现
    • AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略
    • CallerRunsPolicy 让调用者运行任务
    • DiscardPolicy 放弃本次任务
    • DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
    • Dubbo 的实现,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方
      便定位问题
    • Netty 的实现,是创建一个新线程来执行任务
    • ActiveMQ 的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略
  5. 当高峰过去后,超过corePoolSize 的救急线程如果一段时间没有任务做,需要结束节省资源,这个时间由
    keepAliveTime 和 unit 来控制。
1.4.2 newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

特点:

  1. 核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间
  2. 阻塞队列是无界的,可以放任意数量的任务

评价:

  1. 适用于任务量已知,相对耗时的任务
1.4.3 newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

特点:

  1. 核心线程数是 0, 最大线程数是 Integer.MAX_VALUE,救急线程的空闲生存时间是 60s,意味着

    全部都是救急线程(60s 后可以回收)

    救急线程可以无限创建

  2. 队列采用了 SynchronousQueue 实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手交

评价:

  1. 整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线
    程。 适合任务数比较密集,但每个任务执行时间较短的情况
1.4.4 newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

评语:

  1. 希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。

区别:

  1. 自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一
    个线程,保证池的正常工作
  2. Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改
    • FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因
      此不能调用 ThreadPoolExecutor 中特有的方法
  3. Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改
    • 对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改
1.4.5 newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }
1.4.6 线程池关闭
  • shutdown

    将线程池状态置为SHUTDOWN,并不会立即停止:

    • 停止接收外部submit的任务
    • 内部正在跑的任务和队列里等待的任务,会执行完
    • 等到第二步完成后,才真正停止
  • shutdownNow

    将线程池状态置为stop。企图立即停止,事实上不一定:

    • 跟shutdown()一样,先停止接收外部提交的任务
    • 忽略队列里等待的任务
    • 尝试将正在跑的任务interrupt中断
    • 返回未执行的任务列表
    它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它也可能必须要等待所有正在执行的任务都执行完成了才能退出。
    但是大多数时候是能立即退出的
    
  • awaitTermination(long timeOut, TimeUnit unit)

    当前线程阻塞,直到

    • 等所有已提交的任务(包括正在跑的和队列中等待的)执行完
    • 或者等超时时间到
    • 或者线程被中断,抛出InterruptedException

调用 shutdown 后,由于调用线程并不会等待所有任务运行结束,因此如果它想在线程池 TERMINATED 后做些事
情,可以利用此方法等待

2. 线程状态

2.1 线程的五种状态

  • 新建状态(new)
  • 就绪状态(runable)
  • 运行状态(running)
  • 阻塞状态(blocked)
  • 死亡状态(dead)

img

注意:

  • yield、sleep、wait

    yield和sleep都是Thread的静态方法,wait是object的方法,前两个都不释放锁,wait释放锁。yield的和sleep的区别是,sleep可以设定时间,yield可以理解为sleep(0)

2.2 Thread重要方法

2.2.1 常规方法
  1. start方法,开始执行该线程

  2. stop方法,强制结束该线程执行

  3. join方法,等待该线程结束

  4. sleep和yield方法,线程进入等待

  5. run()方法

    直接执行线程的run()方法,但是线程调用start()方法时也会运行run()方法,区别就是一个是由线程调度运行run()方法,一个是直接调用了线程中的run()方法

2.2.2 interrupt
  1. interrupt本质只是一个普通方法,调用时会把线程是否被打断的flag设置为true。这在sleep,join,await,wait等方法中会抛出线程被打断的异常,并将flag设置为false。

  2. 对park的影响

    如下两个条件任何一个成立,park()都不会阻塞:

    • 中断标志位存在(wait、join、sleep或Thread.interrupted()都会清除中断标志位)
    • _counter为1(之前调用了unpark或者interrupt)
2.2.3 park和unpark
  • 每个线程都有自己的一个 Parker 对象,由三部分组成 __counter , _cond 和 _mutex_打个比喻线程就像一个旅人,Parker 就像他随身携带的背包,条件变量就好比背包中的帐篷。_counter 就好比背包中的备用干粮(0 为耗尽,1 为充足)
  • 调用 park 就是要看需不需要停下来歇息
    • 如果备用干粮耗尽,那么钻进帐篷歇息
    • 如果备用干粮充足,那么不需停留,继续前进
  • 调用 unpark,就好比补充干粮
    • 如果这时线程还在帐篷,就唤醒让他继续前进
    • 如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮,不需停留继续前进
    • 因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮

应用

Park在写的时候要拿到线程,然后进行操作,线程多的话可能需要将线程放在数组里面或者集合里,按照自己的需求唤醒对应的线程。

2.3 锁对象方法

  • wait和notify、notifyAll

  1. Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态
  2. BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片
  3. BLOCKED 线程会在 Owner 线程释放锁时唤醒
  4. WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入
    EntryList 重新竞争

正确使用方式:一般我们使用wait和notifyAll,因为notify是随机唤醒,一般不符合业务逻辑

package com.yuepengfei.java.WaitNotify;

public class WaitNotity {

    static Object lock = new Object();
    static volatile boolean flag = true;

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

        new Thread(() ->{
            //todo wait,notify必须在锁代码块中,不然会报错
            synchronized(lock) {
                while(flag) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 干活
                System.out.println("干活了");
            }
        }).start();

        //另一个线程
        new Thread(()->{
            synchronized(lock) {
                flag = false;
                lock.notifyAll();
            }
        }).start();

    }
}

  • await和signal、signalAll

使用案例

package com.yuepengfei.java;

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

public class AwaitSignalDemo {
    static ReentrantLock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

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

        Thread t = new Thread(() -> {
            lock.lock();
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
            System.out.println("线程" + Thread.currentThread().getName());
        });
        t.setName("线程2");
        t.start();

        Thread.sleep(1000);
        new Thread(() -> {
            lock.lock();
            try {
                condition.signal();
            } finally {
                lock.unlock();
            }
        }).start();
    }
}

3. 关键字

3.1. volatile

3.1.1 原理

volatile 的底层实现原理是内存屏障,Memory Barrier(Memory Fence)

  • 对 volatile 变量的写指令后会加入写屏障
  • 对 volatile 变量的读指令前会加入读屏障
3.1.2 保证可见性

写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中

public void actor2(I_Result r) {
    num = 2;
    ready = true; // ready 是 volatile 赋值带写屏障
    // 写屏障
}

而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据

public void actor1(I_Result r) {
    // 读屏障
    // ready 是 volatile 读取值带读屏障
    if(ready) {
    	r.r1 = num + num;
    } else {
    	r.r1 = 1;
}
3.1.3 保证有序性

写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后

读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前

注意:读写屏障之保证线程内的指令重排,不能控制多线程内的指令交错

3.1.4 单例模式中的应用
public final class Singleton {
    private Singleton() { }
    private static volatile Singleton INSTANCE = null;
    public static Singleton getInstance() {
        // 实例没创建,才会进入内部的 synchronized代码块
        if (INSTANCE == null) {
            synchronized (Singleton.class) { // t2
                // 也许有其它线程已经创建实例,所以再判断一次
                if (INSTANCE == null) { // t1
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

解释:

  1. 单层if存在的缺点和不足
  2. 不加volatile存在的问题

3.2 synchronized

3.2.1 锁升级

无锁 -----> 轻量级锁 -----> 偏向锁 -----> 重量级锁

3.2.2 对象头

这里的对象是指:锁对象

img

3.2.3 偏向锁

偏向锁产生的原因:轻量级锁参在锁重入的问题

只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有 。

  • 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的thread、epoch、age 都为 0
  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数-XX:BiasedLockingStartupDelay=0 来禁用延迟
  • 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、
    age 都为 0,第一次用到 hashcode 时才会赋值
3.2.4 轻量级锁

轻量级锁产生的原因:很多时候线程之间并不存在锁竞争,因此不需要启动重量级锁

3.2.5 重量级锁
  • monitor

每个 Java锁对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的
Mark Word 中就被设置指向 Monitor 对象的指针

4. AQS

AQS其实就是AbstractQueuedSynchronizer这个抽象类的简称。重写其内的部分方法就可以实现自己的并发工具,常用的并发工具很多都是基于其开发的,AQS就是并发编程的优秀模板。

4.1 AQS常见概念

4.1.1 关键方法和属性
  • acquire
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

其中tryAcquire(arg)方法需要重写。例如

	@Override
    protected boolean tryAcquire(int acquires) {
        if (acquires == 1){
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
        }
        return false;
    }
  • release
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

其中tryRelease(arg)方法需要重写。例如

    @Override
    protected boolean tryRelease(int acquires) {
        if(acquires == 1) {
            if(getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
        return false;
    }
  • acquireShared(int arg)
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
  • releaseShared(int arg)
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
  • acquireInterruptibly
    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }
  • acquireSharedInterruptibly
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
  • enq
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

解读可参考:并发编程之美 P127

  • isHeldExclusively

是否独自站

  • state

  • ConditionObject

Condition newCondition() { return new ConditionObject(); }

AQS中实现了ConditionObject,却没创建生成ConditionObject的方法,所以一般自定义同步器多添加上面一段代码。

注意: java并发编程之美P135的虚假唤醒的情况

4.1.2 独占锁和共享锁

独占锁:最多一个线程占有锁,例如:ReentrantLock

共享锁:可多个线程占有同一把锁,例如:Semaphore

4.1.3 公平锁和非公平锁

公平锁:如果有线程在排队,则线程直接进入队列排队

非公平锁:线程进来先尝试获取锁,获取不到才排队

4.1.4 可打断和不可打断

不可打断:即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了

4.2 ReentrantLock

重点研究AQS,ReentrantLock只是其一个实现,可重入独占锁。支持公平锁和非公平锁,默认非公平锁。

4.3 读写锁

巧妙使用AQS的状态高16位表示获取到读锁的个数,低16位表示写线程的可重入次数。实现了读写分离。

5. 原子类

5.1 原子整数

AtomicBoolean
AtomicInteger
AtomicLong

使用没啥好说的,有时间可以研究一下其原理。

5.2 原子引用

AtomicReference
AtomicMarkableReference
AtomicStampedReference

AtomicReference

原子意味着尝试更改相同AtomicReference的多个线程(例如,使用比较和交换操作)不会使AtomicReference最终达到不一致的状态。 AtomicReference甚至有一个先进的compareAndSet()方法,它可以将引用与预期值(引用)进行比较,如果它们相等,则在AtomicReference对象内设置一个新的引用。

AtomicStampedReference

在AtomicReference基础之上多了一个时间戳的概念,解决A–>B–>A的问题

AtomicMarkableReference

和AtomicStampedReference比较,时间戳编程了标记位。

5.3 原子数组

AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray

5.4 字段更新器

5.5 原子累加器

  • LongAdder
package com.yuepengfei.java;

import lombok.SneakyThrows;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.LongAdder;

public class LongAdderDemo {

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

        LongAdder longAdder = new LongAdder();

        CountDownLatch countDownLatch = new CountDownLatch(100);

        CyclicBarrier cyclicBarrier = new CyclicBarrier(100, new Runnable() {
            @Override
            public void run() {
                System.out.println("所有线程开已准备就绪");
            }
        });

        Semaphore semaphore = new Semaphore(10);


        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    cyclicBarrier.await();//本线程已到位
                    semaphore.acquire();//或许许可
                    for (int j = 0; j < 100; j++) {
                        longAdder.add(1L);
                    }
                    semaphore.release();//释放许可
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();
        System.out.println("计算结束");
        System.out.println(longAdder.longValue());
    }

}

  • LongAccumulator

LongAdder其实是LongAccumulator的一个特例,调用LongAdder相当使用下面的方式调用LongAccumulator

    LongAccumulator accumulator = new LongAccumulator(new LongBinaryOperator() {
        
        @Override
        public long applyAsLong(long left, long right) {
            return left + right;
        }
    }, 0);

5.6 Unsafe

Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得

public class UnsafeAccessor {
    static Unsafe unsafe;
    static {
        try {
                Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
                theUnsafe.setAccessible(true);
                unsafe = (Unsafe) theUnsafe.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new Error(e);
        }
    }
    static Unsafe getUnsafe() {
    	return unsafe;
    }
}

Unsafe CAS 操作

@Data
class Student {
    volatile int id;
    volatile String name;
}
Unsafe unsafe = UnsafeAccessor.getUnsafe();
Field id = Student.class.getDeclaredField("id");
Field name = Student.class.getDeclaredField("name");
// 获得成员变量的偏移量
long idOffset = UnsafeAccessor.unsafe.objectFieldOffset(id);
long nameOffset = UnsafeAccessor.unsafe.objectFieldOffset(name);
Student student = new Student();
// 使用 cas 方法替换成员变量的值
UnsafeAccessor.unsafe.compareAndSwapInt(student, idOffset, 0, 20); // 返回 true
UnsafeAccessor.unsafe.compareAndSwapObject(student, nameOffset, null, "张三"); // 返回 true
System.out.println(student)

6. 同步器

package com.yuepengfei.java;

import lombok.SneakyThrows;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.LongAdder;

public class LongAdderDemo {

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

        LongAdder longAdder = new LongAdder();

        CountDownLatch countDownLatch = new CountDownLatch(100);

        CyclicBarrier cyclicBarrier = new CyclicBarrier(100, new Runnable() {
            @Override
            public void run() {
                System.out.println("所有线程开已准备就绪");
            }
        });

        Semaphore semaphore = new Semaphore(10);


        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    cyclicBarrier.await();//本线程已到位
                    semaphore.acquire();//或许许可
                    for (int j = 0; j < 100; j++) {
                        longAdder.add(1L);
                    }
                    semaphore.release();//释放许可
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();
        System.out.println("计算结束");
        System.out.println(longAdder.longValue());
    }

}

6.1 CountDownLatch

CountDownLatch countDownLatch = new CountDownLatch(100);

调用countDownLatch.await()的线程进入等待,等待计数(初始值100)为0,当一个线程调用countDownLatch.countDown() ,其中的计数减一。注:await可以设定超时时间。

例子:比如张三、李四和王五几个人约好去饭店一起去吃饭,这几个人都是比较绅士,要等到所有人都到齐以后才让服务员上菜。这种场景就可以用到CountDownLatch。

  • 顾客类
package onemore.study;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.CountDownLatch;

public class Customer implements Runnable {
    private CountDownLatch latch;
    private String name;

    public Customer(CountDownLatch latch, String name) {
        this.latch = latch;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
            Random random = new Random();

            System.out.println(sdf.format(new Date()) + " " + name + "出发去饭店");
            Thread.sleep((long) (random.nextDouble() * 3000) + 1000);
            System.out.println(sdf.format(new Date()) + " " + name + "到了饭店");
            latch.countDown();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 服务员类
package onemore.study;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;

public class Waitress implements Runnable {
    private CountDownLatch latch;
    private String name;

    public Waitress(CountDownLatch latch, String name) {
        this.latch = latch;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
            System.out.println(sdf.format(new Date()) + " " + name  + "等待顾客");
            latch.await();
            System.out.println(sdf.format(new Date()) + " " + name  + "开始上菜");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 测试类
package onemore.study;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class CountDownLatchTester {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);

        List<Thread> threads = new ArrayList<>(3);
        threads.add(new Thread(new Customer(latch, "张三")));
        threads.add(new Thread(new Customer(latch, "李四")));
        threads.add(new Thread(new Customer(latch, "王五")));
        for (Thread thread : threads) {
            thread.start();
        }

        Thread.sleep(100);
        new Thread(new Waitress(latch, "♥小芳♥")).start();

        for (Thread thread : threads) {
            thread.join();
        }
    }
}

参考文档

6.2 CyclicBarrier

CyclicBarrier cyclicBarrier = new CyclicBarrier(100, new Runnable() {
            @Override
            public void run() {
                System.out.println("所有线程开已准备就绪");
            }
        });

设定阈值为100,当线程调用cyclicBarrier.await()进入等待,当等待的线程达到100则等待的线程开始执行。

6.3 Semaphore

Semaphore semaphore = new Semaphore(10);

简单说,作用就是控制线程数。semaphore.acquire()获取许可,最多同时许可10个,semaphore.release()释放许可,线程执行完及时释放。

Semaphore和线程池的区别:

使用Semaphore,实际工作线程由开发者自己创建;使用线程池,实际工作线程由线程池创建

使用Semaphore,并发线程的控制必须手动通过acquire()release()函数手动完成;使用线程池,并发线程的控制由线程池自动管理

使用Semaphore不支持设置超时和实现异步访问;使用线程池则可以实现超时和异步访问,通过提交一个Callable对象获得Future,从而可以在需要时调用Future的方法获得线程执行的结果,同样利用Future也可以实现超时

参考文档

7. 常见线程类

7.1 ThreadLocal

ThreadLocal提供了线程的局部变量,每个线程都可以通过set()get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离。简要言之:往ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。

7.1.1价值意义
  • 管理Connection

**最典型的是管理数据库的Connection:**当时在学JDBC的时候,为了方便操作写了一个简单数据库连接池,需要数据库连接池的理由也很简单,频繁创建和关闭Connection是一件非常耗费资源的操作,因此需要创建数据库连接池。

那么,数据库连接池的连接怎么管理呢??我们交由ThreadLocal来进行管理。为什么交给它来管理呢?ThreadLocal能够实现当前线程的操作都是用同一个Connection,保证了事务

public class DBUtil {
    //数据库连接池
    private static BasicDataSource source;

    //为不同的线程管理连接
    private static ThreadLocal<Connection> local;


    static {
        try {
            //加载配置文件
            Properties properties = new Properties();

            //获取读取流
            InputStream stream = DBUtil.class.getClassLoader().getResourceAsStream("连接池/config.properties");

            //从配置文件中读取数据
            properties.load(stream);

            //关闭流
            stream.close();

            //初始化连接池
            source = new BasicDataSource();

            //设置驱动
            source.setDriverClassName(properties.getProperty("driver"));

            //设置url
            source.setUrl(properties.getProperty("url"));

            //设置用户名
            source.setUsername(properties.getProperty("user"));

            //设置密码
            source.setPassword(properties.getProperty("pwd"));

            //设置初始连接数量
            source.setInitialSize(Integer.parseInt(properties.getProperty("initsize")));

            //设置最大的连接数量
            source.setMaxActive(Integer.parseInt(properties.getProperty("maxactive")));

            //设置最长的等待时间
            source.setMaxWait(Integer.parseInt(properties.getProperty("maxwait")));

            //设置最小空闲数
            source.setMinIdle(Integer.parseInt(properties.getProperty("minidle")));

            //初始化线程本地
            local = new ThreadLocal<>();


        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() throws SQLException {
        //获取Connection对象
        Connection connection = source.getConnection();

        //把Connection放进ThreadLocal里面
        local.set(connection);

        //返回Connection对象
        return connection;
    }

    //关闭数据库连接
    public static void closeConnection() {
        //从线程中拿到Connection对象
        Connection connection = local.get();

        try {
            if (connection != null) {
                //恢复连接为自动提交
                connection.setAutoCommit(true);

                //这里不是真的把连接关了,只是将该连接归还给连接池
                connection.close();

                //既然连接已经归还给连接池了,ThreadLocal保存的Connction对象也已经没用了(是必须清理)
                local.remove();

            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


}
  • 避免一些参数传递

在编写程序中也是一样的:日常中我们要去办理业务可能会有很多地方用到身份证,各类证件,每次我们都要掏出来很麻烦。

	// 咨询时要用身份证,学生证,房产证等等....
    public void consult(IdCard idCard,StudentCard studentCard,HourseCard hourseCard){

    }

    // 办理时还要用身份证,学生证,房产证等等....
    public void manage(IdCard idCard,StudentCard studentCard,HourseCard hourseCard) {

    }

而如果用了ThreadLocal的话,ThreadLocal就相当于一个机构,ThreadLocal机构做了记录你有那么多张证件。用到的时候就不用自己掏了,问机构拿就可以了。

在咨询时的时候就告诉机构:来,把我的身份证、房产证、学生证通通给他。在办理时又告诉机构:来,把我的身份证、房产证、学生证通通给他。

	// 咨询时要用身份证,学生证,房产证等等....
    public void consult(){

        threadLocal.get();
    }

    // 办理时还要用身份证,学生证,房产证等等....
    public void takePlane() {
        threadLocal.get();
    }

参考文章

7.2 CurrentHashMap

  • 原理

在JDK1.7中ConcurrentHashMap采用了数组+Segment+分段锁的方式实现。

1.Segment(分段锁)

ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表,同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

2.内部结构

ConcurrentHashMap使用分段锁技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。如下图是ConcurrentHashMap的内部结构图:

img

从上面的结构我们可以了解到,ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作。第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部。

3.该结构的优劣势

坏处

这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长

好处

写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment,这样,在最理想的情况下,ConcurrentHashMap可以最高同时支持Segment数量大小的写操作(刚好这些写操作都非常平均地分布在所有的Segment上)。所以,通过这一种结构,ConcurrentHashMap的并发能力可以大大的提高。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值