多线程详解&JUC

在这里插入图片描述

线程的基本知识

1. 线程的介绍

1.1 简介

线程是进程中的一个实体,线程本身是不会独立存在的。进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程的一个执行路径,一个进程中至少有一个线程,进程中多个线程共享进程的资源。

操作系统在分配资源时是把资源分配给进程的, 但是 CPU 资源比较特殊。它是被分配到线程的,因为真正要占用CPU运行的是线程,所以也说线程是 CPU 分配的基本单位。

1.2 多线程的优劣势

  • 优势:可以提高CPU的利用率。(在多线程程序中,其中一个线程必须等待时,CPU可以运行其他的线程而不用等待。可以提高CPU的利用率)
  • 劣势:
    • 线程也是程序,所以线程需要占用内存,线程越多占用内存越多。
    • 多线程间需要协调管理,所以需要CPU时间跟踪线程,线程之间会对共享资源的访问相互影响,必须解决竞用共享资源的问题(共享资源的线程安全问题);

1.3 线程的调度算法

java中多线程用到的两种线程调度模型:分时调度模型和抢占式调度模型;

  • 分时调度模型是指让所有的线程轮流获取CPU时间片,并且平均分配每个线程占用的CPU时间片

  • Java虚拟机采用的是抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU时间片。

1.4 并发、并行

  • 并发:单核CPU同时处理多件事情;
  • 并行:同一时间,多核CPU处理多个事情。
1.4.1 并发编程的三大特性
  1. 原子性
  2. 可见性
  3. 有序性
1.4.1.1 原子性:

什么是原子操作:指不可被中断的操作,要么执行,要么不执行。(基本数据类型的变量的赋值和读取都是原子性操作)

自增操作不具有原子性

  • x = 10; //语句1
  • y = x; //语句2
  • x++; //语句3
  • x = x + 1; //语句4

语句1 是原子操作,语句2 不是。(读取x的数据, x的数据写给y),语句3不是(读取x的值,操作x的值,写入新的值)

语句4:不是(读取x的值,操作x的值,写入新的值)

1.4.1.2 可见性:

可见性:是指形成之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果,另一个线程马上就能看到。

当一个共享变量被 volatile修饰时,他会保证修改的值立即被更新到内存中,所以对其他线程是可见的,当有其他线程需要读取时,他会去内存中读取新值。而普通的共享变量不能保证可见性,因为普通的共享变量被修改后,什么时候写入内存不确定。当有其他线程去读取时,此时内存中可能还是原来的旧值。因此无法保证可见性。

1.4.1.3 有序性:

java 的内存模型中,允许编译器和处理器对指令进行重排序。但是重排序过程不影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

多线程并发执行下可以通过volatile 关键字来保证一定的有序性。另外可以通过 synchronizelock 来保证有序性。

1.4.2 并发编程带来的问题:
  1. 线程切换带来的原子性问题
  2. 缓存导致的可见性问题
  3. 编译优化带来的有序性问题

解决方案:

  1. JDK Atomic原子类,synchronizedlock 解决原子性问题

  2. synchronizedvolitilelock 解决可见性问题

  3. Happens-before 规则 解决有序性问题

    因为jvm会对代码进行编译优化,指令会出现重排序的情况,为了避免编译优化对并发编程安全性的影响,需要happens-before规则定义一些禁止编译优化的场景,保证并发编程的正确性。

1.5 线程的同步、异步、线程互斥

  • 线程同步:当一个线程对共享数据进行操作时,不允许其他的线程打断他。(需要等待结果的返回)
  • 线程异步:多个线程之间运行,互不影响;(不需要等待结果的返回)
  • 线程互斥:线程互斥是一种特殊情况下的线程同步。共享资源只能被一个线程使用。(当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其他要使用该资源的线程必须等待,直到占用资源者释放资源)

实现线程同步的方式:

  • synchronized 关键字;
  • Lock锁实现同步;

2. 线程中几种状态(线程的生命周期)

线程的状态变迁

  1. 新建状态(New):新建了一个线程对象
  2. 就绪状态(Runnable):线程对象创建成功,之后,调用了该对象的start()方法,该线程就处于就绪状态(可运行状态)。当该线程成功抢夺到CPU时间片时,线程就处于运行状态(Running);
  3. 阻塞状态(Blocked):线程由于某种原因放弃了CPU使用权,暂停运行。
  4. 等待状态(WAITING):线程进入等待状态(通知或中断方法)
  5. 超时等待状态(TIME_WAITING):不同于WAITING,他是可以在指定的时间自行返回的。
  6. 终止状态(TERMINATED):线程执行完毕;

Java将操作系统中的运行和就绪两个状态合并称为运行状态。

阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在 Lock接口的线程状态却是等待状态,因为Lock接口对于阻塞的实现使用的是 LockSupport类的相关方法;

2.1 阻塞的三种情况

  1. 等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()notifyAll()方法才能被唤醒。
  2. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
  3. 其他阻塞:运行的线程执行sleep()join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

需要注意的是:wait()方式会释放占用的资源,其中包括锁资源;

3. 线程的创建和运行

Java中有三种创建线程的方式,分别是继承Thread类并重写run(),实现Runnable接口的run(),或者使用FutureTask(实现Callable接口)方式;

3.1 继承Thread

package com.wddong.Thread;

public class MyThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        //启动线程
        myThread.start();
        //当前线程(main-thread)等待,myThread线程执行完成;
        myThread.join();
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("MyThread running");
    }
}

3.2 实现Runnable接口

package com.wddong.Thread;

public class MyRunnableDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new MyRunnable());
        //启动线程
        thread.start();
        //当前线程(main-thread)等待,myThread线程执行完成;
        thread.join();
    }
}


class MyRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println("MyRunnable running");
    }
}

3.3 Future(Callable接口)方式

package com.wddong.Thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyFutureDemo {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        FutureTask<String> future = new FutureTask<>(new MyCallable());
        Thread thread = new Thread(future);
        //启动线程
        thread.start();
        //获取返回值
        String returnString = future.get();
        //打印返回值
        System.out.println(returnString);
        //当前线程(main-thread)等待,myThread线程执行完成;
        thread.join();
    }
}
/**
 * 实现Callable接口和实现Runnable接口的差异就是Callable接口具有返回值;
 * 泛型<String>:返回值类型为String;
 */
class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("MyFurure running");
        return "callable";
    }
}

3.4 不同方式创建线程的优缺点

继承Thread

  • 优:在run()获取当前线程直接使用 this 就可以了,无需使用 Thread. currentThread()方法
  • 缺:
    • Java不支持多继承,如果继承了 Thread 类,那么就不能再继承其他类。
    • 任务没有返回值

实现Runnable

  • 优:Runnable是接口,可以多实现;
  • 缺:没有返回值

Future

  • 优:

    • 有返回值
    • 可以多实现

4. 线程的通知和等待

4.1 wait()函数

当一个线程调用一个共享变量wait()方法时, 该调用线程会被阻塞挂起,直到发生下面几件事情中之一才返回:

  1. 线程调用了该共享对象notify()或者notifyAll()方法

  2. 其他线程调用了该线程interrupt()方法使该线程抛出InterruptedException异常返回。

另外需要注意的是,如果调用wait(),线程没有事先获取该对象的监视器锁,则调用wait()方法时调用线程会抛出 IllegalMonitorStateException 异常。

wait系列方法:

  1. wait(long timeout)

    不同之处在于,如果一个线程调用共享对象的该方法挂起后,没有在指定的timeout ms内被其它线程调用该共享变量的notify()或者notifyAll()方法唤醒,那么该函数还是会因为超时而返回。如果将timeout设置为0则和wait() 方法效果一样,因为在wait() 方法内部就是调用了wait(0) 需要注意的是,如果在调用该函数时,传递了一个负的timeout会抛出IllegumentException 异常;

  2. wait(long timeout, int nanos)

    在其内部调用的是wait(long timeout)函数;

    public final void wait(long timeout,int nanos) throws InterruptedException{
        if (timeout < 0){
            throw new IllegalArgumentException("timeout value is negative");
        }
        if(nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException("nanosecond timeout value out of range");
        }
        if (nacos > 0) {
            timeout ++;
        }
        wait(timeout);
    }
    
4.1.1 共享变量的监视器锁

上面提到,线程没有事先获取该对象的监视器锁,则调用wait()方法时调用线程会抛出 IllegalMonitorStateException 异常。那么

一个线程如何获取共享变量的监视器锁呢?

  1. 执行synchronized同步代码块,使用该共享变量作为参数

    synchronized (共享变量){
    //doSomething
    }
    
  2. 调用了该共享变量的方法,并且该方法被synchronized关键字修饰

    synchronized void add nt a , int b) { 
    //doSomething
    }
    

4.2 notify()/notifyAll()

4.2.1 notify()

一个线程调用共享对象的notify()方法后,会唤醒一个在该共享变量上调用wait() 系列方法后被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的。此外,被唤醒的线程不能马上从wait()方法返回并继续执行,它必须在获取了共享对象的监视器锁后才可以返回。也就是唤醒它的线程释放了共享变量上的监视器锁后,被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起竞争该锁。 只有该线程竞争到了共享变量的监视器锁后才可以继续执行。

类似wait()系列方法,只有当前线程获取到了共享变量的监视器锁后,才可以调用共享变量的notify()方法,否则会抛出IllegalMonitorStateException异常。

4.2.2 notifyAll()

在共享变量上调用notify()函数会唤醒被阻塞到该共享变量上某一个随机线程,notifyAll()方法则会唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程。

4.2.3 代码示例
package com.wddong.Thread;

public class WaitAndNotifyDemo {
    private static volatile Object shareResourceA = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(() -> {
            //获取shareResourceA共享资源的监视器锁
            synchronized (shareResourceA) {
                System.out.println("threadA get shareResourceA lock");

                try {
                    System.out.println("threadA begin wait");
                    shareResourceA.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("threadA end wait");
            }
        }, "threadA");
        Thread threadB = new Thread(() -> {
            //获取shareResourceA共享资源的监视器锁
            synchronized (shareResourceA) {
                System.out.println("threadB get shareResourceA lock");

                try {
                    System.out.println("threadB begin wait");
                    shareResourceA.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("threadB end wait");
            }
        }, "threadB");
        Thread threadC = new Thread(() -> {
            synchronized (shareResourceA) {
                System.out.println("threadC begin notify ");
                shareResourceA.notify();
            }
        }, "threadC");

        //启动线程
        threadA.start();
        threadB.start();
        Thread.sleep(1000);
        threadC.start();
        //等待线程结束
        threadA.join();
        threadB.join();
        threadC.join();
    }
}
执行结果:
threadA get shareResourceA lock
threadA begin wait
threadB get shareResourceA lock
threadB begin wait
threadC begin notify 
threadA end wait

从代码运行的结果中,可以看出。notify只会唤醒共享资源下的随机一个线程。我们将threadC中的notify()换成notifyAll()查看运行结果

		Thread threadC = new Thread(() -> {
            synchronized (shareResourceA) {
                System.out.println("threadC begin notify ");
                shareResourceA.notifyAll();
            }
        }, "threadC");
执行结果:
threadA get shareResourceA lock
threadA begin wait
threadB get shareResourceA lock
threadB begin wait
threadC begin notify 
threadB end wait
threadA end wait

可以看到notifyAll()唤醒的是共享资源下所有的线程;

4.3 Lock锁的精确唤醒(await(),signal(),signalAll()

LOCK 对比 Synchronize 的优势在于 LOCK 能够精确唤醒。

LOCK 中的睡眠和唤醒

  • 睡眠: await()
  • 唤醒: signal(),signalAll()
#精确的通知和唤醒线程
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * A -> B -> C -> A
 * @description:
 */

public class ConditionDemo {
    public static void main(String[] args) {
        Data3 data3 = new Data3();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data3.printA();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data3.printB();
            }
        },"B").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data3.printC();
            }
        },"C").start();
    }
}

class Data3{
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int num = 1; // 1A 2B 3C

    public void printA(){
        lock.lock();
        try {
            while (num != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+" Im A ");
            num = 2;
            //唤醒2
            condition2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB(){
        lock.lock();
        try {
            while (num != 2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+" Im B ");
            num = 3;
            //唤醒3
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC(){
        lock.lock();
        try {
            while (num != 3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+" Im C ");
            num = 1;
            // 唤醒1
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

4.4 生产者/消费者

package com.wddong.Thread.ProdAndCustom;

//奶箱类
public class Box {
    //定义奶箱中牛奶的数量
    private int milkCount = 0;
    //奶箱中最大存放的牛奶数
    private volatile static int MAX_MILK_COUNT = 3;

    //放入牛奶
    public synchronized void putMilk() throws InterruptedException {
        //存放牛奶到达上限。等待消费
        if (milkCount == MAX_MILK_COUNT){
            System.out.println("牛奶过多,等待消费");
            wait();
        }

        System.out.println(String.format("奶箱中共有%d瓶牛奶,放入一瓶,还剩%d瓶",milkCount,++milkCount));
        //通知消费
        notify();
    }
    //取出牛奶
    public synchronized void getMilk() throws InterruptedException {
        //如果没有牛奶,等待
        if (milkCount==0) {
            System.out.println("牛奶过少,等待放入");
            wait();
        }

        //有牛奶直接消费
        System.out.println(String.format("奶箱中共有%d瓶牛奶,取出一瓶,还剩%d瓶",milkCount,--milkCount));
        //消费完,通知生产
        notify();
    }
}


package com.wddong.Thread.ProdAndCustom;

//生产者、消费者
public class ProdAndCustomDemo {

    public static void main(String[] args) throws InterruptedException {
        Box box = new Box();

        //生产者线程
        Thread prod = new Thread(() -> {
            for (int i = 0; i <10 ; i++) {
                try {
                    box.putMilk();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "prod");
        //消费者线程
        Thread custom = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    box.getMilk();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "custom");

        prod.start();
        Thread.sleep(1000);
        custom.start();

        prod.join();
        custom.join();

        System.out.println("main thread run end");

    }
}
//执行结果:
奶箱中共有0瓶牛奶,放入一瓶,还剩1瓶
奶箱中共有1瓶牛奶,放入一瓶,还剩2瓶
奶箱中共有2瓶牛奶,放入一瓶,还剩3瓶
牛奶过多,等待消费
奶箱中共有3瓶牛奶,取出一瓶,还剩2瓶
奶箱中共有2瓶牛奶,取出一瓶,还剩1瓶
奶箱中共有1瓶牛奶,取出一瓶,还剩0瓶
牛奶过少,等待放入
奶箱中共有0瓶牛奶,放入一瓶,还剩1瓶
奶箱中共有1瓶牛奶,放入一瓶,还剩2瓶
奶箱中共有2瓶牛奶,放入一瓶,还剩3瓶
牛奶过多,等待消费
奶箱中共有3瓶牛奶,取出一瓶,还剩2瓶
奶箱中共有2瓶牛奶,取出一瓶,还剩1瓶
奶箱中共有1瓶牛奶,取出一瓶,还剩0瓶
牛奶过少,等待放入
奶箱中共有0瓶牛奶,放入一瓶,还剩1瓶
奶箱中共有1瓶牛奶,放入一瓶,还剩2瓶
奶箱中共有2瓶牛奶,放入一瓶,还剩3瓶
牛奶过多,等待消费
奶箱中共有3瓶牛奶,取出一瓶,还剩2瓶
奶箱中共有2瓶牛奶,取出一瓶,还剩1瓶
奶箱中共有1瓶牛奶,取出一瓶,还剩0瓶
牛奶过少,等待放入
奶箱中共有0瓶牛奶,放入一瓶,还剩1瓶
奶箱中共有1瓶牛奶,取出一瓶,还剩0瓶
main thread run end

4.5 虚假唤醒

虚假唤醒问题:一个线程从挂起状态变为可运行状态,但是该线程并未被其他线程调用notify()、notifyAll()进行通知,或者被中断,或者等待超时,这就是所谓的虚假唤醒;

/**
 * 使用了四个线程来模拟虚假唤醒问题
 * A,C 线程是增加
 * B,D线程是减少
 * wait()方法会释放锁
 * A 线程 调用wait()会释放锁。 所以C 线程也可以拿到锁 进入代码块,此时A,C 中的 num = 0;notifyAll()唤醒后,
 * 会同时进行 num++ 会造成数量未按要求变化 num = 2
 * 解决虚假唤醒问题:需要将 if 判断修改为 while 判断即可。
 */
//生产者、消费者
public class ProdAndCustomDemo {

    public static void main(String[] args) throws InterruptedException {
        Box box = new Box();

        //生产者线程
        Thread A = new Thread(() -> {
            for (int i = 0; i <100 ; i++) {
                try {
                    box.putMilk();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A");
        //消费者线程
        Thread B = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                try {
                    box.getMilk();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B");
        //生产者线程
        Thread C = new Thread(() -> {
            for (int i = 0; i <100 ; i++) {
                try {
                    box.putMilk();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C");
        //消费者线程
        Thread D = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                try {
                    box.getMilk();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D");

        A.start();
        Thread.sleep(1000);
        B.start();

        C.start();
        Thread.sleep(1000);
        D.start();

        A.join();
        B.join();
        C.join();
        D.join();

        System.out.println("main thread run end");
    }
}
执行结果:
..........
奶箱中共有14瓶牛奶,取出一瓶,还剩13瓶
奶箱中共有13瓶牛奶,取出一瓶,还剩12瓶
奶箱中共有12瓶牛奶,取出一瓶,还剩11.....
奶箱中共有2瓶牛奶,取出一瓶,还剩1瓶
奶箱中共有1瓶牛奶,取出一瓶,还剩0瓶
main thread run end

可以看到,我们在之前的Box设置了奶瓶上限最多为3瓶,但是由于虚假唤醒导致,输出的结果有问题。解决方案就是将Boxif 判断修改为 while 判断即可。


//奶箱类
public class Box {
    //定义奶箱中牛奶的数量
    private int milkCount = 0;
    //奶箱中最大存放的牛奶数
    private volatile static int MAX_MILK_COUNT = 3;

    //放入牛奶
    public synchronized void putMilk() throws InterruptedException {
        //存放牛奶到达上限。等待消费
        while (milkCount == MAX_MILK_COUNT){
            System.out.println("牛奶过多,等待消费");
            wait();
        }

        System.out.println(String.format("奶箱中共有%d瓶牛奶,放入一瓶,还剩%d瓶",milkCount,++milkCount));
        //通知消费
        notify();
    }
    //取出牛奶
    public synchronized void getMilk() throws InterruptedException {
        //如果没有牛奶,等待
        while (milkCount==0) {
            System.out.println("牛奶过少,等待放入");
            wait();
        }

        //有牛奶直接消费
        System.out.println(String.format("奶箱中共有%d瓶牛奶,取出一瓶,还剩%d瓶",milkCount,--milkCount));
        //消费完,通知生产
        notify();
    }
}

5. 线程中的常见方法

5.1 等待线程停止 join()

public class JoinDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("threadOne over");
            }
        });
        threadOne.start();
        System.out.println("wait threadOne over");
        //下方的代码是 在main线程中执行的 threadOne.join(); 所以是 main 线程等待 threadOne线程执行完毕;
        threadOne.join();
    }
}

5.2 线程休眠Sleep()

Thread类中有静态的sleep 方法,当一个执行中的线程调用了Thread.sleep()方法后,调用线程会暂时让出指定时间的执行权,也就是在这期间不参与 CPU 的调度,但是该线程所拥有的监视器资源,比如锁还是持有不让出的。指定的睡眠时间到了后,该函数会正常返回,线程处于就绪状态。参与CPU的调度。当重新获取到CPU时间片资源后就可以继续运行了;

package com.wddong.Thread;

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

//线程Sleep期间不释放监视器资源
public class SleepDemo {
    private static final Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        //创建线程A
        Thread threadA = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("child threadA is in sleep");
                Thread.sleep(10000);
                System.out.println("child threadA is in awaked");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }, "threadA");
        //创建线程A
        Thread threadB = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("child threadB is in sleep");
                Thread.sleep(10000);
                System.out.println("child threadB is in awaked");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "threadB");
        
        //启动线程
        threadA.start();
        threadB.start();
        
        threadA.join();
        threadB.join();
    }
}

程序运行结果:

child threadA is in sleep

child threadA is in awaked

child threadB is in sleep
child threadB is in awaked

从程序运行结果中可以看出,线程在 sleep()期间是不释放锁资源的;

5.3 执行权 yield()

Thread类中的静态方法 yield(),当一个线程调用yield()方法时,就是告诉线程调度器当前线程可以让出自己的CPU时间片。

我们知道操作系统是为每个线程分配一个时间片来占有CPU的,正常情况下当一个线程将分配给自己的时间片使用完后,线程调度器才会进行下一轮的线程调度,而当一个线程调用了Thread类的静态方法 yield()时,是在告诉线程调度器自己占有的时间片中还没有使用完的部分自己不想使用了,这暗示线程调度器现在就可以进行下一轮的线程调度。

当一 线程调用 yield()方法时, 当前线程会让出CPU使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出CPU的那个线程来获取CPU行权。

public class YieldDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    //告知线程调度器,让出CPU时间片;threadlOne 线程中执行的,所以是 threadOne线程让出;
                    Thread.yield();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("threadOne over");
            }
        });
        threadOne.start();
        System.out.println("wait threadOne over");
    }
}

5.4 守护线程Daemon()

Java中的线程分为两类,分别为daemon线程(守护线程)user 线程(用户线程)JVM启动会调用main函数,main 数所在的线程就是一个用户线程,其实在 JVM内部同时还启动了好多守护线程,比如垃圾回收线程

那么守护线程和用户线程有什么区别呢?区别之一是当最后一个非守护线程结束时,JVM正常退出,而不管当前是否还有守护线程,也就是说守护线程是否结束并不影响JVM退出。

public class DaemonDemo {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(() -> {
            System.out.println("daemon thread ");
        });
        //设置为守护线程
        daemonThread.setDaemon(true);
        daemonThread.start();
    }
}

5.5 wait()sleep() 的比较

  1. wait()Object 类的方法, sleep() 是线程类 Thread 的静态方法
  2. wait() 不需要捕获异常 ,sleep() 需要捕获异常
  3. wait() 会释放锁,sleep()不会释放锁wait()方法需要notify(),notifyAll()唤醒
  4. wait() 的调用位置只能在同步代码块中sleep() 可以在任何地方进行调用

6. 线程中断

Java中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止线程,而是被中断的线程通过判断中断状态自行处理;

  1. void interrupt():设置线程的中断状态为true并直接返回;

    线程A运行时,线程B可以通过调用线程Ainterrupt()方法来设置线程A的中断标志为true并直接返回。此时,仅仅只是设置了线程A的中断标志为true线程A并没有中断,会继续向下执行。

    如果线程A因为调用了wait()join()sleep()等方法而被阻塞挂起的情况下,这个时候如果线程B中调用了线程Ainterrupt()方法,线程A会在调用这些方法的地方抛出 InterruptedException异常而返回;

  2. boolean isInterrupted():检测当前线程是否被中断, 返回true返回false;

  3. boolean interrupted():检测当前线程是否被中断, 返回true返回false。与isInterrupted()方法不同的是,该方法如果发现当前线程被中断,则会清楚中断标志。且当前线程为 static方法

    public boolean isInterrupted() {
        //传递false ,说明不清除中断标志
        return this.isInterrupted(false);
    }
    public static boolean interrupted() {
        //清除中断标志
        return currentThread().isInterrupted(true);
    }
    

    static静态方法意味着interrupted()是获取当前调用线程的中断标志而不是调用interrupted()方法的实例对象中的中断标志;

    public class InterruptedDemo {
        public static void main(String[] args) {
            Thread thread = new Thread(() -> {
            });
            //中断标志
            thread.interrupted();
            Thread.interrupted();
        }
    }
    

    如上,虽然第一次调用interrupted()的对象是thread,第二次调用的对象是main线程,这两种方式调用 的对象虽然不同。但是其实获取的都是main线程的状态。

7. 线程安全、共享资源

共享资源:被多个线程访问的变量称为共享资源;

多钱程访问同一个共享变量时特别容易出现并发问题,特别是在多个线程需要对共享变量进行写入时,为了保证线程安全,一般使用者在访问共享变量时需要进行适当的同步,同步的措施 般是加锁。或者采用ThreadLocal

servlet 是线程安全的吗?
servlet 不是线程安全的。servlet 是单实例多线程的。当多个线程同时访问同一个方法,不能保证共享变量的线程安全性
Struts2action 是多实例多线程的,是线程安全的。每次请求都会new一个新的action分配给这个请求,请求完成后销毁

8. 线程死锁

死锁是指两个或两个以上的线程在争夺共享资源时,出现相互等待的情况,在无外力的情况下,会一直无法运行下去;

在这里插入图片描述

线程A持有资源2线程B持有资源1线程A等待资源1线程B等待资源2,进入了死锁状态;

死锁产生的四个条件:

  1. 互斥条件:线程对已经获取到的资源进行排他性的使用,一个资源只能被一个线程占用;(其他线程只能等待资源释放)
  2. 请求并持有:一个线程至少持有了一个资源,但又提出了新的资源请求
  3. 不可剥夺条件:线程持有的资源在释放前,不能被别的线程抢占;
  4. 环路等待:发生死锁时,存在一个环路链;(即线程集合{T0, T1 T2 ,…, Tn}T0正在等待一T1占用的资源T1正在等待T2用的资源,……Tn在等待己被T0占用的资源。
package com.wddong.Thread;

public class DeadLockDemo {
    private static Object resouceA = new Object();
    private static Object resouceB = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(() -> {
            synchronized (resouceA){
                System.out.println("threadA get resouceA");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("threadA waiting resouceB");
                synchronized (resouceB){
                    System.out.println("threadA get resouceB");
                }
            }
        }, "threadA");

        Thread threadB = new Thread(() -> {
            synchronized (resouceB){
                System.out.println("threadB get resouceB");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("threadB waiting resouceA");
                synchronized (resouceA){
                    System.out.println("threadB get resouceA");
                }
            }
        }, "threadB");

        threadA.start();
        threadB.start();

        threadA.join();
        threadB.join();
    }
}


8.1 死锁问题排查解决:

  1. 使用 jps -l 定位进程号

    D:\ideaworkspace\testtest\wddong\target\classes\com\wddongtt\wddong\java8>jps -l
    11492 org.jetbrains.idea.maven.server.RemoteMavenServer
    14164 org.jetbrains.jps.cmdline.Launcher
    4884 sun.tools.jps.Jps
    14680 com.wddongtt.wddong.thread.cas.DeadLockDemo
    16268 org.jetbrains.kotlin.daemon.KotlinCompileDaemon
    7020
    
  2. 使用 jstack 进程号 找到死锁问题

    D:\ideaworkspace\testtest\wddong\target\classes\com\wddongtt\wddong\java8>jstack 14680
    2022-02-13 17:39:02
    Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode):
    
    "DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x0000000003583000 nid=0x3f84 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "T2" #13 prio=5 os_prio=0 tid=0x000000001f23e800 nid=0x1338 waiting for monitor entry [0x000000001ff3e000]
       java.lang.Thread.State: BLOCKED (on object monitor)
            at com.wddongtt.wddong.thread.cas.MyThread.run(DeadLockDemo.java:36)
            - waiting to lock <0x000000076bbd7d20> (a java.lang.String)
            - locked <0x000000076bbd7d58> (a java.lang.String)
            at java.lang.Thread.run(Thread.java:748)
    
    "T1" #12 prio=5 os_prio=0 tid=0x000000001f23e000 nid=0x3ef0 waiting for monitor entry [0x000000001fe3f000]
       java.lang.Thread.State: BLOCKED (on object monitor)
            at com.wddongtt.wddong.thread.cas.MyThread.run(DeadLockDemo.java:36)
            - waiting to lock <0x000000076bbd7d58> (a java.lang.String)
            - locked <0x000000076bbd7d20> (a java.lang.String)
            at java.lang.Thread.run(Thread.java:748)
    
    

8.2 如何避免死锁

要想避免死锁,只需要破坏掉至少一个构造死锁的必要条件即可。目前只有请求并持有环路等待条件是可以被破坏的;

上面的案例,我们就可通过环路等待条件就可以避免死锁;更改申请资源的顺序。使用资源申请的有序性原则就可以避免死锁;

修正线程A线程B申请资源的顺序都为 resourceA =>resouceB;

	 Thread threadB = new Thread(() -> {
            synchronized (resouceA){
                System.out.println("threadB get resouceA");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("threadB waiting resouceB");
                synchronized (resouceB){
                    System.out.println("threadB get resouceB");
                }
            }
        }, "threadB");

线程安全

9. JMM内存模型

java的内存模型:

java 的内存模型分为主内存和线程自己独有的工作内存。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主内存进行操作。 每个线程的工作内存是私有的。

9.1 JMM 介绍

JMM:Java内存模型,Java 线程之间的通信由Java内存模型(JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见;

JMM的抽象示意图

java内存模型的抽象示意图

9.2 重排序

编译器处理器为了优化程序执行性能会对指令序列进行重排序

JMM对于编译器重排序和处理器重排序制定了一些规则,保证一致的内存可见性保证;

编译器重排序:禁止特定类型的编译器重排序

处理器重排序:要求Java编译器生成指令序列时,插入指定类型的内存屏障;

从Java源代码到最终实际执行的指令序列,会分别经历下面三个重排序:

源代码==>1. 编译器优化重排序==>2. 指令级并行重排序==>3. 内存系统重排序==>最终执行的指令序列

9.3 JMM的设计

JMM的设计过程中,包含了两个角色程序员编译器和处理器,对于程序员他们希望内存模型易于理解,易于编程。对于编译器和处理器而言,希望JMM的束缚越少越好,以便于更好的做优化。

基于双方角色提出的如此的需求:

JMM对于程序员做出了如下的保障,程序员不需要管编译器和处理器如何去实现,只需要知道编译器和处理器实现完之后会满足Happens-before规则;

JMM对于编译器和处理器的要求:在不改变(单线程和正确同步的多线程程序)的执行结果条件下,编译器和处理器怎么优化都可以;

JSR-133:Java Memory Model and Thread Specification》定义了如下happens-before规则:

  1. 程序顺序规则:一个线程中的每个操作,happens-before 于该线程中的任意后续操作
  2. 监视器锁规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁
  3. volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读;
  4. 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C
  5. start() 规则:如果线程A执行操作 ThreadB.start(),那么A线程ThreadB.start() 操作 happens-before线程B 中的任意操作
  6. join() 规则:如果线程A执行操作 ThreadB.join() 并成功返回,那么线程B中的任意操作happens-before线程AThreadB.join() 操作成功返回。

如果A happens-before BJMM 并不要求A一定要在B之前执行。JMM仅仅要求前一个操作(执行结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前;

9.4 as-if-serial

as-if-serial语义:不管怎么重排序,单线程程序的执行结果不能被改变(编译器,处理器,runtime都必须遵守 as-if-serial语义);

9.4 Happens-before 和 as-if-serial的比较

as-if-serialhappens-before的比较

  1. as-if-serial保证单线程内程序的执行结果不被改变,

    as-if-serial语义给编写单线程程序的程序员创造了一个环境:单线程程序是按程序的顺序来执行的。

  2. happens-before关系保证正确同步的多线程程序的执行结果不被改变,都是在不改变程序执行结果的前提下,尽可能的提高程序执行的并发度;

    happens-before关系给编写正确同步的多线程程序的程序员创造了一个环境:正确同步的多线程程序是按happens-before指定的顺序来执行的

10. 线程安全的方式

10.1 Synchronized 关键字

synchronized

public class SynchronizedDemo {
    public static void main(String[] args) {
        SynchronizedCase synchronizedCase = new SynchronizedCase();
//        可以任意的去指定是锁Class模板还是具体对象
//        synchronized (SynchronizedCase.class){
        synchronized (synchronizedCase){
        }
    }
}
public class SynchronizedCase{
    //synchronized修饰静态方法,锁的是Class
    public synchronized static void method1(){   
    }
    //synchronized修饰实例方法,锁的是方法的调用对象
    public synchronized void method2(){
    }
}

10.2 Lock 锁

private Lock lock = new ReentrantLock();
lock.lock();
try{}finally{
	lock.unlock();    
}

10.3 锁的差异性比较

  1. Synchronized 是内置的 java内置关键字,Lock是一个Java
  2. synchronized 可以给类,方法,代码块加锁,而Lock 只能给代码块加锁
  3. Synchronized 可以自动释放锁,Lock需要手动释放(不释放会造成死锁)

11. volatile

Java虚拟机中的提供的轻量级的同步机制

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排(通过内存屏障实现防止指令重排)

作用:

通过JIT编译器生成的汇编指令查看 volatile 进行写操作时,会多出一行Lock前缀的指令;

Lock前缀的指令在多核处理器下会引发两件事情:

  1. 将当前处理器缓存行的数据写回到系统内存;
  2. 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效;

如何使其他CPU中的该内存地址数据无效?

在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置为无效状态;当处理器需要对数据进行操作时,会重新从系统内存中获取;

查询Java代码的汇编指令需要设置JVM允许参数:-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp;如果你的jdk版本小于等于8还要在jdk里面添加Hsdis插件,将该插件目录里面的两个文件(hsdis-amd64.dll,hsdis-i386.dll)复制到 %JAVA_HOME%\jre\bin\server 下,然后运行你的Java程序,就可以看到控制台里面一堆的汇编指令代码输出了。

public class Singleton {
    private volatile static Singleton myinstance;
 
    public static Singleton getInstance() {
        if (myinstance == null) {
            synchronized (Singleton.class) {
                if (myinstance == null) {
                    myinstance = new Singleton();//对象创建过程,本质可以分文三步
                }
            }
        }
        return myinstance;
    }
 
    public static void main(String[] args) {
        Singleton.getInstance();
    }
}

不加volatile关键字时在控制台输出指令搜索myinstance可以看到如下两行

0x00000000038064dd: mov    %r10d,0x68(%rsi)
0x00000000038064e1: shr    $0x9,%rsi
0x00000000038064e5: movabs $0xf1d8000,%rax
0x00000000038064ef: movb   $0x0,(%rsi,%rax,1)  ;*putstatic myinstance
                                                ; - com.it.edu.jmm.Singleton::getInstance@24 (line 22)

加了volatile关键字后,变成下面这样了:

0x0000000003cd6edd: mov    %r10d,0x68(%rsi)
0x0000000003cd6ee1: shr    $0x9,%rsi
0x0000000003cd6ee5: movabs $0xf698000,%rax
0x0000000003cd6eef: movb   $0x0,(%rsi,%rax,1)
0x0000000003cd6ef3: lock addl $0x0,(%rsp)     ;*putstatic myinstance
                                                ; - com.it.edu.jmm.Singleton::getInstance@24 (line 22)

11.1 volatile 可见性的验证

public class Test {

    //线程1
    static boolean isFlag = false;

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

        new Thread(()->{
            while(!isFlag){
                doSomething();
            }
        },"A").start();

        TimeUnit.SECONDS.sleep(1);
        System.out.println("sleep ending");
        //main 等待线程A启动完成
        isFlag = true;
        System.out.println("main run ending");
    }

    public static void doSomething(){
        System.out.println("1");
    }

    /**
     * 
     *  
     *  当执行 doSomething 方法时,不执行打印语句时,由于 isFlag 变量的可见性,程序将会变为死循环。
     *  需要添加 volatile 关键字,保证isFlag字段的可见性
     *  
     *     public void println(String x) {
     *         synchronized (this) {
     *             print(x);
     *             newLine();
     *         }
     *     }
     * 	   当执行 doSomething 方法时,执行打印语句时
     *     当执行dosomething方法打印语句时,无volatile关键字,while循环将在执行一段时间后停止。
     */
}

线程1 运行时,会将变量的值拷贝到自己的工作内存中去, 当线程2 更改了 stop 变量的值之后,但是没有来得及写入内存。就会造成问题。

11.2 volatile 不保证原子性

public class Test1 {

    private static volatile int num = 0;
    //使用JUC下的原子包,解决volatile 不能解决原子性的问题
    private static volatile AtomicInteger numAtomic = new AtomicInteger(0);


    public static void main(String[] args) {
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                for (int j = 0; j < 100; j++) {
                    doSomething();
                }
            }).start();
        }

        //保证上方创建的线程执行完全
        while (Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println("num:"+num);
    }

    public static void doSomething(){
        num++;
    }
    
//    public static void doSomething(){
//        numAtomic.incrementAndGet();
//    }

}

11.3 volatile 防止指令重排

Volatile 可以通过内存屏障,在volatile 修饰的变量操作前后,添加相应的屏障。
在这里插入图片描述

11.4 volatileSystem.out.println 问题

volatileSystem.out.println 问题

12. 线程锁

乐观锁,悲观锁,公平锁,非公平锁,独占锁,共享锁,可重入锁,自旋锁;

12.1 乐观锁和悲观锁

乐观锁和悲观锁是在数据库中引入的名词,但是在并发包锁里面也引入了类似的思想。

悲观锁指对数据被外界修改持保守态度,认为数据很容易就会被其他线程修改。所以在数据被处理前先对数据进行加锁,并且在整个数据处理的过程中。使数据处于锁定状态;

  • 乐观锁: 每次拿数据的时候都认为别人不会修改,所以拿的时候不会上锁,但是在更新数据的时候会判断一下在此期间有没有人去更新这个数据,可以使用版本号等机制。乐观锁使用于多读的机制,可以提高吞吐量。
  • 悲观锁:总是假设最坏的情况,读数据和更新数据都会上锁。synchronized 就是悲观锁。

乐观锁的实现方式

  1. 使用版本号来确定独到的数据与提交的数据是否一致,提交后修改版本号,不一致时可以采取丢弃和再次尝试的策略

    update table1 set name = #{name},age = #{age} where id = #{id} and version = # {version};

  2. CAS (乐观锁是一种思想。CAS 是这种思想的一种实现方式)

CAS 是乐观锁的一种实现方式之一

12.2 公平锁、非公平锁

根据线程获取锁的抢占机制,锁可以分为公平和非公平锁;

公平锁: 非常公平, 不能够插队,必须先来后到!

非公平锁:非常不公平,可以插队 (默认都是非公平

public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

12.3 独占锁(写锁)共享锁(写锁)

根据锁只能被单个线程持有还是能被多个线程共同持有,锁可以分为独占锁和共享锁;

读写锁(ReentrantReadWriteLock): 相对于Lock锁更加细粒度的控制

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCacheLock myCache = new MyCacheLock();
        // 写入
        for (int i = 1; i <= 10; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.put(temp, temp);
            }, String.valueOf(i)).start();
        }
        // 读取
        for (int i = 1; i <= 10; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.get(temp);
            }, String.valueOf(i)).start();
        }
    }
}

/**
 * 自定义缓存
 * 没有进行加锁操作,在多线程写入的时候,很容易被其他的写入线程插入写入操作
 * 通过读(共享锁)写(独占锁)锁解决插入时被其他线程插队的情况
 */
class MyCache {
    private volatile Map<Integer, Integer> map = new HashMap<>();

    public void put(Integer key, Integer value) {
        System.out.println(Thread.currentThread().getName() + "写入" + key);
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "写入OK");
    }
    public void get(Integer key) {
        System.out.println(Thread.currentThread().getName() + "读取" + key);
        Object o = map.get(key);
       System.out.println(Thread.currentThread().getName() + "读取OK");
    }
}

/**
 * 加锁的,线程安全的
 */
class MyCacheLock {
    private volatile Map<Integer, Integer> map = new HashMap<>();
    // 读写锁: 相对于Lock锁更加细粒度的控制
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void put(Integer key, Integer value) {
        //写锁
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    public void get(Integer key) {
        //读锁
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

12.4 可重入锁(递归锁)

可重入锁:synchronized/ReentrantLock都是可重入锁

可重入锁(也叫递归锁:递归传递同一把锁)

  • 同一个线程在进入外层同步方法获得锁之后,同样可以进入该外层同步方法中所拥有的内层同步方法,它们此时所拥有的是同一把锁
  • 这种设计可以避免死锁(如果不是可重入锁,在进入外层同步方法之后,无法进入该外层同步方法所拥有的内层同步方法,这种情况下就会出现死锁)

12.5 自旋锁

在这里插入图片描述

12.5.1 自定义自旋锁
import java.util.concurrent.atomic.AtomicReference;

/**
 * 自旋锁
 */
public class SpinlockDemo {
    // int 0
    // Thread null
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    // 加锁
    public void myLock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "==> mylock");
    // 自旋锁
        while (!atomicReference.compareAndSet(null, thread)) {
        }
    }

    // 解锁
    public void myUnLock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "==> myUnlock");
        atomicReference.compareAndSet(thread, null);
    }
}

13. CAS

CAS(compare and swap)是乐观锁的一种实现方式,JUC中的很多工具类都是基于CAS 实现的。

CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就 一直循环!

CAS 中的三个操作数:

  1. 内存位置( V )
  2. 预期原值( A )
  3. 新值( B )

13.1 CAS的实现

CAS 是通过无限循环来获取数据的,如果在第一轮循环中,a 线程获取地址里面的值被 b线程修改了,那么 a 线程需要自旋,到下次循环才有可能机会执行。

public final class Unsafe {
	public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        //var2 偏移量 offset
        //var4  = 1;
        do {
            //获取内存地址中的值
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
		// var1 + var2 = var5 时,执行 var5 + var4
        return var5;
    }
	public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
}

13.2 CAS的缺点

  1. 循环时间长开销大
  2. 一次性只能保证一个共享变量的原子性
  3. ABA问题

对于只能保证一个共享变量的原子操作:从JDK1.5开始,JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象中来进行CAS操作;

13.3 ABA问题

13.3.1 什么是ABA问题

什么是ABA问题?两个线程,线程1线程2线程1从内存位置V 取出 值A线程2 从内存位置V 取出值A线程2 修改A值 变成 B 值线程2 修改B 值又变成A 值线程1 进行CAS 操作发现 内存中仍然是 A 值线程1 操作成功。

13.3.2 ABA问题如何解决

Java1.5 开始 JDKatomic 包中的 atomicStampedReference 类 来解决ABA问题

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

public class CASDemo1 {
    //AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题

    static AtomicStampedReference<Integer> atomicStampedReference = new
            AtomicStampedReference<>(1, 1);

    // CAS compareAndSet : 比较并交换!
    public static void main(String[] args) {
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("a1=>" + stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicStampedReference.compareAndSet(1, 2,
                    stamp,
                    stamp + 1));
            
            stamp = atomicStampedReference.getStamp();
            System.out.println("a2=>" + atomicStampedReference.getStamp());
            System.out.println(atomicStampedReference.compareAndSet(2, 1,
                    stamp,
                    stamp + 1));
            System.out.println("a3=>" + atomicStampedReference.getStamp());
        }, "a").start();
        // 乐观锁的原理相同!
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("b1=>" + stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicStampedReference.compareAndSet(1, 6,
                    stamp, stamp + 1));
            System.out.println("b2=>" + atomicStampedReference.getStamp());
        }, "b").start();
    }
}
执行结果:
a1=>1
b1=>1
true
a2=>2
true
a3=>3
false
b2=>3

14. Unsafe 类

JDKrt.jar 包中 Unsafe 类提供了硬件级别的原子性操作,Unsafe 类中的方法都是 native 方法,它们使用JNI的方式访问 C++实现库。

15. 伪共享

15.1 什么是伪共享?

为了解决计算机系统中主内存和 CPU 之间运行速度差问题,会在CPU与主内存之间添加一级或多级高速缓冲存储器(Cache)。这个 Cache 一般被集成到 CPU 内部,所以也叫 CPU cache。如下为两级 Cache 结构。

在这里插入图片描述

在 Cache 内部是按行存储的,其中每一行称为一个 Cache 行,Cache 行是 Cache 与主内存进行数据交换的单位,Cache 行的大小一般为 2 的幂次数字节;

在这里插入图片描述

当 CPU 访问某个变量时,会先去 CPU Cache 中查询变量是否存在,存在直接过去,当 CPU Cache 中不存在时,则从主内存中获取变量,由于上面我们介绍了是以Cache行为交换单位的,所以可能会把多个变量存放到一个Cache行中。当多个线程同时修改一个Cache行中的多个变量时,由于同时只能有一个线程操作缓存行,所以相比将每个变量放到缓存行,性能会有所下降,这个就是伪共享

在这里插入图片描述

15.2 伪共享产生的原因?

产生的原因就是交换单位为一个 Cache行,一个 Cache行 中可能同时包含多个变量。当多个线程同时操作这个 Cache行 上的变量时,就会造成 伪共享

15.3 如何避免伪共享

JDK8 之前一般通过字节填充的方式避免伪共享的问题,也就是创建一个变量时使用填充字段 填充 该变量所在的缓存行。如下代码

public final static class FilledLong{
    public volatile long value = 0L;
    public long p1,p2,p3,p4,p5,p6;
}
// 假设一个缓存行大小为 64 字节,如上的代码一个long类型为 8 字节,我们填充了6个long类型;
// FilledLong 类对象对象头占 8 个字节,value字段占8个字节,填充了6个long类型48字节
// 刚好 8 + 8 + 48 = 一个`cache行` 64字节; 

JDK8提供了一个 sun.misc.Contended注解,用来解决伪共享问题;

// Thread类中就使用到了  @sun.misc.Contended 注解;
public class Thread implements Runnable {
    @sun.misc.Contended("tlr")
    long threadLocalRandomSeed;
    /** Probe hash value; nonzero if threadLocalRandomSeed initialized */
    @sun.misc.Contended("tlr")
    int threadLocalRandomProbe;
    /** Secondary seed isolated from public ThreadLocalRandom sequence */
    @sun.misc.Contended("tlr")
    int threadLocalRandomSecondarySeed;
}

需要注意的是,在默认情况下,@Contended 注解只用于 Java 核心类,比如 rt 包下的类,如果用户类路径下的类需要使用这个注解,则需要添加JVM参数-XX:-RestrictContended填充的宽度默认为128,若自定义宽度则可以设置 -XX:ContendedPaddingWidth参数;

并发包JUC

16.Fork/Join

Fork/Join框架是Java7提供的一个用于并行执行任务的框架,是一个把大任务拆分为若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架;

并行执行:提高效率

大数据量:小数据量完全没有使用 Fork/Join的必要;

  1. 步骤一分割任务
  2. 步骤二执行任务并合并结果

img

任务分割 fork结果合并join。这点在代码编写时也有所体现;

16.1 工作窃取

工作窃取算法:是指某个线程从其他队列里窃取任务来执行。

在这里插入图片描述

一个很大的任务,分割成为了多个互不依赖的子任务,为了减少线程间的竞争,把子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应;

如上图,线程1窃取线程2中的任务执行,由于他们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务。

16.1.1 工作窃取的优缺点

**优点:**充分利用线程进行并行计算,减少线程间竞争

**缺点:**某些时候存在竞争,比如双端队列中只有一个任务时。

16.2 Fork/Join的使用

要使用Fork/Join框架,必须先创建一个Fork/Join任务(ForkJoinTask),通过RecursiveActionRecursiveTask类实现。通过ForkJoinPool执行ForkJoinTask

RecursiveActionRecursiveTask都是ForkJoinTask的子类;

RecursiveAction:没有返回结果的任务

RecursiveTask:有返回结果的任务

16.3 从累加案例看Fork/join的好处

累加 1~10_0000_0000之间所有的数,下面的案例使用三个方法,测试三种方法的效率。分别是普通累加Fork/JoinStream并行流

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

/**
 * 求和计算的任务!
 *
 *  如何使用 forkjoin
 *  1、forkjoinPool 通过它来执行
 *  2、计算任务 forkjoinPool.execute(ForkJoinTask task)
 *  3. 计算类要继承 ForkJoinTask
 *
 *public class ForkJoinPool extends AbstractExecutorService {
 *      public void execute(ForkJoinTask<?> task) {
 *         if (task == null)
 *             throw new NullPointerException();
 *         externalPush(task);
 *      }
 * }
 *
 * public abstract class RecursiveTask<V> extends ForkJoinTask<V> {}
 */
public class ForkJoinDemo extends RecursiveTask<Long> {
    private Long start;
    private Long end;
    // 临界值
    private Long temp = 10000000L;

    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    // 计算方法
    @Override
    protected Long compute() {
        if ((end - start) < temp) {
            Long sum = 0L;
            for (Long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else { // forkjoin 递归
            long middle = (start + end) / 2; // 中间值
            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
            task1.fork(); // 拆分任务,把任务压入线程队列
            ForkJoinDemo task2 = new ForkJoinDemo(middle + 1, end);
            task2.fork(); // 拆分任务,把任务压入线程队列
            return task1.join() + task2.join();
        }
    }
    
}

class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//         test1(); // 12224   sum=500000000500000000 时间:5267
         test2(); // 10038      sum=500000000500000000 时间:3240
//         test3(); // 153        sum=500000000500000000时间:190
    }

    // 普通程序员
    public static void test1() {
        Long sum = 0L;
        long start = System.currentTimeMillis();
        for (Long i = 1L; i <= 10_0000_0000; i++) {
            sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum=" + sum + " 时间:" + (end - start));
    }

    // 会使用ForkJoin
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);// 提交任务
        Long sum = submit.get();
        long end = System.currentTimeMillis();
        System.out.println("sum=" + sum + " 时间:" + (end - start));
    }
    // Stream并行流
    public static void test3() {
        long start = System.currentTimeMillis();
        // .range(): ()
        // .rangeClose: (]
        long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();

        System.out.println("sum=" +sum +  "时间:" + (end - start));
    }
}

17. 并发包下的原子操作类

17.1 AtomicLongAtomic原子包下的类

public class AtomicDemo {

    private static AtomicInteger atomicInteger = new AtomicInteger(0);
    private static AtomicLong atomicLong = new AtomicLong(0);
    private static int count = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    atomicInteger.incrementAndGet();
                    atomicLong.incrementAndGet();
                    count++;
                }
            }).start();
        }
        //等待线程执行完毕
        while (Thread.activeCount()>2){}

        System.out.println("atomicInteger:"+atomicInteger);
        System.out.println("atomicLong:"+atomicLong);
        System.out.println("count:"+count);
    }
}
执行结果:
atomicInteger:20000
atomicLong:20000
count:19207
public class AtomicLong extends Number implements java.io.Serializable {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //调用unsafe方法,原子性设置value值为原始值+1,返回值为递增后的值
    public final long incrementAndGet() {
        return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
    }
}

public final class Unsafe{
    public final long getAndAddLong(Object var1, long var2, long var4) {
        long var6;
        do {
            var6 = this.getLongVolatile(var1, var2);
            //CAS操作保证安全(代码A)
        } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
        return var6;
    }
}

在没有原子类的情况下,实现累加需要使用一定的同步措施,比如synchronized等。但是这些都阻塞算法,对性能有一定的消耗,本章介绍的Atomic原子操作类都是CAS非阻塞算法,性能更好。但是在高并发情况下AtomicLong还会存在性能问题,JDK8提供了一个高并发下性能更好的LongAdder类。

17.2 LongAdder

AtomicLong 通过CAS提供了非阻塞的原子性操作,相比使用过阻塞算法的同步器性能已经提升很多了。但是在使用AtomicLong时存在一个问题,在高并发下大量线程会同时竞争更新同一个原子变量(上面代码块的代码A处),由于同时只有一个线程CAS会成功,这就造成大量线程竞争失败后,通过自旋导致白白浪费CPU资源;

在这种背景下,JDK8新增了LongAdder来克服高并发下AtomicLong的缺点;

LongAdder的实现思路:把一个变量分解为多个变量,让同样多的线程去竞争多个资源;

在这里插入图片描述

LongAdder内部维护多个Cell变量,同等并发量情况下,减少了争夺共享资源的并发量。多个线程在争夺同一个Cell原子变量时如果失败,它不是在当前Cell一直自旋CAS重试,而是尝试其他Cell,增加了当前线程CAS的成功性;最后在获取LongAdder当前值时,是把所有Cell变量的value值累加再加上base返回的。

18. JUC下的并发ListCopyOnWriteArrayList

CopyOnWriteArrayList

19. 并发包下的锁原理

19.1 LockSupport

19.1 线程许可证

JDK中的rt.jar包里的LockSupport是一个工具类,主要作用是挂起和唤醒线程,该工具类是创建锁和其他同步类的基础;

LockSupport使用Unsafe类实现;

介绍LockSupport之前,需要特别提到许可证LockSupport类与每个使用它的线程都会关联一个许可证,默认情况下调用LockSupport类的方法的线程不持有许可证。那么LockSupport许可证的关系,我们从下面几个LockSupport中的函数讲解;

19.1.1 void park()

如果调用park方法的线程已经拿到了LockSupport关联的许可证,则调用LockSupport.park()时会直接返回,否则禁止参与线程调度,直接阻塞挂起

public static void park() {
        UNSAFE.park(false, 0L);
}
public class LockSupportDemo {
    public static void main(String[] args) {
        System.out.println("begin park");
        LockSupport.park();
        System.out.println("end park");
    }
}
执行结果:
begin park
 
由于未拿到许可证,导致线程被阻塞挂起;

在其他线程调用unpark(Thread thread)方法并且将当前线程做为参数时,调用park方法而被阻塞的线程会被返回。另外如果其他线程调用了阻塞线程的interrupt()方法,设置了中断标志或者线程被虚假唤醒,则阻塞线程也返回;

因调用park方法而被阻塞的线程被其他线程中断而返回时不会抛出InterruptedException异常;

19.1.2 void unpark(Thread thread)

当一个线程调用unpark()时,

  1. 如果参数thread线程没有持有与LockSupport类关联的许可证,那么就让其thread线程持有;
  2. 如果thread之前因调用park方法而被挂起,那么该线程将被唤醒;
  3. 如果thread之前没有调用park方法,那么调用unpark方法之后再调用park方法,将会直接返回。
public class LockSupportDemo {
    public static void main(String[] args) {
        System.out.println("begin park");
        //使当前线程获取到许可证
        LockSupport.unpark(Thread.currentThread());
        LockSupport.park();
        System.out.println("end park");
    }
}
执行结果:
begin park
end park
public class LockSupportDemo2 {
    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -> {
            System.out.println("child thread begin park!");
            LockSupport.park();
            System.out.println("child thread unpark");
        }, "thread");

        //启动子线程
        thread.start();
        //main 线程等待1s
        Thread.sleep(1000);

        System.out.println("main thread begin unpark");
        //调用unpark使thread线程持有许可证,使park方法返回
        LockSupport.unpark(thread);
    }
}

执行结果:
child thread begin park!
main thread begin unpark
child thread unpark

同样,前文我们也介绍了另外如果其他线程调用了阻塞线程的interrupt()方法,设置了中断标志或者线程被虚假唤醒,则阻塞线程也返回;这里面提到了两个关键字中断虚假唤醒,我们看下面的案例

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

        Thread thread = new Thread(() -> {
            System.out.println("child thread begin park!");
            //调用park方法挂起,只有被中断才会被退出循环
            //这里使用while而不是使用if进行判断就是防止虚假唤醒的情况发生;
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("child thread ready park!");
                LockSupport.park();
            }
            System.out.println("child thread unpark");
        }, "thread");

        //启动子线程
        thread.start();
        //main 线程等待1s
        Thread.sleep(1000);

        System.out.println("main thread begin unpark");
        //中断thread线程
        thread.interrupt();
    }
}
执行结果:
child thread begin park!
child thread ready park!
main thread begin unpark
child thread unpark

如上代码,只有中断子线程才会运行结束,即使你调用unpark子线程也不会结束;

19.2 condition

本节需要注意的点,同步队列和等待队列;

关于condition的介绍,在前面的Lock锁精确唤醒已经初步有介绍;

Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创建出来的,Condition依赖Lock对象。

Condition的使用方式比较简单,需要注意的是在调用Condition方法前要先获取到锁,否则会抛出IllegalMonitorStateException异常;

public class ConditionDemo {
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    public void conditionWait() throws InterruptedException {
        lock.lock();
        try {
            condition.await();
        }finally {
            lock.unlock();
        }
    }
    public void conditionSignal() throws InterruptedException {
        lock.lock();
        try {
            condition.signal();
        }finally {
            lock.unlock();
        }
    }
}
19.2.1condition的实现分析
	public Condition newCondition() {
        return sync.newCondition();
    }
	final ConditionObject newCondition() {
            return new ConditionObject();
    }
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer{
	public class ConditionObject implements Condition{
       
    }
}

ConditionObject是同步器AbstractQueuedSynchronizer的内部类,每个Condition对象都包含着一个队列(等待队列),该队列是Condition对象实现等待/通知功能的关键。

等待队列的基本结构

一个Lock锁(更确切的说法是同步器)拥有一个同步队列和多个等待队列

在这里插入图片描述

下面我们看下Conditionawaitsignal系列方法;

19.2.2await的实现分析

如果一个线程调用了Condition.await(),那么该线程将会释放锁,构造成节点加入等待队列并进入等待状态。

构造成节点:节点的定义复用了同步器中节点的定义,也就是说,同步队列和等待队列中节点类型都是同步器的静态内部类AbstractQueuedSynchronizer.Node

关于Node节点的详细信息,在后续的AQS篇幅会具体讲解。

public class ConditionObject implements Condition{
        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;
}

一个Condition包含一个等待队列,Condition拥有首节点firstWaiter和尾结点lastWaiter。当await方法调用时,会以当前线程构造节点,并将节点从尾部加入等待队列。

新增节点只需要将原有的尾结点nextWaiter指向新增的节点,并且更新尾结点即可;

在这里插入图片描述

从队列的角度看await方法相当于同步队列的首节点(获取了锁的节点)移动到Condition的等待队列中;

        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            //当前线程加入等待队列
            Node node = addConditionWaiter();
            //释放同步状态(释放锁)
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

能够成功调用await方法,也就是意味着成功获取到了锁,也就是同步队列中的首节点,也就是当前线程构造成节点并加入等待队列,然后释放同步状态,唤醒同步队列中的后继节点,然后当前线程会进入等待状态;

19.2.3signal的实现分析

如果一个线程调用了Condition.siganl(),将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移动到同步队列;

在这里插入图片描述

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}
private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
}

通过调用同步器的enq(Node node)方法,等待队列中的头节点线程安全的移动到同步队列。当节点移动到同步队列之后,当前线程再通过LockSupport唤醒该节点的线程;

被唤醒后的线程,将从await方法中的while循环中退出isOnSyncQueue(Node node)方法,返回true,节点已经在同步队列中,进而调用同步器的acquireQueued(final Node node, int arg)方法加入到获取同步状态的竞争中。

在这里插入图片描述

ConditionsignalAll方法相当于对等待队列中的每一个节点均执行一次signal方法,效果就是将等待队列中所有节点全部移动到同步队列中,并唤醒每个节点的线程;

19.3 AQS 原理

AQS

AQS原理

20. 并发包下队列原理

20.1 ConcurrentLinkedQueue

20.2 阻塞队列(BlockingQueue

在这里插入图片描述

JDK 7提供了7个阻塞队列,如下。

  1. ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。

  2. LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。

  3. PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。

  4. DelayQueue:一个使用优先级队列实现的无界阻塞队列。

    DelayQueue是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队 列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。 只有在延迟期满时才能从队列中提取元素。

  5. SynchronousQueue:一个不存储元素的阻塞队列。

    同步队列和其他的 BlockingQueue 不一样, SynchronousQueue 不存储元素, put了一个元素,必须从里面先take取出来,否则不能再put进去值!(没有容量, 进去一个元素,必须等待取出来之后,才能再往里面放一个元素!)

  6. LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。

  7. LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

    双向阻塞队列,可以从队列的两端插入和移出元素。多了addFirstaddLastofferFirstofferLastpeekFirstpeekLast等方法

20.2.1 阻塞队列(BlockingQueue)四组API
方式抛出异常有返回值,不抛出异常阻塞等待超时等待
添加add(e)offer(e)put(e)offer(e,time,unit)
移除remove()poll()take()poll(time,unit)
检测队首元素element()peek()--
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

public class Test {
    // 队列的大小 2
    static ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(2);

    public static void main(String[] args) {
        test1();
    }

    /**
     * 抛出异常
     */
    public static void test1() {
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        // IllegalStateException: Queue full 抛出异常!(超出队列大小抛出异常)
         System.out.println(blockingQueue.add("c"));
        System.out.println("===========");
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        // java.util.NoSuchElementException 抛出异常!
         System.out.println(blockingQueue.remove());
    }

    /**
     * 有返回值,没有异常
     */
    public static void test2(){

        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));   // 返回 false 不抛出异常!
        System.out.println("============================");
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());   // 返回 null 不抛出异常!
    }

    /**
     * 等待,阻塞(一直阻塞)
     */
    public static void test3() throws InterruptedException {
        // 一直阻塞
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c"); // 队列没有位置了,一直阻塞
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take()); // 没有这个元素,一直阻塞
    }

    /**
     * 等待,阻塞(等待超时)
     */
    public static void test4() throws InterruptedException {
        blockingQueue.offer("a");
        blockingQueue.offer("b");
        blockingQueue.offer("c",2, TimeUnit.SECONDS); // 等待超过2秒就退出
        System.out.println("===============");
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        blockingQueue.poll(2,TimeUnit.SECONDS); // 等待超过2秒就退出
    }
}
20.2.2 阻塞队列的原理

如果队列是空的,消费者会一直等待。当生产者添加元素时,消费者是如何知道当前队列有元素的呢?JDK采用的是通知模式实现的。

通知模式:当生产者往满的队列里添加元素时会阻塞住生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用。

我们通过查看ArrayBlockingQueue的阻塞方法put,take源码。

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
	public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
    private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal();
    }
    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }

}

通过源码我们可以发现ArrayBlockingQueue使用了Condition来实现。当向队列中插入元素,队列不可用时,通过await方法。之前我们学过的Condition章节,也知道了Conditionawait也就最终调用了LockSupport.park(this)来实现;

20.2.3 DelayQueue

import java.util.Random;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class DelayQueueDemo {
    static class DelayedEle implements Delayed {
        private final long delayTime; //延迟时间
        private final long expire; //到期时间
        private String taskName;//任务名称

        public DelayedEle(long delay, String taskName) {
            delayTime = delay;
            this.taskName = taskName;
            expire = System.currentTimeMillis() + delay;
        }

        /**
         * 剩余时间=到期时间 当前时间
         */
        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        /**
         * 优先级队列里面的优先级规则
         */
        @Override
        public int compareTo(Delayed o) {
            return (int) (this.getDelay(TimeUnit.MILLISECONDS) -
                    getDelay(TimeUnit.MILLISECONDS));
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder(" DelayedEle { ");
            sb.append(" delay=").append(delayTime);
            sb.append(", expire=").append(expire);
            sb.append(", taskName='").append(taskName).append('\'');
            sb.append('}');
            return sb.toString();
        }
    }

    /**
     * 创建了一个延迟队列,然后使用随机数生成器生成了 10 个延迟任
     * 务,最后通过循环依次获取延迟任务,并打印 。
     */
    public static void main(String[] args) {
        //创建delay队列
        DelayQueue<DelayedEle> delayQueue = new DelayQueue<DelayedEle>();
        //创建延迟任务
        Random random = new Random();
        for (int i = 0; i < 10; ++i) {
            DelayedEle element = new DelayedEle(random.nextInt(500), " task :" + i);
            delayQueue.offer(element);
            //依次取出任务并打印
            DelayedEle ele = null;
            try {
                //循环,如采想逃免虚假唤醒,则不能把全部元素都打印出来
                for (; ; ) {
                    //获取过期任务并打印
                    while ((ele = delayQueue.take()) != null) {
                        System.out.println(ele.toString());
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

创建延迟任务 DelayedEle 其中 delayTime表示当前任务需要延迟多少 ms 时间过期, expire 是当前时间的 ms 值加上 delayTime 的值 。另 外,实现了 Delayed 接口,实现了 long getDelay(TimeUnit unit) 方法用来获取当前元素还剩下多少时间过期,实现了 int compareTo(Delayed o)方法用来决定优先级队列元素的比较规则;

21. 线程同步器原理

21.1 CountDownLatch(减法计数器)

//构造方法
public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
}
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //设置减法计数器
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 0; i < 5; i++) {
            final String temp = i+"";
            new Thread(()->{
                System.out.println(temp);
                countDownLatch.countDown(); //计数器减一
            }).start();
        }
        //等待计数器归零,再往下执行
//        countDownLatch.await();
//        public boolean await(long timeout, TimeUnit unit);
        countDownLatch.await(10, TimeUnit.SECONDS);//等待超时时间
        System.out.println("CountDownLatchDemo ending ");
    }
}

21.2 CyclicBarrier(加法计数器)

/**
 * 加法计数器
 * 集齐7颗龙珠召唤神龙
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        // 召唤龙珠的线程(计数器达到7),计数器达到设定值,执行Runnable类的run();
        //CyclicBarrier 的两种构造法方法
        //public CyclicBarrier(int parties, Runnable barrierAction);
        //public CyclicBarrier(int parties);
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("召唤神龙成功!");
        });
        for (int i = 1; i <= 7; i++) {
            final int temp = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "收集" + temp + " 个龙珠");
                try {
                    cyclicBarrier.await(); // 等待,计数器加一
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
21.2.1 CountDownLatchCyclicBarrier 比较
  1. 调用CountDownLatchcountDown方法后,当前线程并不会阻塞,会继续往下执行;而调用CyclicBarrierawait方法,会阻塞当前线程,直到CyclicBarrier指定的线程全部都到达了指定点的时候,才能继续往下执行;

  2. CountDownLatch是不能复用的,而CyclicLatch是可以复用的。

  3. CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;CountDownLatch强调一个线程等多个线程完成某件事情。CyclicBarrier是多个线程互等,等大家都完成,再携手共进。

21.3 Semaphore(信号量)

作用: 限制并发数

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * Semaphore 的两种构造方法
 * public Semaphore(int permits, boolean fair);
 * public Semaphore(int permits);
 */
public class SemaphoreDemo {
    public static void main(String[] args) {
        // 线程数量:停车位! 限流!
        Semaphore semaphore = new Semaphore(3);//限定信号量的最大值
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire(); //获取信号量(假设信号量已满,等待,一直等到信号量被释放)
                    System.out.println(Thread.currentThread().getName() + "得到了車位");
                    if (Thread.currentThread().getName().equals("1")){
                        TimeUnit.SECONDS.sleep(3);
                    }else TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName() + "離開了車位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // release() 释放信号量(唤醒其他等待的线程)
                }
            }, String.valueOf(i)).start();
        }
    }
}

22. Exchanger

Exchanger(交换者)是一个用于县城管间协作的工具类。Exchanger用于线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以彼此交换数据。

如果第一个线程先执行exchange(),它会一直等待第二个线程也执行exchange()方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方;

import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExchangerDemo {
    public static void main(String[] args) {
        Exchanger<String> exgr = new Exchanger<>();
        ExecutorService threadPool = Executors.newFixedThreadPool(2);

        threadPool.execute(()->{
            String A = "待交换数据A";
            try {
                String exchangeB = exgr.exchange(A);
                System.out.println("A这边拿到的交换数据是:"+exchangeB);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        threadPool.execute(()->{
            String B = "待交换数据B";
            try {
                String exchangeA = exgr.exchange(B);
                System.out.println("B这边拿到的交换数据是:"+exchangeA);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadPool.shutdown();

    }
}
执行结果:
A这边拿到的交换数据是:待交换数据B
B这边拿到的交换数据是:待交换数据A

23. 线程池

线程池的好处(线程复用,控制最大并发数,管理线程)

  1. 降低资源的消耗
  2. 降低响应速度
  3. 方便管理

23.1 线程池的五种创建方式

  1. newFixThreadPool //创建固定线程数的线程池
  2. newCachedThreadPool //大小无界的线程池,适用执行很多的短期异步任务的小程序。
  3. newScheduledThreadPool //固定个数的线程池。适用于需要多个后台线程执行周期任务,为了满足资源管理的需求而需要限制后台线程的数量的应用场景;
  4. newSingleThreadExecutor //创建单个线程的线程池
  5. newThreadPoolExecuted(自定义线程池)
23.1.1 使用 Executors 创建线程的弊端

为什么阿里巴巴开发手册中强制不建议使用Executors创建线程池,使用Executors创建的线程池有什么弊端?

public class Demo {
    public static void main(String[] args) {
        Executors.newCachedThreadPool();
        Executors.newFixedThreadPool(1);
        Executors.newSingleThreadExecutor();
        Executors.newScheduledThreadPool(1);
    }
}
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler);


	//1. newCachedThreadPool
	public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
	//2. newScheduledThreadPool(ScheduledThreadPoolExecutor中DelayedWorkQueue是一个无界队列,所以设置maximumPoolSize的大小没有什么效果)
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
	//3. newFixedThreadPool(keepAliveTime设置为0,表示多余的空闲线程会被立即终止。)
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }
    //4. newSingleThreadExecutor
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  1. newCachedThreadPoolnewScheduledThreadPool 允许的线程(非核心线程)数量为 Integer.MAX_VALUE,会创建大量的线程,可能会导致OOM
  2. newFixedThreadPoolnewSingleThreadExecutor 允许创建的请求队列长度为 Integer.MAX_VALUE,会堆积大量的请求,可能会导致OOM
23.1.2 自定义线程池中的参数说明
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler);

#参数说明
corePoolSize:核心线程数量
maximumPoolSize:最大线程数
keepAliveTime:存活时间,(除了核心线程之外,其他的线程超时了没有人调用就会释放的时间)
unit:存活单位
workQueue:阻塞队列(前面章节介绍的。)
handler:拒绝策略
## 拒绝策略:默认是直接抛出异常
    1. AbortPolicy:直接抛出异常
    2. CallRunsPolicy: 只用调用者所在的线程处理任务
    3. DiscurdOldestPlicy:队列满了,丢弃队列中最近的一个任务,来执行最新的任务(尝试和最早的任务做一个竞争)
    4. DisCardPolicy:队列满了,不处理直接丢弃
    5. 也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略;
    

23.2 自定义线程池中最大的线程数量应该如何设置

  1. IO密集型: 判断你程序中十分耗IO 的数量(假设你的程序中有15 个大型任务,IO十分占用资源 。设置最大线程数 > 15 就好)
  2. CPU 密集型: 查看自己的CPU 是几核的。可以保持CPU效率最高 (程序中获取线程数: Runtime.getRuntime().availableProcessores()

23.3 线程池中核心线程,最大线程,队列的执行顺序

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
         /**
 			 * 1. 如果运行的线程少于corePoolSize,则尝试
			 * 用给定的命令作为第一个命令启动一个新线程
			 * 的任务。调用addWorker会自动检查runState和
			 * workerCount,这样可以防止误报
			 * 线程,当它不应该返回false。
			 * 2. 如果一个任务可以成功排队,那么我们仍然需要
			 * 再次检查我们是否应该添加一个线程
			 * (因为现有的已经在上次检查后死亡)或其他
			 * 进入此方法后池关闭。所以我们
			 * 如果需要,请重新检查状态并回滚排队
			 * 停止,或者启动一个新的线程(如果没有)。
			 * 3.如果不能对任务进行排队,则尝试添加一个新的
			 * 线程。如果它失败了,我们知道我们已经关闭或饱和了
			 * 所以拒绝这个任务。
		 */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }
23.3.1 任务添加顺序

在这里插入图片描述

23.3.2 任务执行顺序

先执行线程(核心+最大线程)中的任务,最后再执行任务队列中的任务

在这里插入图片描述

23.4 线程池中submit()execute() 方法有什么区别?

两个方法均可以向线程池提交任务。

  • 接收的参数不一样
  • sumbit() 有返回值,而execute() 没有
  • submit方便exception处理

23.5 线程池中任务添加(addWorker)/执行(execute)源码分析

public class ThreadPoolExecutor extends AbstractExecutorService {
    //用来记录线程池状态和线程池中线程个数(高3位表示线程池状态,后29位表示线程池中线程个数)
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    //
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

    // Packing and unpacking ctl
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }
}

线程池状态含义如下。

  1. RUNNING接受新任务并且处理阻塞队列里的任务
  2. SHUTDOWN :拒绝新任务但是处理阻塞队列里的任务
  3. STOP :拒绝新任务并且抛弃阻塞队列的任务,同时会中断正在处理的任务。
  4. TIDYING :所有任务都执行完(包含阻塞队列里面的任务)后当前线程池活动线程数为0, 将要调用 terminated 方法
  5. TERMINATED:终止状态 。terminated 方法调用完成以后的状态

线程池状态转换列举如下

  1. RUNNING -> SHUTDOWN: 显式调用 shutdown() 方法,或者隐式调用了 finalize() 方法里面的 shutdown()方法
  2. RUNNING或 SHUTDOWN-> STOP 显式调用 shutdownNow()方法
  3. SHUTDOWN ->TIDYING:当线程池和任务队列都为空时 。
  4. STOP -> TIDYING:当线程池为空时 。
  5. TIDYING-> TERMINATED:当 terminated() hook 方法执行完成时。
23.5.1execute()
    public void execute(Runnable command) {
        //1. 如果任务为null,则抛出NPE异常
        if (command == null)
            throw new NullPointerException();
		//2. 获取当前线程池的状态+线程个数变量的组合值
        int c = ctl.get();
        //3. 当前线程池中线程数量是否小于corePoolSize,小于则开启新线程运行
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //4. 如果线程池处于RUNNING状态,则添加任务到阻塞队列
        if (isRunning(c) && workQueue.offer(command)) {
            //4.1 二次检查
            int recheck = ctl.get();
            //4.2 如果当前线程池状态不是RUNNING则从队列中删除任务,并执行拒绝策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //4.3 否则如果当前线程池为空,则添加一个线程
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //5. 如果队列满,则新增线程,新增失败则执行拒绝策略
        else if (!addWorker(command, false))
            reject(command);
    }

代码4处:线程处于 RUNNING 状态则添加当前任务到任务队列。这里需要判断线程池状态是因为有可能线程池己经处于非RUNNING 状态 ,而在 非RUNNING 状态下是要抛弃新任务;

如果向任务队列添加任务成功,则代码4.2 对线程池状态进行二次校验,这是因为添加任务到 任务队列后 ,执行代码4.2前有可能线程池的状态己经发生改变了。这里进行二次校验,如果当前线程池状态不是RUNNING 了则把任务从任务队列移除,移除后执行拒绝策略 ;如果二次校验通过,则执行代码4.3重新判断当前线程池里面是否还有线程,如果没有则新增一个线程。

23.5.2 addWorker()
//主要分为两部分:
//第一部分双重循环的目的是通过CAS操作增加线程数;
//第二部分主要是把并发安全的任务添加到workers里面,并且启动任务执行;
private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
}

引用

《Java并发编程之美》____翟陆续

《Java并发编程的艺术》____方腾飞

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王叮咚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值