java并发编程及JUC工具包学习笔记

笔记将根据学习进度持续更新整理,尽量翻译成自己的语言并将每个知识点分成基础和高级部分

目前参考的资料有:
《Java并发编程之美》 翟陆续 薛宾田
Java 并发编程 78 讲 徐隆曦
JUC初级视频教程 尚硅谷

目录

  • 一 创建线程的几种方式
    • 基础部分:
      • 1 继承java.lang.Thread类
      • 2 实现Runnable接口
      • 3 Callable实现多线程
      • 线程池
    • 高级部分:
      • 1 Thread本质是实现的Runnable接口
      • 2 所有的创建方式本质上只有1种
  • 二 线程常用操作方法
    • 基础:
      • 1 线程的命名和取得
      • 2 线程休眠、线程中断、线程强制执行
      • 3 线程礼让、线程优先级
    • 高级:
      • 1 sleep和wait区别
  • 三 线程的运行状态
    • 基础:
    • 高级:
  • 四 线程的同步与死锁
    • 基础:
      • 1 问题引出
      • 2 同步方法及同步代码块
      • 3 Lock
      • 4 生产者与消费者
      • 5 虚假唤醒
      • 6 Condition中的await signalAll
      • 7 线程死锁
    • 高级:
      • 公平和非公平锁
      • Synchronized和Lock区别
  • 五 集合类的线程安全
    • 基础
    • 高级
      • 深入探讨CopyOnWriteArrayList
      • ConcurrentModificationException
      • 深入探讨CopyOnWriteHashMap
  • 优雅地停止线程
  • 守护线程
  • volatile关键字
  • CountDownLatch
  • CyclicBarrier
  • Semaphore
  • ReadWriteLock
  • BlockingQueue
  • 线程池
  • ForkJoin
  • 异步回调
  • Synchronized
  • Atomic
  • 应用

一 创建线程的几种方式

基础部分:

1 继承java.lang.Thread类

我们需要复写run方法,但注意的是,在main方法调用时,如果使用MyThread().run()调用,并不会创建一个新线程

class MyThread extends Thread {
    private String name;

    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(name + i);
        }
    }
}

public class Hello {

    public static void main(String[] args) {
        new MyThread("A").start();
        new MyThread("B").start();
        new MyThread("C").start();
    }
}

点进去看start()源码

if (threadStatus != 0)
            throw new IllegalThreadStateException();

如果状态!=0 会抛出一个RuntimeException子类的异常,如果同一个线程类对象重复启动,就会抛出该异常
例如下面的代码

public static void main(String[] args) {
        MyThread t = new MyThread("A");
        t.start();
        t.start();
}

在源码中我们还注意到了start0方法

try {
            start0();
            started = true;
        } 

其中start0 是一个native方法
java不能直接操作操作系统,而是由JVM根据不同的操作系统,如win mac linux的具体实现, 去调用JNI本地接口来操作操作系统的底层

private native void start0();

2 实现Runnable接口

和上面的调用方式不同,我们需要在Thread构造方法中传递Runnable参数,再由Thread启动start

public Thread(Runnable target)

实例

class MyThread implements Runnable {
    private String name;

    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(name + i);
        }
    }
}
public static void main(String[] args) {
        Thread t = new Thread(new MyThread("A"));
        t.start();
    }

在jdk1.8时,引入了函数式接口和lambda表达式,所以我们可以用lambda对它进行操作

@FunctionalInterface
public interface Runnable
public static void main(String[] args) {
        for(int x = 0;x < 10;x++){
            new Thread(()->{
                for(int i = 0;i < 20;i++){
                    System.out.println(i);
                }
            }).start();
        }
    }

3 Callable实现多线程

从JDK1.5开始 可以使用java.util.concurrent.Callable接口实现多线程
它的特点是可以有返回值

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

那么如何去创建新线程呢?
从下面的继承关系可以看见,FutureTask是Runnable的子类,而Thread需要一个Runnable接口
所以我们可以将FutureTask传给Thread去调用 另外,在Future的构造中,可以传递Callable接口的实现类
我们可以将自己的多线程实现类继承自callable接口 然后封装到FutureTask中去

public class FutureTask<V> implements RunnableFuture<V>{
	public FutureTask(Callable<V> callable) 
}


public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

public interface Future<V> {
,,,
V get() throws InterruptedException, ExecutionException;
...
}

java并发编程及JUC工具包学习笔记_第1张图片
实例

class MyThread implements Callable<String> {
    @Override
    public String call() throws Exception {
        for(int i = 0;i < 20 ;i++){
            System.out.println(i);
        }
        return "Hello";
    }
}

public class Hello {

    public static void main(String[] args) throws Exception {
        FutureTask<String> task = new FutureTask<>(new MyThread());
        new Thread(task).start();
        System.out.println(task.get());
    }
}

线程池

代写

高级部分:

1 Thread本质是实现的Runnable接口

class Thread implements Runnable
...
同时我们注意到当调用构造方法时,该runnable会传到init方法,
public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals){
...
	最终会传到Thread类的target属性中
	this.target = target;
...
}
最后当调用Thread的start方法时,会内部调用run方法,之后实际调用的为runnable的run方法
    @Override
    public void run() {0
        if (target != null) {
            target.run();
        }
    }

2 所有的创建方式本质上只有1种

二 线程常用操作方法

基础:

1 线程的命名和取得

线程命名 构造
public Thread(Runnable target, String name)
成员方法
public final synchronized void setName(String name)
public final String getName()
获取当前线程
public static native Thread currentThread();
实例
class MyThread implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName());
        return "Hello";
    }

}

public class Hello {

    public static void main(String[] args) throws Exception {
        FutureTask<String> task = new FutureTask<>(new MyThread());
        
        new Thread(task,"THREAD-A").start();
    }
}    

若未命名,则会自动命名,原因是Thread构造中的nextThreadNum()

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

//nextThreadNum() 点进去看一下
    private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

2 线程休眠、线程中断、线程强制执行

休眠
public static native void sleep(long millis) throws InterruptedException;
在JDK8中,还有一种方法可以让线程休眠
TimeUnit.SECONDS.sleep(4);

中断
判断是否被中断
@return  true if the current thread has been interrupted;
		false otherwise.
public static boolean interrupted()
中断线程
public void interrupt() 

实例
    public static void main(String[] args) throws Exception {
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(100000);
                System.out.println("end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t.start();
        Thread.sleep(1000);
        if (!t.isInterrupted()){
            t.interrupt();
            System.out.println("OK");
        }
    }

join
public final synchronized void join(long millis)

实例
public static void main(String[] args) throws Exception {
        Thread mainThread = Thread.currentThread();
        Thread t = new Thread(() -> {
            for(int x = 0; x < 1000; x ++){
                if(x == 5) {
                    try {
                        mainThread.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName() + x);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"子线程");
        t.start();
        for (int i = 0; i < 1000; i++) {
            Thread.sleep(100);
            System.out.println(i);
        }
    }

3 线程礼让、线程优先级

 public static native void yield();
 
    public static void main(String[] args) throws Exception {
        Thread mainThread = Thread.currentThread();
        Thread t = new Thread(() -> {
            for(int x = 0; x < 1000; x ++){
                if(x % 5 == 0) {
                    Thread.yield();
                    System.out.println("让给你");
                }
                try {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName() + x);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"子线程");
        t.start();
        for (int i = 0; i < 1000; i++) {
            Thread.sleep(100);
            System.out.println("main" + i);
        }
    }

从理论上,优先越高,越有可能执行(不是绝对的)

public final void setPriority(int newPriority) 
 public final int getPriority()

实例 main和默认线程的优先级默认为5
System.out.println(Thread.currentThread().getPriority());

java并发编程及JUC工具包学习笔记_第2张图片

高级:

1 sleep和wait区别

sleep方法是Thread类的方法
wait方法是Object类的方法。

sleep方法会使当前线程让出cpu的调度资源,从而让其他线程有获得被执行的机会, 但是并不会让当前线程释放锁对象。
wait方法是让当前线程释放锁并进入wait状态, 不参与获取锁的争夺,从而让其他等待资源的线程有机会获取锁, 只有当其他线程调用notify或notifyAll方法,被wait的线程才能重新与其他线程一起争夺资源。

wait必须在同步代码块中
sleep可以在任何地方

三 线程的运行状态

基础:

并不是调用start()方法后,线程就开始处理了,而是进入就绪状态。之后由操作系统调度,调度成功后调用run方法
java并发编程及JUC工具包学习笔记_第3张图片
在Thread.State中
线程状态描述如下

NEW 
尚未启动的线程处于此状态。
RUNNABLE 
在Java虚拟机中执行的线程处于此状态。
BLOCKED 
被阻塞等待监视器锁定的线程处于此状态。
WAITING 
正在等待另一个线程执行特定动作的线程处于此状态。
TIMED_WAITING 
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
TERMINATED 
已退出的线程处于此状态。

高级:

四 线程的同步与死锁

基础:

1 问题引出

class MyThread implements Runnable{
    private int ticket = 100;

    @Override
    public void run() {
        while(true){
            if(ticket > 0){
                //模拟延时操作
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + --ticket);
            }else{
                System.out.println("卖光了");
                break;
            }
        }
    }
}

public class Hello {

    public static void main(String[] args) throws Exception {
        MyThread mt = new MyThread();
        new Thread(mt,"A").start();
        new Thread(mt,"B").start();
        new Thread(mt,"C").start();
    }
}

上面的代码多次测试,会发现还有负数的情况    

2 同步方法及同步代码块

同步代码块
synchronized(对象){
	同步代码操作
}
例如
@Override
public void run() {
    while(true){
        synchronized (this){
            if(ticket > 0){
                //模拟延时操作
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + --ticket);
            }else{
                System.out.println("卖光了");
                break;
            }
        }
    }
}
不过我们发现加入同步后性能下降了
同步方法
class MyThread implements Runnable {
    private int ticket = 10;

    public synchronized boolean sale(){
        if (ticket > 0) {
            //模拟延时操作
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + --ticket);
            return true;
        } else {
            System.out.println("卖光了");
            return false;
        }
    }

    @Override
    public void run() {
        while (this.sale()) {
        }
    }
}

public class Hello {

    public static void main(String[] args) throws Exception {
        MyThread mt = new MyThread();
        new Thread(mt, "A").start();
        new Thread(mt, "B").start();
        new Thread(mt, "C").start();
    }
}   

3 Lock

实例
class Ticket
{
    private volatile int count = 30;

    private Lock lock = new ReentrantLock();

    public void sale()
    {

        lock.lock();
        try
        {
            if(count > 0)
            {
                System.out.println(Thread.currentThread().getName() + " 卖出第 " + (count--) + " 还剩: " + count);
            }
        }catch (Exception e)
        {
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }
}

public class SaleTickets {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(()->{for (int i = 0 ;i < 40; ++i){ticket.sale();}},"A").start();
        new Thread(()->{for (int i = 0 ;i < 40; ++i){ticket.sale();}},"B").start();
        new Thread(()->{for (int i = 0 ;i < 40; ++i){ticket.sale();}},"C").start();
    }
}

4 生产者与消费者

在Object类中的wait方法
public final void wait()
                throws InterruptedException
public final void wait(long timeout)
                throws InterruptedException                
唤醒
public final void notify()
public final void notifyAll()
实例
使用红绿灯即标记位的方式实现
class Message {
    private String tittle;
    private String content;
    //true表示生产 false 表示消费
    private boolean flag = true;

    public synchronized void set(String tittle, String content) throws InterruptedException {
        if (!this.flag) {
            super.wait();
        }
        this.tittle = tittle;
        Thread.sleep(100);
        this.content = content;
        this.flag = false;
        this.notifyAll();
    }

    public synchronized String get() throws InterruptedException {
        if (this.flag) {
            super.wait();
        }
        Thread.sleep(100);
        this.flag = true;
        super.notifyAll();
        return this.tittle + this.content;
    }
}


class Producer implements Runnable {
    private Message msg;

    public Producer(Message msg) {
        this.msg = msg;
    }

    @Override
    public void run() {
        for (int x = 0; x < 5; x++) {
            try {
                if (x % 2 == 0) {
                    msg.set("A", "爱吃苹果");
                } else {
                    msg.set("B", "爱吃菠萝");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

class Consumer implements Runnable {
    private Message msg;

    public Consumer(Message msg) {
        this.msg = msg;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println(this.msg.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Hello {

    public static void main(String[] args) throws Exception {
        Message msg = new Message();
        new Thread(new Producer(msg)).start();
        new Thread(new Consumer(msg)).start();
    }
}

注意,在上面的 if (!this.flag)和if(!this.flag)有可能会引起“虚假唤醒”的问题

5 虚假唤醒

实例
//多个线程对number操作
public class ProducerConsumer {
    //资源
    private volatile int number = 0;

    //增加
    public synchronized void increment()
    {
        try
        {
            while(number > 0)
            {
                this.wait();
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        ++number;

        System.out.println(Thread.currentThread().getName() + " --> " + number);
        this.notifyAll();
    }

    //减少
    public synchronized void decrement()
    {
        try {
            while (number <= 0)
            {
                this.wait();
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        --number;
        System.out.println(Thread.currentThread().getName() + " --> " + number);
        this.notifyAll();
    }

    public static void main(String[] args)
    {
        ProducerConsumer producerConsumer = new ProducerConsumer();
        new Thread(()->{for (int i = 0 ; i < 10; ++i){producerConsumer.increment();}},"A").start();
        new Thread(()->{for (int i = 0 ; i < 10; ++i){producerConsumer.increment();}},"B").start();
        new Thread(()->{for (int i = 0 ; i < 10 ; ++i){producerConsumer.decrement();}},"C").start();
        new Thread(()->{for (int i = 0 ; i < 10 ; ++i){producerConsumer.decrement();}},"D").start();
}       

6 Condition中的await signalAll

java并发编程及JUC工具包学习笔记_第4张图片

class ProducerConsumer{

    private volatile int number = 0;

    private Lock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    public void increment(){
        lock.lock();
        try{
            while(number != 0){
                condition.await();
            }
            ++number;
            System.out.println(Thread.currentThread().getName() + number);
            condition.signalAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void decrement(){
        lock.lock();
        try{
            while(number == 0){
                condition.await();
            }
            --number;
            System.out.println(Thread.currentThread().getName() + number);
            condition.signalAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}


public class Hello {

    public static void main(String[] args){
        ProducerConsumer producerConsumer = new ProducerConsumer();
        new Thread(()->{for(int i = 0;i < 100;i++){producerConsumer.increment();}},"A").start();
        new Thread(()->{for(int i = 0;i < 100;i++){producerConsumer.increment();}},"B").start();
        new Thread(()->{for(int i = 0;i < 100;i++){producerConsumer.decrement();}},"C").start();
        new Thread(()->{for(int i = 0;i < 100;i++){producerConsumer.decrement();}},"D").start();
    }

}    

使用Condition可以实现精确唤醒,实例如下:

//3个线程顺序打印 A打印5次 B打印10次 C打印15次 打印5轮
class ConditionDemo {

    //标志位,flag == 1 , 第一个线程打印 , flag == 2 , 第二个线程打印, flag==3,第三个线程打印
    private volatile int flag = 1;

    private Lock lock = new ReentrantLock();

    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    public void printFirst() {
        lock.lock();
        try {
            while (flag != 1) {
                c1.await();
            }
            for (int i = 0; i < 5; ++i) {
                System.out.println(Thread.currentThread().getName() + " --> " + i);
            }

            //更新标志位,让第二个线程打印
            flag = 2;
            c2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printSecond() {
        lock.lock();
        try {
            while (flag != 2) {
                c2.await();
            }
            for (int i = 0; i < 10; ++i) {
                System.out.println(Thread.currentThread().getName() + " --> " + i);
            }
            flag = 3;

            c3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printThird() {
        lock.lock();
        try {
            while (flag != 3) {
                c3.await();
            }
            for (int i = 0; i < 15; ++i) {
                System.out.println(Thread.currentThread().getName() + " --> " + i);
            }
            flag = 1;

            c1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    
    public static void main(String[] args)
    {
        ConditionDemo conditionDemo = new ConditionDemo();
        new Thread(()->{for (int i = 0; i < 5 ; ++i){conditionDemo.printFirst();}},"A").start();
        new Thread(()->{for (int i = 0; i < 5 ; ++i){conditionDemo.printSecond();}},"B").start();
        new Thread(()->{for (int i = 0; i < 5 ; ++i){conditionDemo.printThird();}},"C").start();
    }
}

7 线程死锁

高级:

公平和非公平锁

在ReentrantLock()中,默认使用是非公平锁
公平锁:十分公平,可以先来后到
比如进程A 3h执行完 线程B 3s 执行完 线程B就必须等线程A
非公平锁:可以插队
java并发编程及JUC工具包学习笔记_第5张图片

Synchronized和Lock区别

1 Synchronized 内置关键字 Lock是一个Java类
2 Synchronized 无法判断锁的状态 Lock可以判断是否获得了锁
3 Synchronized会自动释放锁 Lock 必须要手动释放
4 Lock可以使用lock.tryLock()方法,使得在未获得锁时,线程不会像Synchronized阻塞
5 Synchronized 可重入锁 、不可以中断、非公平;Lock 可重入锁 可以判断锁 默认是非公平但可以手动设置
6 Synchronized可以锁同步方法和同步代码块,Lock只能锁同步代码块

五 集合类的线程安全

基础

来看个线程不安全的例子

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 1; i < 100; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list);
            }).start();
        }
    }

不光出现了数据不一致,列表中含有NULL,还发生了java.util.ConcurrentModificationException异常
java并发编程及JUC工具包学习笔记_第6张图片
如果改成Vector可以解决这个问题,因为在它的add方法中加了synchronized 但不推荐使用

public synchronized boolean add(E e)

还用种方法就是使用Collections将它从不安全的转成安全的。但也不推荐

List<String> list = Collections.synchronizedList(new ArrayList<>());

还可以使用JUC包下的类

List<String> list = new CopyOnWriteArrayList();

再来看HashSet

其底层为hashmap
    public HashSet() {
        map = new HashMap<>();
    }
    添加时,其value放的是一个Object常量
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
// Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

高级

深入探讨CopyOnWriteArrayList

这是它的add方法的底层实现,可以看到本质上还是用Arrays.copyOf数组拷贝 每次扩容1个元素放在末尾
即它是一种写时复制的容器从而做到可以并发的读操作而不需要加锁
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

ConcurrentModificationException

ConcurrentModificationException异常并不是在修改的时候会抛出的,而是在调用迭代器遍历集合的时候才会抛出。

而集合类的大部分toString方法,都是使用迭代器遍历的。所以如果多线程修改集合后, 接着就遍历集合,那么很有可能会抛出ConcurrentModificationException。

在ArrayList,HashMap等非线程安全的集合内部都有一个modCount变量, 这个变量是在集合被修改时(删除,新增),都会被修改。

如果是多线程对同一个集合做出修改操作,就可能会造成modCount与实际的操作次数不符, 那么最终在调用集合的迭代方法时,modCount与预期expectedModeCount比较, expectedModCount是在迭代器初始化时使用modCount赋值的, 如果发现modCount与expectedModeCount不一致,就说明在使用迭代器遍历集合期间, 有其他线程对集合进行了修改,所以就会抛出ConcurrentModificationException异常。

深入探讨CopyOnWriteHashMap

------------下面待整理--------------------

优雅地停止线程

在JDK里提供的下面这些方法都被废除了,这些方法有可能会导致死锁
public final void stop()
public void destroy()
public final void suspend()
public final void resume()

守护线程

public final void setDaemon(boolean on)
public final boolean isDaemon()

实例
    注意守护线程执行次数大于用户线程
    当用户线程执行完毕后,守护线程也结束了
    public static void main(String[] args) throws Exception {
        Thread user = new Thread(()->{
            for(int x = 0;x < 10;x++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + x);
            }
        },"user");
        Thread daemon = new Thread(()->{
            for(int x = 0;x < 1000;x++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        },"daemon");
        daemon.setDaemon(true);
        user.start();
        daemon.start();
    }

JVM里,最大的线程就是GC线程,程序执行中,GC一直存在

volatile关键字

正常流程对属性的操作为,先将属性拷贝到工作内存,然后再去处理,最后再重新拷回内存中
而该关键字表示对属性直接进行内存的处理操作
注意它并不是同步属性
java并发编程及JUC工具包学习笔记_第7张图片

A B线程都用到一个变量,A线程中对该该变量从主内存中做一份拷贝,拷贝到自己的线程缓存区中,然后再操作,操作完成后再写入。这样如果B线程修改了值,将其从主内存中拷贝到自己的工作内存再写会到主内存后,A可能还是读的自己工作内存中的值
使用volatile关键字,在运行过程中可以强制所有线程都去堆内存中渎值
它只能保证可见性,禁止重排序,但不保证同步性,即不能替代synchronized
public class Hello {

//    volatile boolean running = true;
    boolean running = true;
    void m(){
        System.out.println("start");
        while(running){

        }
        System.out.println("m end");
    }

    public static void main(String[] args) {
        Hello thread = new Hello();
        new Thread(thread::m,"t1").start();

        try{
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.running = false;
        System.out.println(thread.running);
    }
}    

CountDownLatch

当一个或多个线程调用await方法时,这些线程会阻塞,其他线程调用countDown方法会将计数器减1(调用该方法的线程不会阻塞),当其计数器变为0时,因await方法阻塞的线程会被唤醒
实例
必须在6人离开后才能接着进行下面的操作
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for(int i = 1;i <= 6;i++){
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"离开");
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName()+"最后走");
    }

CyclicBarrier

实例
   public static void main(String[] args){
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙");
        });
        for(int i = 1;i <=7 ;i++){
            final int tempInt = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "第"+tempInt+"颗");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

Semaphore

    public static void main(String[] args) {
        //主要用在限流
        Semaphore semaphore = new Semaphore(3);
        for(int i = 1;i <=6;i++){
            new Thread(()->{
                try {
                //acquire() 获得,如果已经满了,等待
				//release() 释放,将当前信号量+1
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢到车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+"离开");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }

ReadWriteLock

class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void put(String key, Object value) {
        //读要加锁,这样做防止在线程写未完成时就去读数据
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t 写数据");
            TimeUnit.MILLISECONDS.sleep(300);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t 写完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            readWriteLock.writeLock().unlock();
        }
    }

    public void get(String key) {
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t 读取数据");
            TimeUnit.MILLISECONDS.sleep(300);
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t 读完成" + result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            readWriteLock.readLock().unlock();
        }

    }
}

public class Hello {

    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for(int i = 1;i <=5;i++){
            final int intTemp = i;
            new Thread(()->{
                myCache.put(intTemp+"", intTemp+"");
            },String.valueOf(i)).start();
        }
        for(int i = 1;i <=5;i++){
            final int intTemp = i;
            new Thread(()->{
                myCache.get(intTemp+"");
            },String.valueOf(i)).start();
        }
    }
}    

BlockingQueue

java并发编程及JUC工具包学习笔记_第8张图片
ArrayBlockingQueue:由数组构成的有界阻塞队列
LinkedBlockingQueue: 由链表组成的有界,大小默认为Integer.MAX_VALUE 的阻塞队列
SynchronousQueue 单个元素的阻塞队列
LinkedBlockingDeque 由链表组成的双向阻塞队列

方式 抛出异常 有返回值,不抛异常 阻塞等待 超时等待
添加 add offer(e) put(e) offer(e,time,unit)
移除 remove poll take poll(time.unit)
检测队首元素 element peek - -
    public static void main(String[] args) {
        ArrayBlockingQueue<Character> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.add('a'));
        System.out.println(blockingQueue.add('b'));
        System.out.println(blockingQueue.add('c'));
        System.out.println(blockingQueue.add('d'));
    }

	//SynchronousQueue
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
        new Thread(()->{
            try{
                System.out.println(Thread.currentThread().getName() + "put 1");
                blockingQueue.put("1");
                System.out.println(Thread.currentThread().getName() + "put 2");
                blockingQueue.put("2");
                System.out.println(Thread.currentThread().getName() + "put 3");
                blockingQueue.put("3");
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        },"T1").start();
        new Thread(()->{
            try{
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName() +" " + blockingQueue.take());
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName() +" " + blockingQueue.take());
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName() +" " + blockingQueue.take());
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        },"T2").start();
    }

线程池

    public static void main(String[] args) throws InterruptedException {
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池5个受理线程
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();//一1池1个受理线程
        ExecutorService threadPool = Executors.newCachedThreadPool();//一池N线程,可扩容
        for(int i = 1;i <= 10;i++){
            threadPool.execute(()->{
                System.out.println(Thread.currentThread().getName() + "办理业务");
            });
        }
        threadPool.shutdown();
    }
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

corePoolSize:线程池中的常驻核心线程数
maximumPoolSize:线程池中能够容纳同时执行的最大线程数,该值>=1
keepAliveTime:多余的空闲线程的存活时间,当前池中线程数量超过corePoolSize时,当空间空间达到keeyAliveTime时,多余线程会被销毁直到只剩下corePoolSize个线程为止
unit:keeyAliveTime的单位
workQueue:正在排队的任务队列
threadFactory :表示生成线程池中工作线程的线程工厂,用于创建线程
handler:拒绝策略,表示当队列满了,并且工作线程大于等于线程池的最大线程数时,如何拒绝请求执行的runnable的策略
当候客区也满时,如果此时新来了任务(6 7 8),且可以扩容窗口,那么就将候客区的任务放到扩容窗口中,新来的任务放到候客区中。当扩容窗口也满时,就会根据拒绝策略去对新来的任务执行相应操作。
java并发编程及JUC工具包学习笔记_第9张图片
阿里 一个都不用

java并发编程及JUC工具包学习笔记_第10张图片

java并发编程及JUC工具包学习笔记_第11张图片

    public static void main(String[] args) throws InterruptedException {
        ExecutorService threadPool = new ThreadPoolExecutor(2,
                5,
                2L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        //这里大于8后 就会报Exception in thread "main" java.util.concurrent.RejectedExecutionException:
        for(int i = 1;i <= 8;i++){
            threadPool.execute(()->{
                System.out.println(Thread.currentThread().getName() + "办理业务");
            });
        }
        threadPool.shutdown();
    }

异常策略:
AbortPolicy 默认 直接抛出异常
CallerRunsPolicy 不会抛弃任务,而是将任务返回给调用者
DisCardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
DisCardPolicy 该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种策略

maximumPoolSize设置值的参考:
如果是CPU密集型
可以将maximumPoolSize定为CPU核数,从而减少上下文切换
CPU核数通过下面的代码获取

Runtime.getRuntime().availableProcessors()

如果是IO密集型
判断程序中十分耗IO的线程有多少个,只需大于这个数即可

ForkJoin

class MyTask extends  RecursiveTask<Integer>{

    private static final Integer ADJUST_VALUE = 10;

    private int begin;
    private int end;
    private int result;

    public MyTask(int begin,int end){
        this.begin = begin;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        if((end - begin) <= ADJUST_VALUE){
            for(int i = begin;i <=end;i++){
                result = result + i;
            }
        }else{
            int middle = begin + (end - begin) / 2;
            MyTask task01 = new MyTask(begin,middle);
            MyTask task02 = new MyTask(middle+1,end);
            task01.fork();
            task02.fork();
            result = task01.join() + task02.join();
        }
        return result;
    }
}

public class Hello {


    public static void main(String[] args) throws InterruptedException, ExecutionException {
        MyTask myTask = new MyTask(0,1000);
        ForkJoinPool threadPool = new ForkJoinPool();
        ForkJoinTask<Integer> forkJoinTask = threadPool.submit(myTask);
        System.out.println(forkJoinTask.get());
        threadPool.shutdown();
    }
}

异步回调

    public static void main(String[] args) throws InterruptedException, ExecutionException {
//        CompletableFuture completableFuture1 = CompletableFuture.runAsync(()->{
//            System.out.println(Thread.currentThread().getName() + "没有返回值的");
//        });
//        completableFuture1.get();
        CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName());
            int a = 10/0;
            return 200;
        });

        int result = completableFuture2.whenComplete((t,u)->{
            System.out.println("t:" + t+" u:"+u);
        }).exceptionally(f -> {
            System.out.println("exception"+f.getMessage());
            return 500;
        }).get();
        System.out.println("result "+ result);
    }

Synchronized

class Test{
    private static int count = 10;

    public synchronized static void m(){
        count --;
        System.out.println(Thread.currentThread().getName() + "count ="+count);
    }

    //上下是等价的,这里锁的是类而不是当前对象
    public static void mm(){
        //考虑一下这里是否可以改为synchronized(this)?
        //静态的方法、属性,不需要new对象就能访问,没有this引用的存在
        //锁的为Class对象
        synchronized (Test.class){
            count --;
        }
    }
}

程序在执行过程中,如果出现异常,默认情况锁会释放
所以,在并发处理的过程中,有异常要多加小心,不然可能出现不一致情况

class Test{
    private int count = 0;

    synchronized void m(){
        System.out.println(Thread.currentThread().getName() + "start");
        while(true){
            count ++;
            System.out.println(Thread.currentThread().getName() + "count" + count);
            try{
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(count == 5){
                int i = 10/0;
            }
        }
    }
}

public class Hello {


    public static void main(String[] args) {
        Test t = new Test();
        new Thread(t::m,"t1").start();
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(t::m,"t2").start();
    }
}    

锁对象发生了改变的情况

public class Hello {

    Object o = new Object();
    void m(){
        synchronized (o){
            while(true){
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        }
    }

    public static void main(String[] args) {
        Hello thread = new Hello();
        new Thread(thread::m,"t1").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread t2 = new Thread(thread::m,"t2");
        thread.o = new Object();
        t2.start();
    }
}    

不要以字符串常量作为锁定对象

public class Hello {

    // m1 和 m2锁的为同一个对象
    //比如用到了一个类库,在该类库中锁定了字符串 在没有读到源码的情况下,在自己的代码中也锁定了相同的字符串,这时就会发生死锁现象
    //因为程序和类库使用了同一把锁
    String s1 = "Hello";
    String s2 = "Hello";

    void m1(){
        synchronized (s1){

        }
    }

    void m2(){
        synchronized (s2){

        }
    }
}    

Atomic


public class Hello {

    AtomicInteger count = new AtomicInteger(0);
    void m(){
        for(int i = 0;i < 10000;i++){
            count.incrementAndGet();
        }
    }

    public static void main(String[] args) {
        Hello thread = new Hello();
        List<Thread> threads = new ArrayList<>();
        for(int i = 0;i < 10;i++){
            threads.add(new Thread(thread::m,"thread"+i));
        }
        threads.forEach(o -> o.start());
        threads.forEach(o ->{
            try {
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(thread.count);
    }
}    

应用

写两个线程,线程1添加10个元素到容器中,线程2实时监控元素的个数,当容器中元素个数达到5时,线程2给出提示并立即结束
解法一: 线程2轮询且将容器加volatile修饰

public class MyContainer {

	// 主要容器,设为volatile保证线程间可见性
    private volatile List<Object> list = new ArrayList<>();	

    public void add(Object ele) {
        list.add(ele);
    }

    public int size() {
        return list.size();
    }

    public static void main(String[] args) {

        MyContainer container = new MyContainer();

        // 线程1,每隔一秒向容器中添加一个元素
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                container.add(new Object());
                System.out.println("add " + i);
                // 每隔一秒添加一个元素
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
            }
        }, "线程1").start();

        // 线程2,轮询容器内元素个数
        new Thread(() -> {
            while (true) {
                if (container.size() == 5) {
                    break;
                }
            }
            System.out.println("监测到容器长度为5,线程2立即退出");
        }, "线程2").start();
    }
}


不够精确: 若当container.size == 5还未执行break时,被其他线程抢占;或container.add()之后还未打印,就被线程2抢占并判断到container.size == 5并退出了.
损耗性能: 线程2一直在走while(true)循环,浪费性能
解法二: 使用wait/notify机制,当线程1写入5个元素后通知线程2

public class MyContainer {

    // 主要容器,因为只有线程1对其进行修改和查询操作,所以不用加volatile关键字
    private List<Object> list = new ArrayList<>();

    public void add(Object ele) {
        list.add(ele);
    }

    public int size() {
        return list.size();
    }

    public static void main(String[] args) {

        MyContainer container = new MyContainer();

        final Object lock = new Object();    // 锁对象

        // 线程2先启动并进入wait状态,等待被线程1唤醒
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程2启动");
                if (container.size() != 5) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("监测到容器长度为5,线程2立即退出");
                // 线程1唤醒线程2后立刻睡眠了,因此线程2退出前要再次唤醒线程1
                lock.notify();
            }
        }, "线程2").start();

        // 主线程睡2秒钟再创建线程1,确保线程2先得到锁
		try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 线程1,每隔一秒向容器中添加一个元素
        new Thread(() -> {
            synchronized (lock) {
                for (int i = 0; i < 10; i++) {
                    container.add(new Object());
                    System.out.println("add " + i);
                    // 当容器中元素个数达到5时,唤醒线程2并退出线程1
                    if (container.size() == 5) {
                        lock.notify();
                        // notify()方法不会释放锁,因此即使通知了线程2,也不能让线程2立刻执行
                        // 所以要先将线程1 wait()住,让其释放锁给线程2,等待线程2退出前再通知唤醒线程1
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 每隔一秒添加一个元素
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "线程1").start();
    }
}

分析程序中锁移交的顺序,锁再线程1和线程2之间进行接力:

先启动线程2并使主线程睡2秒以确保线程2先抢到锁.
线程2抢到锁后调用wait(),让其释放锁并阻塞,以确保线程1获得锁.
线程1抢到锁后开始向容器内添加元素.当线程1添加了5个元素后调用notify()通知线程2并调用wait()释放锁并阻塞,以确保线程2获得锁.
线程2抢到锁后输出语句并退出,退出之前调用notify()唤醒线程1,因为线程2退出后会释放锁,因此这时不用调用wait()释放锁.
通过上边分析我们看到: 当不涉及同步,只涉及线程通信的时候,用synchronized+wait/notify机制就显得太重了,实际编程中常用封装层次更深的类库实现线程间通信.
解法三: 使用门闩锁CountDownLatch类锁住线程2,并等待线程1撤去门闩释放线程2

public class MyContainer {

    // 主要容器,因为门闩锁只是一种同步方式,不保证可见性,因此需要用volatile修饰
    private volatile List<Object> list = new ArrayList<>();

    public void add(Object ele) {
        list.add(ele);
    }

    public int size() {
        return list.size();
    }

    public static void main(String[] args) {

        MyContainer container = new MyContainer();

        // 门闩锁,构造函数中传入门闩数,使用其countDown()方法撤掉一条门闩
        // 当门闩数为0时,门会打开,两个线程都会被执行
        CountDownLatch latch = new CountDownLatch(1);

        // 线程2先启动并调用await()让其被门闩锁锁住
        new Thread(() -> {
            System.out.println("线程2启动");
            if (container.size() != 5) {
                try {
                    // 让线程被门闩锁锁住,等待门闩的开放,而不是进入等待队列
                    latch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("监测到容器长度为5,线程2立即退出");
        }, "线程2").start();

        // 主线程睡2秒钟再创建线程1,确保线程2先得到锁
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 线程1,每隔一秒向容器中添加一个元素
        new Thread(() -> {
            System.out.println("线程1 启动");
            for (int i = 0; i < 10; i++) {
                container.add(new Object());
                System.out.println("add " + i);
                // 当容器中元素个数达到5时,撤去一个门闩,打开门闩锁,两个线程都会被执行
                if (container.size() == 5) {
                    latch.countDown();
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程1").start();
    }
}

门闩锁CountDownLatch在框架中使用的非常广泛,如在Spring框架中,要先实例化所有Properties和Service对象后才能实例化Bean对象.因此我们给初始化Bean对象的线程上一个两道门闩的门闩锁,初始化完毕所有Properties对象后撤去一道门闩,初始化完毕所有Service对象后再撤去一道门闩,两道门闩撤去后,门闩锁打开,创建Bean的线程开始执行.

你可能感兴趣的:(java)