Java - 基础 & JUC 多线程

文章目录

基础多线程

1、程序 · 进程 · 线程

  • 程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
  • 进程是程序的一次执行过程,是一个动态的概念;进程是系统资源分配的单位
  • 线程是CPU调度和执行的单位,是一个独立的执行路径;一个进程可以包含多个线程,且一个进程中至少应有一个线程,不然没有存在的意义
  • 在程序运行时,即使没有自己创建线程,后台也会有线程在跑,如main主线程、gc垃圾回收线程等

  • 主线程main()是系统的入口,用于执行程序

  • 在一个进程中,如果开辟了多个线程,线程的运行顺序由调度器安排,调度器与操作系统相关,无法人为干预

  • 对同一份资源进行操作时,会存在资源抢夺的问题,需要加入并发控制

  • 线程会带来额外的开销,如CPU调度时间,并发控制开销

  • 每个线程在自己的工作内存交互,内存控制不当会造成数据的不一致

2、线程创建的四种方式

2.1、继承Thread类

class Thread implements Runnable

自定义线程类继承Thread类:

class MyThread extends Thread{ }

重写run()方法,编写线程执行体

@Override
public void run() {}

创建此线程对象,调用start()方法启动线程:(线程开启后不一定立即执行,而是由CPU调度)

MyThread myThread = new MyThread();
myThread.start();

2.2、实现Runnable接口(※)

自定义线程类实现Runnable接口:

class MyRunnable implements Runnable{}

重写run()方法,编写线程执行体

@Override
public void run() {}

该实现类实例作为参数传入一个线程对象,调用该线程对象的start()方法启动线程:

MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();

2.3、实现Callable接口

自定义线程类实现Callable接口:

class MyCallable implements Callable<Boolean /* or ... */ >{}

重写call()方法,编写线程执行体

@Override
public Boolean call() throws Exception {
    return true;
}

该实现类实例作为参数传入一个FutureTask<>对象,该对象实例作为参数传入一个线程对象,调用该线程对象的start()方法启动线程:

MyCallable myCallable = new MyCallable();
FutureTask<Boolean> futureTask = new FutureTask<>(myCallable);
new Thread(futureTask).start();
Boolean b = futureTask.get(); // 获取返回值
System.out.println(b);

Thread类只接受实现了Runnable接口的实例,不能直接调用Callable接口的实例化对象,而FutureTask实现了RunnableFuture,RunnableFuture又继承了Runnable接口,FutureTask就是那个适配器(这就是适配器模式)

public interface RunnableFuture<V> extends Runnable, Future<V>
public class FutureTask<V> implements RunnableFuture<V> 
public Thread(Runnable target) 

Callable或者说是FutureTask的一个小坑:

在这里插入图片描述
在这里插入图片描述

由于JVM第二次再调用FutrueTask对象所持有的线程时,FutrueTask的state已是非NEW状态,此时会直接结束对应线程,就会导致任务也不执行而是返回第一次调用的返回值

2.4、实现Executor接口

自定义线程类实现Executor接口:

class MyExecutor implements Executor {}

重写execute()方法,启动Runnable实例任务:

@Override
public void execute(Runnable command) {
    new Thread(command).start();
}

Executor对象调用execute()方法执行runnable()接口的实现类的run()方法:

new MyExecutor().execute(() -> System.out.println(Thread.currentThread().getName()));

2.5、Java可以开启线程吗

不可以

public synchronized void start() {
    /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
        }
    }
}

private native void start0();

java程序是运行在虚拟机上的,不能直接操作本地硬件(如CPU)来开启线程,需要通过native本地方法private native void start0();来调用底层的C/C++来实现

3、start()和run()的区别

在这里插入图片描述

start()会新开启一个线程,让新开启的线程去执行run()方法中的线程任务,此时无需等待run()方法执行完毕,即可继续执行下面的代码:
在这里插入图片描述

调用run()并未开启新线程,去执行run()的只有调用run()的线程(主线程),还是要顺序指定,要等待run()方法体执行完毕后才可继续执行下面的代码:
在这里插入图片描述

4、线程的生命周期 / 线程有哪些状态

创建、就绪、运行、阻塞、死亡

1、新建状态(New):创建了一个线程对象

2、就绪(Runnable):线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行的线程池中,变得可运行,等待获取CPU的使用权。

3、运行状态(Running):就绪状态的线程获取到了CPU,执行程序代码。

4、阻塞状态(Blocked):线程因为某种原因放弃了CPU的使用权,暂时停止运行。直到线程重新变为就绪状态,采用可能再次变成运行状态。

阻塞又分为三种:

(1)等待阻塞:wait()方法(Object类的方法),该线程会释放占用的所有资源,JVM会把该线程放入等待池中。这种状态无法自动唤醒,必须依靠其他线程调用notify()/notifyAll()方法才能被唤醒。

(2)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

(3)其他阻塞:运行的线程执行sleep()(Thread类的方法)或join()方法、或者发出了I/O请求时,JVM会把线程置为阻塞状态。当sleep状态超时、join等待线程终止或者超时、I/O处理完毕时,线程重新转入就绪状态。

5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

5、线程停止

@Deprecated
public final void stop()
@Deprecated
public void destroy() 

public void interrupt()

5.1、interrupt()

Thread thread = new Thread(() -> {
    for (int i = 0; i < 100; i++) {
        if (Thread.currentThread().isInterrupted()) {
            return;
        }
        System.out.println(i);
    }

});
thread.start();
Thread.sleep(2);
thread.interrupt();

在这里插入图片描述

5.2、自定义开关(标识位)

private static boolean target = true;

public static void myInterrupt() {
    target = false;
}

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            if (!target) {
                return;
            }
            System.out.println(i);
        }

    });
    thread.start();
    Thread.sleep(2);
    myInterrupt();
}

在这里插入图片描述

6、线程休眠 sleep()

public static native void sleep(long millis) throws InterruptedException;
  • sleep()是Thread类的静态本地方法
  • sleep(long millis) 休眠时间为毫秒
  • sleep()存在异常InterruptedException需要抛出
  • sleep()到达时间后线程进入就绪状态
  • sleep()不会释放锁

7、线程礼让 yield()

public static native void yield();
  • yield()是Thread类的静态本地方法
  • yield()执行后线程直接进入就绪状态,马上释放了CPU的执行权,但是依然保留了CPU的执行资格,所以有可能CPU下次进行线程调度还会让这个线程获取到执行权继续执行(礼让不成功)

8、线程插队 join()

public final void join() throws InterruptedException
  • join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入阻塞队列,直到线程A结束或中断。

没有join(),先执行main线程,再执行MyThread线程:
在这里插入图片描述

有join(),MyThread线程插队,先执行MyThread线程,再执行main线程:
在这里插入图片描述

9、线程状态 State

6个:

 public enum State {
        /**
         * 新建
         *
         * 尚未启动的线程的线程状态。
         */
        NEW,

        /**
         * 就绪
         *
         * 可运行线程状态正在Java虚拟机中执行,但它可能等待来自操作系统的其他资源如处理器
         */
        RUNNABLE,

        /**
         * 阻塞
         * 
         * 线程的线程状态被阻塞,等待监视器锁定
         * wait
         */
        BLOCKED,

        /**
         * 等待(阻塞)
         *
         * 处于等待状态的线程正在等待另一个线程执行特定操作
  		 * wait()
  		 * join()
  		 * ...
         */
        WAITING,

        /**
         * 超时等待(阻塞)
         *
         * 处于等待状态的线程正在等待另一个线程执行特定操作达到指定时间
         * sleep
  		 * wait(long)
  		 * join(long)
  		 * ...
         */
        TIMED_WAITING,

        /**
  		 * 死亡
  		 * 
  		 * 线程已完成执行或被强制终止
         */
        TERMINATED;
    }

10、线程优先级

public final static int MIN_PRIORITY = 1; # 最低优先级
public final static int NORM_PRIORITY = 5; # 默认优先级
public final static int MAX_PRIORITY = 10; # 最高优先级

设置优先级:

public final void setPriority(int newPriority)

获取优先级:

public final int getPriority()

优先级一致时,优先按照启动顺序来:
在这里插入图片描述
在这里插入图片描述
优先级不一致时,优先按照优先级来:
在这里插入图片描述
不过,情况都不是绝对的,只是大多数情况下而已

11、守护线程

  • 线程分为用户线程守护线程
  • JVM必须确保用户线程执行完毕
  • JVM不必确保守护线程执行完毕

设置守护线程:

public final void setDaemon(boolean on)

判断守护线程:

public final boolean isDaemon()

在这里插入图片描述

12、线程同步

12.1、synchronized 关键字

  • 并发:同一个对象被多个线程同时操作

  • 线程同步:多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用

  • 锁:当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可

    • 一个线程持有锁会导致其他需要这个锁的线程挂起

    • 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题

    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

12.1.1、同步方法
public synchsronized void method(){}
12.1.2、同步块
synchronized(Obj){}

12.2、显示定义同步锁 Lock

package java.util.concurrent.locks;

public interface Lock
12.2.1、可重入锁 ReentrantLock
public class ReentrantLock implements Lock, java.io.Serializable

使用方法:

private final Lock lock = new ReentrantLock();
@Override
public void run() {
    while (true) {
        lock.lock();
        try {
			/* ... */
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

在这里插入图片描述

12.3、synchronized 与 Lock 对比

  • synchronized是内置的java关键字,Lock是一个类
  • synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
  • synchronized默认就是可重入锁、不可中断、非公平;Lock可以判断锁,可以设置是否公平
  • synchronized适合锁少量代码,Lock适合锁大量代码
  • Lock是显式锁(手动lock和unlock),synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少时间来调度线程,性能更好,且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序:Lock > 同步代码块(已经进入了方法体,分配了相应的资源) > 同步方法(在方法体之外)

13、死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,导致两个或者多个线程都在等待对方释放资源,都停止执行。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题

13.1、死锁的产生

四个条件:互斥、不可剥夺、循环等待、请求保持

public class DeadLock {
    static String x1 = "x1";
    static String x2 = "x2";

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (x1) {
                System.out.println(Thread.currentThread().getName() + " " + x1);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (x2) {
                    System.out.println(Thread.currentThread().getName() + " " + x2);
                }
            }
        }).start();

        new Thread(() -> {
            synchronized (x2) {
                System.out.println(Thread.currentThread().getName() + " " + x2);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (x1) {
                    System.out.println(Thread.currentThread().getName() + " " + x1);
                }
            }
        }).start();
    }
}

如此便会产生死锁:(两个线程各抱着一个对象的锁不放,产生死锁,导致程序无法执行下去)
在这里插入图片描述

13.2、死锁的排查

13.2.1、jps -l查看进程号

在这里插入图片描述

13.2.2、jstack -进程号查看死锁堆栈信息

关键信息:

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00000000034a9fd8 (object 0x00000000d60baa60, a java.lang.String),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x0000000019fd61f8 (object 0x00000000d60baa90, a java.lang.String),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at juc.lock.DeadLock.lambda$main$1(DeadLock.java:31)
        - waiting to lock <0x00000000d60baa60> (a java.lang.String)
        - locked <0x00000000d60baa90> (a java.lang.String)
        at juc.lock.DeadLock$$Lambda$2/793589513.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
"Thread-0":
        at juc.lock.DeadLock.lambda$main$0(DeadLock.java:17)
        - waiting to lock <0x00000000d60baa90> (a java.lang.String)
        - locked <0x00000000d60baa60> (a java.lang.String)
        at juc.lock.DeadLock$$Lambda$1/1915910607.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

可以看出来,大概意思就是Thread-1想要的锁被Thread-0持有着,Thread-0想要的锁被Thread-1持有

13.3、死锁的避免

不满足死锁产生的四个条件的任何一个都可以避免死锁

例如,针对上面的例子:

public class DeadLock {

    static String x1 = "x1";
    static String x2 = "x2";

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (x1) {
                System.out.println(Thread.currentThread().getName() + " " + x1);
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (x2) {
                System.out.println(Thread.currentThread().getName() + " " + x2);
            }
        }).start();
        new Thread(() -> {
            synchronized (x2) {
                System.out.println(Thread.currentThread().getName() + " " + x2);
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (x1) {
                System.out.println(Thread.currentThread().getName() + " " + x1);
            }
        }).start();
    }
}

想要不发生死锁,就尽量避免让一个线程拿到一个以上的锁(即一个线程只拿一把锁,不要吃着碗里的还占着锅里的…)(即尽量不要发生synchronized嵌套)
在这里插入图片描述

14、线程协作 / 线程通信

wait()和notify()/notifyAll()均是Object类的方法,都只能在synchronized同步方法或同步代码块中使用

14.1、等待 wait() / wait(long timeout)

  • wait() 方法让当前线程进入等待状态,直到其他线程调用此对象的notify()方法或notifyAll()方法
  • wait(long timeout) 方法让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的notify()方法或notifyAll()方法,或者超过参数timeout设置的超时时间;如果timeout参数为0,则不会超时,会一直进行等待,类似于wait() 方法
public final void wait() throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
public final native void wait(long timeout) throws InterruptedException;

14.2、唤醒 notify() / notifyAll()

  • notify()方法用于唤醒一个在此对象监视器上等待的线程, 如果所有的线程都在此对象上等待,那么只会选择一个线程,选择是任意性的,并在对实现做出决定时发生
  • notifyAll()方法用于唤醒在该对象上等待的所有线程
public final native void notify();
public final native void notifyAll();

14.3、配合使用

public class PCTest {

  private final List<String> syncList;

    public PCTest() {
     syncList = Collections.synchronizedList(new LinkedList<>());
    }
   
    public String removeElement() throws InterruptedException {
     synchronized (syncList) {
            while (syncList.isEmpty()) {
                System.out.println(Thread.currentThread().getName() + "  " + "列表为空...");
                syncList.wait();
                System.out.println(Thread.currentThread().getName() + "  " + "等待中...");
            }
            return syncList.remove(0);
        }
    }
   
    public void addElement(String element) {
     System.out.println(Thread.currentThread().getName() + "  " + "addElement 方法 开始");
        synchronized (syncList) {
            syncList.add(element);
            System.out.println(Thread.currentThread().getName() + "  " + "添加元素: " + element);
            /*****************************************/
            syncList.notify();
            //syncList.notifyAll();
            /*****************************************/
            System.out.println(Thread.currentThread().getName() + "  " + "添加元素完成,通知...");
        }
        System.out.println(Thread.currentThread().getName() + "  " + "addElement 方法 结束");
    }
   
    public static void main(String[] args) throws InterruptedException {
     final PCTest demo = new PCTest();
   
        Thread threadA1 = new Thread(() -> {
         try {
                System.out.println(Thread.currentThread().getName() + "  " + "移除元素: " + demo.removeElement());
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "  " + e.toString());
            }
   
        }, "threadA1");
     threadA1.start();
        Thread.sleep(500);
   
        Thread threadA2 = new Thread(() -> {
         try {
                System.out.println(Thread.currentThread().getName() + "  " + "移除元素: " + demo.removeElement());
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "  " + e.toString());
            }
        }, "threadA2");
        threadA2.start();
        Thread.sleep(500);
   
        Thread threadA3 = new Thread(() -> {
         try {
                System.out.println(Thread.currentThread().getName() + "  " + "移除元素: " + demo.removeElement());
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "  " + e.toString());
            }
        }, "threadA3");
        threadA3.start();
        Thread.sleep(500);
   
        Thread threadB1 = new Thread(() -> demo.addElement("B1"), "threadB1");
     threadB1.start();
        Thread threadB2 = new Thread(() -> demo.addElement("B2"), "threadB2");
        threadB2.start();
        Thread.sleep(1000);
   
        threadA1.interrupt();
     threadA2.interrupt();
        threadA3.interrupt();
   
    }
}

比较addElement方法中notify()和notifyAll()的区别:

  • notify(): 唤醒哪个线程取决于操作系统对多线程管理的实现 (近似理解为随机吧…)

在这里插入图片描述

  • notifyAll():

在这里插入图片描述

在这里插入图片描述

15、sleep()、wait()、join()、yield()的区别

  • 锁池:
  • 所有需要竞争同步锁的线程都会放入锁池中,比如当前对象的锁已经被一个线程得到,那么其他线程则需要在这个锁池中进行等待;当前面的线程释放掉这个同步锁后锁池中的线程再去竞争同步锁,当某个线程得到后就会进入就绪队列等待CPU进行资源分配。
  • 等待池:
  • 当我们调用wait()方法后,线程会被放到等待池中,等待池中的线程不回去竞争同步锁,只有调用了notify() / notifyAll()方法后等待池的线程才会开始去竞争锁。notify()是随机释放等待池的一个线程到锁池中,notifyAll()则是将等待池所有的线程释放到锁池中

1、sleep()是Thread类的静态本地方法,wait()则是Object类的本地方法。(native)

2、sleep()方法不会释放锁,但是wait()会,并且会把该线程加入到等待队列中。

sleep()就是把CPU的执行资格和执行权释放出去,不再运行此线程,当定时时间结束时再取回CPU资源,参与CPU的调度,获取到CPU资源后就可以继续运行了。如果sleep()时该线程有锁,那么sleep()不会释放这个锁,而是把锁带着进入了冻结状态,也就是说其他线程根本不可能获取到这个锁,无法执行程序。如果在sleep()期间其他线程调用了这个线程的interrupt()方法,那么这个线程则会抛出interruptException异常,这点和wait()是一样的。

3、sleep()方法不依赖于同步器synchronized,但是wait()需要依赖synchronized关键字。

4、sleep()不需要被唤醒,wait()需要。

5、sleep()一般用于当前线程的休眠,或者轮询暂停;wait()多用于多线程之间的通信。

6、sleep()会让出CPU执行时间且强制上下文切换,wait()不一定,wait()被唤醒后线程会重新进入锁池,仍然有机会竞争得到锁去执行。

7、yield()执行后线程直接进入就绪状态,马上释放了CPU的执行权,但是依然保留了CPU的执行资格,所以有可能CPU下次进行线程调度还会让这个线程获取到执行权继续执行。(礼让)

8、join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入阻塞队列,直到线程A结束或中断。(插队)

16、线程池

public interface ExecutorService extends Executor
public class Executors{
    public static ExecutorService newFixedThreadPool(int nThreads) {
    	return new ThreadPoolExecutor(nThreads, 
                                      nThreads, 
                                      0L, 
                                      TimeUnit.MILLISECONDS, 
                                      new LinkedBlockingQueue<Runnable>());
    }
    
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 
}

16.1、ThreadPoolExecutor创建线程池

ExecutorService pool = new ThreadPoolExecutor(
    5, // 线程池的核心线程数
    10, // 能容纳的最大线程数
    1L, // 空闲线程存活时间
    TimeUnit.SECONDS, // 存活的时间单位
    new LinkedBlockingQueue<>(3), // 存放提交但未执行任务的队列
    Executors.defaultThreadFactory(), // 创建线程的工厂类
    new ThreadPoolExecutor.AbortPolicy() // 等待队列满后的拒绝策略
);

同时,该方式使用时建议把最后两个参数也显式地指定出来
在这里插入图片描述

其中的ThreadFactory可以使用guava的ThreadFactoryBuilder来指定格式:

ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();

需要引入依赖:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.1-jre</version>
</dependency>

16.2、方式二:newFixedThreadPool显示创建线程池(不推荐)

ExecutorService pool = Executors.newFixedThreadPool(5);

在这里插入图片描述

17、8锁问题

17.1、两个同步方法,一个对象调用(延时无关)

public class Demo1 {
    public static void main(String[] args) {
        Lock1 lock1 = new Lock1();
        new Thread(() -> lock1.fun1()).start();
        new Thread(() -> lock1.fun2()).start();
    }
}

class Lock1 {
    public synchronized void fun1() {
        System.out.println("fun1");
    }

    public synchronized void fun2() {
        System.out.println("fun2");
    }
}

执行结果:
在这里插入图片描述

synchronized在这里锁的是lock1对象,两个线程中的方法用的同一把锁,因此是谁先拿到这把锁谁先执行;即便加了延时,lock1也是抱着锁睡觉,因此执行顺序依旧是按照拿到锁的顺序,这种情况下和延时没有关系

17.2、两个同步方法,两个对象调用(延时有关)

public class Demo4 {
    public static void main(String[] args) {
        Lock4 lock41 = new Lock4();
        Lock4 lock42 = new Lock4();
        new Thread(() -> lock41.fun1()).start();
        new Thread(() -> lock42.fun2()).start();
    }
}

class Lock4 {
    public synchronized void fun1() {
        System.out.println("fun1");
    }

    public void fun2() {
        System.out.println("fun2");
    }
}

执行结果:
在这里插入图片描述
在这里插入图片描述

synchronized在这里锁的分别是各自的对象(lock41、lock42),两个线程用的不是同一把锁,,因此执行顺序没有必然联系,由CPU调度决定(两种情况都有可能出现),如果加上延时,那么就是延时短的先执行

17.3、一个同步方法,一个普通方法,一个对象调用(延时有关)

public class Demo3 {
    public static void main(String[] args) {
        Lock3 lock3 = new Lock3();
        new Thread(() -> lock3.fun1()).start();
        new Thread(() -> lock3.fun2()).start();
    }
}

class Lock3 {
    public synchronized void fun1() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(10);
        System.out.println("fun1");
    }

    public void fun2() {
        System.out.println("fun2");
    }
}

执行结果:
在这里插入图片描述
在这里插入图片描述

虽然synchronized在这里锁的是lock3对象,但是fun2()不是同步方法,不受锁的影响,第二个线程也不需要等待第一个线程去释放锁,因此这里就和延时有关系了:哪个方法的延时长,哪个方法就后执行

17.4、一个同步方法,一个普通方法,两个对象调用(延时有关)

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Lock1 lock11 = new Lock1();
        Lock1 lock12 = new Lock1();
        new Thread(() -> lock11.fun1()).start();
        new Thread(() -> lock12.fun2()).start();
    }
}

class Lock1 {
    public synchronized void fun1() {
        System.out.println("fun1");
    }

    public void fun2() {
        System.out.println("fun2");
    }
}

执行结果:
在这里插入图片描述
在这里插入图片描述

synchronized在这里锁的是lock11对象,但是fun2()不是同步方法,不受锁的影响,lock12对象并没有锁,第二个线程与第一个线程没有任何关系,更不用等待第一个线程去释放锁,两个线程的执行顺序没有必然联系,由CPU调度决定(两种情况都有可能出现),因此这里就和延时有关系了:哪个方法的延时短,哪个方法就先执行

17.5、两个静态同步方法,一个对象调用(延时无关)

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Lock1 lock1 = new Lock1();
        new Thread(() -> lock1.fun1()).start();
        new Thread(() -> lock1.fun2()).start();
    }
}

class Lock1 {
    public static synchronized void fun1() {
        System.out.println("fun1");
    }

    public static synchronized void fun2() {
        System.out.println("fun2");
    }
}

执行结果:
在这里插入图片描述

synchronized在这里锁的是Lock1的Class类模板(唯一),因此第二个线程必须等待第一个线去释放锁,不管有没有延时

17.6、两个静态同步方法,两个对象调用(延时无关)

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Lock1 lock11 = new Lock1();
        Lock1 lock12 = new Lock1();
        new Thread(() -> lock11.fun1()).start();
        new Thread(() -> lock12.fun2()).start();
    }
}

class Lock1 {
    public static synchronized void fun1() {
        System.out.println("fun1");
    }

    public static synchronized void fun2() {
        System.out.println("fun2");
    }
}

执行结果:
在这里插入图片描述

synchronized在这里锁的是Lock1的Class类模板(唯一),因此第二个线程必须等待第一个线去释放锁,不管有几个对象,不管有没有延时

17.7、一个静态同步方法,一个普通同步方法,一个对象调用(延时有关)

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Lock1 lock1 = new Lock1();
        new Thread(() -> lock1.fun1()).start();
        new Thread(() -> lock1.fun2()).start();
    }
}

class Lock1 {
    public static synchronized void fun1() {
        System.out.println("fun1");
    }

    public synchronized void fun2() {
        System.out.println("fun2");
    }
}

执行结果:
在这里插入图片描述
在这里插入图片描述

fun1()的synchronized锁的是Lock1的Class类模板,fun2()的synchronized锁的是lock1对象,两个方法的锁锁的不是同一个东西,两个线程用的也不是同一把锁,因此执行顺序没有关系,由CPU调度决定(谁先谁后都有可能);但是如果方法加了延时,那么一定是延时小的方法先执行

17.8、一个静态同步方法,一个普通同步方法,两个对象调用(延时有关)

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Lock1 lock11 = new Lock1();
        Lock1 lock12 = new Lock1();
        new Thread(() -> lock11.fun1()).start();
        new Thread(() -> lock12.fun2()).start();
    }
}

class Lock1 {
    public static synchronized void fun1() {
        System.out.println("fun1");
    }

    public synchronized void fun2() {
        System.out.println("fun2");
    }
}

执行结果:
在这里插入图片描述
在这里插入图片描述

fun1()的synchronized锁的是Lock1的Class类模板,fun2()的synchronized锁的是lock12对象,两个方法的锁锁的不是同一个东西,两个线程用的也不是同一把锁,因此与执行顺序没有关系,也与方法的调用者没有关系,由CPU调度决定(谁先谁后都有可能);但是如果方法加了延时,那么一定是延时小的方法先执行

JUC多线程 ( java.util.concurrent )

18、并发和并行

  • 并发:多个线程操作同一个资源,即在很短的时间段内交替执行多个任务
  • 并行:多核CPU同时执行,即在同一时刻同时执行多个任务
System.out.println(Runtime.getRuntime().availableProcessors()); //获取CPU的核数

19、线程安全的 Collection & Map

19.1、CopyOnWriteArraySet ( java.util.concurrent.CopyOnWriteArraySet )

public static void main(String[] args) {
    // java.util.ConcurrentModificationException 并发修改异常
    // Set<String> set = new HashSet<>();

    //解决方案 1.Collections.synchronizedSet
    // Set<String> set = Collections.synchronizedSet(new HashSet<>());
    
    //解决方案 2.CopyOnWriteArraySet
    Set<String> set = new CopyOnWriteArraySet<>();

    for (int i = 0; i < 100; i++) {
        new Thread(() -> {
            set.add(UUID.randomUUID().toString().substring(0, 5));
            System.out.println(set);
        }, String.valueOf(i)).start();
    }
}

19.2、CopyOnWriteArrayList ( java.util.concurrent.CopyOnWriteArrayList )

  • CopyOnWriteArrayList是一个ArrayList的线程安全的变体(用于读多写少的场合),其原理大概可以通俗的理解为:初始化的时候只有一个容器,很长一段时间,这个容器数据、数量等没有发生变化的时候,大家(多个线程)都是读取(假设这段时间里只发生读取的操作)同一个容器中的数据,所以这样大家读到的数据都是唯一、一致、安全的,但是后来有人往里面增加了一个数据,这个时候CopyOnWriteArrayList 底层实现添加的原理是先copy出一个容器(可以简称副本),再往新的容器里添加这个新的数据,最后把新的容器的引用地址赋值给了之前那个旧的的容器地址,但是在添加这个数据的期间,其他线程如果要去读取数据,仍然是读取到旧的容器里的数据
  • CopyOnWriteArrayList比Vector NB在哪里 :CopyOnWriteArrayList用的是ReentrantLock,Vector用的是synchronized(synchronized是重量级锁,ReentrantLock是轻量级锁),因此CopyOnWriteArrayList比Vector高效
public static void main(String[] args) {
    // java.util.ConcurrentModificationException 并发修改异常
    // List<String> list = new ArrayList<>();

    // 解决方案 1.Vector
    // List<String> list = new Vector<>();

    // 解决方案 2.Collections.synchronizedList
    //  List<String> list=Collections.synchronizedList(new ArrayList<>());

    // 解决方案 3.CopyOnWriteArrayList
    List<String> list = new CopyOnWriteArrayList<>(); // 写入时复制 (COW)

    for (int i = 0; i < 100; i++) {
        new Thread(() -> {
            list.add(UUID.randomUUID().toString().substring(0, 5));
            System.out.println(list);
        }, String.valueOf(i)).start();
    }
}

19.3、ConcurrentHashMap ( java.util.concurrent.ConcurrentHashMap )

public static void main(String[] args) {
    // java.util.ConcurrentModificationException 并发修改异常
    // Map<String, String> map = new HashMap<>();

    //解决方案 1.Collections.synchronizedMap
    // Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
    //解决方案 2.ConcurrentHashMap
    Map<String, String> map = new ConcurrentHashMap<>();

    for (int i = 0; i < 100; i++) {
        new Thread(() -> {
            map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
            System.out.println(map);
        }, String.valueOf(i)).start();
    }
}

20、BlockingQueue & BlockingDeque

20.1、阻塞队列 BlockingQueue ( java.util.concurrent.BlockingQueue )

public interface BlockingQueue<E> extends Queue<E>
方式① 抛出异常(常规)② 有返回值,不抛出异常③ 阻塞等待④ 超时等待
添加add(E e)offer(E e)put(E e)offer(E e, long timeout, TimeUnit unit)
移除remove()poll()take()poll(long timeout, TimeUnit unit)
获得队首元素element()peek()
// ①
ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.add("a")); // true
System.out.println(arrayBlockingQueue.add("b")); // true
System.out.println(arrayBlockingQueue.add("c")); // true
// System.out.println(arrayBlockingQueue.add("d")); // java.lang.IllegalStateException: Queue full
System.out.println(arrayBlockingQueue); // [a, b, c]
System.out.println(arrayBlockingQueue.element()); // a
System.out.println(arrayBlockingQueue.remove()); // a 
System.out.println(arrayBlockingQueue.remove()); // b 
System.out.println(arrayBlockingQueue.remove()); // c
// System.out.println(arrayBlockingQueue.remove()); // java.util.NoSuchElementException
System.out.println(arrayBlockingQueue); // []
//System.out.println(arrayBlockingQueue.element()); // java.util.NoSuchElementException
// ②
ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.offer("a")); // true
System.out.println(arrayBlockingQueue.offer("b")); // true
System.out.println(arrayBlockingQueue.offer("c")); // true
System.out.println(arrayBlockingQueue.offer("d")); // false
System.out.println(arrayBlockingQueue); // [a, b, c]
System.out.println(arrayBlockingQueue.peek()); // a
System.out.println(arrayBlockingQueue.poll()); // a
System.out.println(arrayBlockingQueue.poll()); // b
System.out.println(arrayBlockingQueue.poll()); // c
System.out.println(arrayBlockingQueue.poll()); // null
System.out.println(arrayBlockingQueue); // []
System.out.println(arrayBlockingQueue.peek()); // null
// ③
ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
arrayBlockingQueue.put("a");
arrayBlockingQueue.put("b");
arrayBlockingQueue.put("c");
// arrayBlockingQueue.put("d"); // 队列已满时,程序会一直卡在这里,直到其他线程有值出对
System.out.println(arrayBlockingQueue); // [a, b, c]

System.out.println(arrayBlockingQueue.take()); // a
System.out.println(arrayBlockingQueue.take()); // b
System.out.println(arrayBlockingQueue.take()); // c
// System.out.println(arrayBlockingQueue.take()); // 队列为空时,程序会一直卡在这里,直到其他线程有值入队
System.out.println(arrayBlockingQueue); // []
// ④
ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.offer("a")); // true
System.out.println(arrayBlockingQueue.offer("b")); // true
System.out.println(arrayBlockingQueue.offer("c")); // true
System.out.println(arrayBlockingQueue.offer("d", 2L, TimeUnit.SECONDS)); // false : 等待2秒,如果2秒后队列依旧是满的,返回false
System.out.println(arrayBlockingQueue); // [a, b, c]
System.out.println(arrayBlockingQueue.poll()); // a
System.out.println(arrayBlockingQueue.poll()); // b 
System.out.println(arrayBlockingQueue.poll()); // c
System.out.println(arrayBlockingQueue.poll(2L, TimeUnit.SECONDS)); // null : 等待2秒,如果2秒后队列依旧是空的,返回null
System.out.println(arrayBlockingQueue); // []
20.1.1、java.util.concurrent.ArrayBlockingQueue
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable 

随便举个例子:

BlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        try {
            TimeUnit.MILLISECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "  offer  " + i + "  " + arrayBlockingQueue.offer(String.valueOf(i)) + "   当前队列:" + arrayBlockingQueue);
    }
}).start();

new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        try {
            TimeUnit.MILLISECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "  poll " + arrayBlockingQueue.poll() + "   当前队列:" + arrayBlockingQueue);
    }
}).start();

运行结果:
在这里插入图片描述

可以看到,虽然队列初始长度很小,但是只要多个线程入队出队的速度够快,队列一般也不会被阻塞

20.1.2、java.util.concurrent.LinkedBlockingQueue

用法和ArrayBlockingQueue一样

20.1.3、同步阻塞队列SynchronousQueue ( java.util.concurrent.SynchronousQueue )
public class SynchronousQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable
  • SynchronousQueue是一种无缓冲的等待队列, 内部并没有数据缓存空间, 不允许使用null元素,不能调用peek()方法来看队列中是否有数据元素,队列也不能被遍历,一个put必须等待一个take
/**
 * Always returns {@code null}.
 * A {@code SynchronousQueue} does not return elements
 * unless actively waited on.
 *
 * @return {@code null}
 */
public E peek() {
    return null;
}

/**
 * Always returns zero.
 * A {@code SynchronousQueue} has no internal capacity.
 *
 * @return zero
 */
public int size() {
    return 0;
}

/**
 * Always returns {@code true}.
 * A {@code SynchronousQueue} has no internal capacity.
 *
 * @return {@code true}
 */
public boolean isEmpty() {
    return true;
}
  • SynchronousQueue非常适合于传递性设计,在这种设计中,在一个线程中运行的对象要将某些信息、事件或任务传递给在另一个线程中运行的对象,它就必须与该对象同步
  • 如果put与take数量不等,那么该队列就会处于阻塞状态
public class SynchronousQueueDemo {
    public static void main(String[] args) {
        SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();

        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    TimeUnit.MILLISECONDS.sleep(100);
                    System.out.println(Thread.currentThread().getName() + "  put " + i);
                    synchronousQueue.put(String.valueOf(i));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    TimeUnit.MILLISECONDS.sleep(100);
                    System.out.println(Thread.currentThread().getName() + "  get " + synchronousQueue.take());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

运行结果:
在这里插入图片描述

20.2、双端阻塞队列 BlockingDeque ( java.util.concurrent.BlockingDeque )

public interface BlockingDeque<E> extends BlockingQueue<E>, Deque<E> 
方式① 抛出异常(常规)② 有返回值,不抛出异常③ 阻塞等待④ 超时等待
添加到头部addFirst(E e) / add(E e)offerFirst(E e) / offer()putFirst(E e) / put()offerFirst(E e, long timeout, TimeUnit unit) / offer(E e, long timeout, TimeUnit unit)
添加到尾部addLast(E e)offerLast(E e)putLast(E e)offerLast(E e, long timeout, TimeUnit unit)
移除队首元素removeFirst() / remove()pollFirst() / poll()takeFirst() / take()pollFirst(long timeout, TimeUnit unit) / poll(long timeout, TimeUnit unit)
移除队尾元素removeLast()pollLast()takeLast()pollLast(long timeout, TimeUnit unit)
获得队首元素element()peekFirst() / peek()
获得队尾元素peekLast()
BlockingDeque<String> linkedBlockingDeque = new LinkedBlockingDeque<>(3);
new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "  offerFirst  " + i + "  " + linkedBlockingDeque.offerFirst(String.valueOf(i)) + "   当前队列:" + linkedBlockingDeque);
    }
}).start();

new Thread(() -> {
    for (int i = 5; i < 10; i++) {
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "  offerLast  " + i + "  " + linkedBlockingDeque.offerLast(String.valueOf(i)) + "   当前队列:" + linkedBlockingDeque);
    }
}).start();

new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        try {
            TimeUnit.MILLISECONDS.sleep(99);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "  pollFirst " + linkedBlockingDeque.pollFirst() + "   当前队列:" + linkedBlockingDeque);
    }
}).start();

new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        try {
            TimeUnit.MILLISECONDS.sleep(99);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "  pollLast " + linkedBlockingDeque.pollLast() + "   当前队列:" + linkedBlockingDeque);
    }
}).start();

运行结果:
在这里插入图片描述

21、Callable

@FunctionalInterface // 函数式接口
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}
// 例
new Thread(new FutureTask<String>(() -> Thread.currentThread().getName())).start();
/**************************************************************/
FutureTask<String> task = new FutureTask<>(() -> Thread.currentThread().getName());
Thread t1 = new Thread(task);
t1.start();
System.out.println(task.get()); // Thread-0
/**************************************************************/
// 上去看 2.3 ↑

22、常用辅助类

22.1、CountDownLatch ( java.util.concurrent.CountDownLatch )

CountDownLatch类是一个允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助类

public CountDownLatch(int count) 

线程通过调用countDown()方法让倒计时中的计数器count减去1,当计数器为0时,会唤醒因为调用了await()而阻塞的线程

22.1.1、基本用法
CountDownLatch countDownLatch = new CountDownLatch(5);
Thread t1 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName());
    countDownLatch.countDown();
});

Thread t2 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName());
    countDownLatch.countDown();
});

Thread t3 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName());
    countDownLatch.countDown();
});

Thread t4 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName());
    countDownLatch.countDown();
});

Thread t5 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName());
    countDownLatch.countDown();
});

t1.start();
t2.start();
t3.start();
t4.start();
t5.start();

countDownLatch.await();
System.out.println("exit");

执行结果:
在这里插入图片描述

countDownLatch会阻塞主线程输出"exit"直到计数器变为0,在这里初始化时指定了计数器,每条线程计数器减一,也就是说countDownLatch会阻塞着主线程直到上面五条线程都执行完(计数器为0)

22.1.2、CountDownLatch和join()的区别

上面的用join()同样也可以实现:

Thread t1 = new Thread(() -> System.out.println(Thread.currentThread().getName()));
Thread t2 = new Thread(() -> System.out.println(Thread.currentThread().getName()));
Thread t3 = new Thread(() -> System.out.println(Thread.currentThread().getName()));
Thread t4 = new Thread(() -> System.out.println(Thread.currentThread().getName()));
Thread t5 = new Thread(() -> System.out.println(Thread.currentThread().getName()));

t1.start();
t2.start();
t3.start();
t4.start();
t5.start();

t1.join();
t2.join();
t3.join();
t4.join();
t5.join();

System.out.println("exit");

执行结果:
在这里插入图片描述

join()同样会阻塞主线程直到上面五条线程都执行完毕再执行

那么CountDownLatch和join()的区别在哪呢?

CountDownLatch countDownLatch = new CountDownLatch(5);

Thread t1 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    countDownLatch.countDown();
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t2 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    countDownLatch.countDown();
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t3 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    countDownLatch.countDown();
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t4 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    countDownLatch.countDown();
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t5 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    countDownLatch.countDown();
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

t1.start();
t2.start();
t3.start();
t4.start();
t5.start();

countDownLatch.await();//等待计数器归零然后再向下执行
System.out.println("exit");

执行结果:
在这里插入图片描述

这段代码会让主线程在所有子线程的第一个输出(“before”)执行完后就执行,不必等待子线程的第二个输出(“after”)执行完成;而这种情形是join()无法做到的:

Thread t1 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t2 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t3 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t4 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t5 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

t1.start();
t2.start();
t3.start();
t4.start();
t5.start();

t1.join();
t2.join();
t3.join();
t4.join();
t5.join();

System.out.println("exit");

执行结果:
在这里插入图片描述

这段代码的join()版本执行出来的结果只能是这样,即主线程必须等待子线程全部执行完毕后才可以执行。

这样一看,CountDownLatch和join()的区别就显而易见了:

区别:

调用thread.join()方法必须等待thread执行完毕,当前线程才能继续往下执行,而CountDownLatch通过计数器提供了更灵活的控制,只要检测到计数器为0当前线程就可以往下执行而不用管相应的thread是否执行完毕

22.2、CyclicBarrier ( java.util.concurrent.CyclicBarrier )

CyclicBarrier类是一个允许一组线程全部等待彼此达到共同屏障点的同步辅助类

public CyclicBarrier(int parties, Runnable barrierAction)
public CyclicBarrier(int parties)

parties:是参与线程的个数

barrierAction:Runnable接口的实现类,是最后一个到达线程要做的任务

只有调用await()方法的线程数量等于parties时,创建CyclicBarrier的线程才会被唤醒,执行barrierAction

22.2.1、基本用法

CyclicBarrier实现【让主线程在所有子线程的第一个输出(“before”)执行完后就执行,不必等待子线程的第二个输出(“after”)执行完成】:

CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> System.out.println("exit"));

Thread t1 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        cyclicBarrier.await();
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t2 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        cyclicBarrier.await();
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t3 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        cyclicBarrier.await();
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t4 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        cyclicBarrier.await();
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t5 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        cyclicBarrier.await();
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

t1.start();
t2.start();
t3.start();
t4.start();
t5.start();

执行结果:
在这里插入图片描述

22.2.2、CyclicBarrier和CountDownLatch的区别

CyclicBarrier可以实现每5个子线程在输出(“before”)后就执行一次(“exit”):

CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> System.out.println("exit"));

Thread t1 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        cyclicBarrier.await();
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t2 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        cyclicBarrier.await();
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t3 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        cyclicBarrier.await();
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t4 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        cyclicBarrier.await();
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t5 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        cyclicBarrier.await();
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t6 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        cyclicBarrier.await();
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t7 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        cyclicBarrier.await();
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t8 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        cyclicBarrier.await();
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t9 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        cyclicBarrier.await();
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t10 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    try {
        cyclicBarrier.await();
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

t1.start();
t2.start();
t3.start();
t4.start();
t5.start();

t6.start();
t7.start();
t8.start();
t9.start();
t10.start();

执行结果:
在这里插入图片描述

CountDownLatch实现:

CountDownLatch countDownLatch1 = new CountDownLatch(5);
CountDownLatch countDownLatch2 = new CountDownLatch(5);

Thread t1 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    countDownLatch1.countDown();
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t2 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    countDownLatch1.countDown();
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t3 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    countDownLatch1.countDown();
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t4 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    countDownLatch1.countDown();
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t5 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    countDownLatch1.countDown();
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t6 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    countDownLatch2.countDown();
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t7 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    countDownLatch2.countDown();
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t8 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    countDownLatch2.countDown();
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t9 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    countDownLatch2.countDown();
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

Thread t10 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "    before");
    countDownLatch2.countDown();
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    after");
});

t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
countDownLatch1.await();
System.out.println("exit");

t6.start();
t7.start();
t8.start();
t9.start();
t10.start();
countDownLatch2.await();
System.out.println("exit");

执行结果:
在这里插入图片描述

CountDownLatch想要实现这个效果就必须new出新的CountDownLatch对象来

区别:

  • CountDownLatch是一次性的,即每个CountDownLatch对象都是一次性的,当他的计数器count减为0、阻塞线程被唤醒后,这个CountDownLatch对象就没用了,想要重新实现计数等待功能就必须重新new一个CountDownLatch对象出来才行;而CyclicBarrier则是可循环利用的,只需要new一个对象就够了
  • CountDownLatch参与的线程的职责是不一样的,有的在倒计时,有的在等待倒计时结束;而CyclicBarrier参与的线程职责是一样的

22.3、Semaphore ( java.util.concurrent.Semaphore )

信号量, 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源

public Semaphore(int permits)
public Semaphore(int permits, boolean fair)
22.3.1、基本用法

模拟“抢车位”

Semaphore semaphore = new Semaphore(3);

for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        try {
            System.out.println(Thread.currentThread().getName() + " 来到停车场" );
            if (semaphore.availablePermits() == 0) {
                System.out.println("车位不足, " + Thread.currentThread().getName() + " 请耐心等待");
            }else{
                System.out.println("空余车位数量:" + semaphore.availablePermits());
            }
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + " 成功进入停车场");
            TimeUnit.MILLISECONDS.sleep(1000);
            semaphore.release();
            System.out.println(Thread.currentThread().getName() + " 驶出停车场,等待的车的数量:" + semaphore.getQueueLength());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
}

执行结果:
在这里插入图片描述

23、线程池相关

在这里插入图片描述

三大方法、七大参数、四种拒绝策略

  • 三大方法:Executors创建线程池的三个方法
  • 七大参数:ThreadPoolExecutor方法的七个参数
  • 四种拒绝策略:ThreadPoolExecutor方法的最后一个参数( RejectedExecutionHandler handler )的四种拒绝策略

23.1、Executor ( java.util.concurrent.Executor )

Executor接口定义了一个接收Runnable对象的方法executor(Runnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现了Runnable接口的类,一般来说,Runnable任务开辟在新线程中的使用方法为:new Thread(Runnable实现类实例).start(),但在Executor中,可以使用Executor而不用显示地创建线程:executor.execute(Runnable实现类实例)

public interface Executor {
    void execute(Runnable command);
}
public class ExecutorDemo {
    public static void main(String[] args) {
        new MyExecutor().execute(() -> System.out.println(Thread.currentThread().getName()));
    }
}

class MyExecutor implements Executor {
    @Override
    public void execute(Runnable command) {
        new Thread(command).start();
    }
}

在这里插入图片描述

23.2、ExecutorService ( java.util.concurrent.ExecutorService )

  • ExecutorService是一个比Executor使用更广泛的子类接口,提供了生命周期管理的方法,返回 Future 对象,可以跟踪一个或多个异步任务执行状况返回Future的方法,可以调用ExecutorService的shutdown()方法来平滑地关闭 ExecutorService,调用该方法后,将导致ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。因此我们一般用该接口来实现和管理多线程
  • 通过ExecutorService.submit()方法返回的Future对象,可以调用isDone()方法查询Future是否已经完成。当任务完成时,它具有一个结果,可以调用get()方法来获取该结果。也可以不用isDone()进行检查就直接调用get()获取结果,在这种情况下,get()将阻塞,直至结果准备就绪,还可以取消任务的执行。Future提供了cancel()方法用来取消执行pending中的任务
public interface ExecutorService extends Executor {
    /* ... */
	void shutdown();
	<T> Future<T> submit(Callable<T> task);
	<T> Future<T> submit(Runnable task, T result);
	<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;
    /* ... */
}

23.3、ThreadPoolExecutor ( java.util.concurrent.ThreadPoolExecutor )

public interface ExecutorService extends Executor 
public abstract class AbstractExecutorService implements ExecutorService
public class ThreadPoolExecutor extends AbstractExecutorService
public ThreadPoolExecutor(int corePoolSize, 
                       int maximumPoolSize,
                       long keepAliveTime,
                       TimeUnit unit,
                       BlockingQueue<Runnable> workQueue,
                       ThreadFactory threadFactory,
                       RejectedExecutionHandler handler)
  1. corePoolSize:核心线程数

    • 核心线程会一直存活,及时没有任务需要执行
    • 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
  2. maxPoolSize:最大线程数

    • 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务

    • 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常

    • 最大核心线程池大小maximumPoolSize如何定义:

      • CPU密集型任务:本机CPU几核就定为几,可以保证效率最高

        int maximumPoolSize = Runtime.getRuntime().availableProcessors();
        
      • IO密集型任务:判断程序中十分耗IO的线程,大于这个数(一般指定为2倍)

  3. keepAliveTime:线程空闲时间

    • 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
  4. unit:keepAliveTime时间单位

  5. workQueue:阻塞队列

  6. threadFactory:线程工厂

  7. handler:拒绝策略; 当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给handler来处理

    • new ThreadPoolExecutor.AbortPolicy() //抛出异常 java.util.concurrent.RejectedExecutionException (默认)

      public static class AbortPolicy implements RejectedExecutionHandler
      
    • new ThreadPoolExecutor.DiscardOldestPolicy() //不抛异常,尝试去和最早的去竞争,竞争失败再丢掉任务

      public static class DiscardOldestPolicy implements RejectedExecutionHandler
      
    • new ThreadPoolExecutor.DiscardPolicy() //丢掉任务,不抛异常

      public static class DiscardPolicy implements RejectedExecutionHandler
      
    • new ThreadPoolExecutor.CallerRunsPolicy() //哪来的回哪里(比如返回给main线程去处理)

      public static class CallerRunsPolicy implements RejectedExecutionHandler 
      

四种拒绝策略:

public class ExecutorServiceDemo {
    private static ThreadPoolExecutor getPool(RejectedExecutionHandler handler) {
        return new ThreadPoolExecutor(2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                handler
        );
    }

    private static void doExe(ThreadPoolExecutor t) {
        try {
            // 最大承载 8 = maximumPoolSize=5 + workQueue=3
            // 这里设置10条线程就是为了看看handler是如何处理多出来的两条线程的
            for (int i = 1; i <= 10; i++) {
                int finalI = i;
                t.execute(() -> {
                    try {
                        TimeUnit.MILLISECONDS.sleep(1900);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(finalI + ":  " + Thread.currentThread().getName());
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            t.shutdown();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("不抛异常,尝试去和最早的去竞争");
        doExe(getPool(new ThreadPoolExecutor.DiscardOldestPolicy()));
        TimeUnit.SECONDS.sleep(5);
        System.out.println("丢掉任务,不抛异常");
        doExe(getPool(new ThreadPoolExecutor.DiscardPolicy()));
        TimeUnit.SECONDS.sleep(5);
        System.out.println("哪来的回哪里(这里就是返回给main线程去处理)");
        doExe(getPool(new ThreadPoolExecutor.CallerRunsPolicy()));
        TimeUnit.SECONDS.sleep(5);
        System.out.println("抛出异常");
        doExe(getPool(new ThreadPoolExecutor.AbortPolicy()));
    }
}

在这里插入图片描述

23.4、Executors ( java.util.concurrent.Executors )

Executors类提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口

public class Executors
23.4.1、newSingleThreadExecutor
//创建单个线程
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
      (new ThreadPoolExecutor(1, 1,
                              0L, TimeUnit.MILLISECONDS,
                              new LinkedBlockingQueue<Runnable>()));
}
public static void main(String[] args) {
    ExecutorService threadPool = Executors.newSingleThreadExecutor(); //创建单个线程
    try {
        for (int i = 0; i < 10; i++) {
            threadPool.execute(() -> System.out.println(Thread.currentThread().getName()));
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        threadPool.shutdown();
    }
}

执行结果:
在这里插入图片描述

23.4.2、newFixedThreadPool
//创建固定线程池的大小
public static ExecutorService newFixedThreadPool(int nThreads) {
	return new ThreadPoolExecutor(nThreads, nThreads,
                            	0L, TimeUnit.MILLISECONDS,
                            	new LinkedBlockingQueue<Runnable>());
}
public static void main(String[] args) {
    ExecutorService threadPool = Executors.newFixedThreadPool(5); //创建固定线程池的大小
    try {
        for (int i = 0; i < 10; i++) {
            threadPool.execute(() -> System.out.println(Thread.currentThread().getName()));
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        threadPool.shutdown();
    }
}

执行结果:
在这里插入图片描述

23.4.3、newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
public static void main(String[] args) {
    ExecutorService threadPool = Executors.newCachedThreadPool(); //可伸缩
    try {
        for (int i = 0; i < 100; i++) {
            threadPool.execute(() -> System.out.print(Thread.currentThread().getName() + "      "));
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        threadPool.shutdown();
    }
}

执行结果:
在这里插入图片描述

不要使用Executors去创建线程

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允许的请求队列长度workQueue为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
2)CachedThreadPool:允许的创建线程数量maximumPoolSize为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM

因此创建线程时应使用ThreadPoolExecutor(Executors创建线程也是通过它实现的)

24、分支合并 ForkJoin ( java.util.concurrent.ForkJoin )

工作窃取:以完成的线程窃取未完成的线程的任务处理来提高效率

ForkJoinPool: task要通过ForkJoinPool来执行,分割的子任务也会添加到当前工作线程的双端队列中,
进入队列的头部。当一个工作线程中没有任务时,会从其他工作线程的队列尾部获取一个任务(工作窃取)

public interface ExecutorService extends Executor
public abstract class AbstractExecutorService implements ExecutorService
public class ForkJoinPool extends AbstractExecutorService
//invoke(ForkJoinTask)有join, task会被同步到主进程
public <T> T invoke(ForkJoinTask<T> task) {
    if (task == null)
        throw new NullPointerException();
    externalPush(task);
    return task.join();
}

//submit(ForkJoinTask)异步执行,有task返回值,可通过task.get实现同步到主线程
public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) {
    if (task == null)
        throw new NullPointerException();
    externalPush(task);
    return task;
}

//execute(ForkJoinTask)异步执行task,无返回值
public void execute(ForkJoinTask<?> task) {
    if (task == null)
        throw new NullPointerException();
    externalPush(task);
}
public interface Future<V>
public abstract class ForkJoinTask<V> implements Future<V>, Serializable
//分支
public final ForkJoinTask<V> fork() {
    Thread t;
    if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
        ((ForkJoinWorkerThread)t).workQueue.push(this);
    else
        ForkJoinPool.common.externalPush(this);
    return this;
}

//合并
public final V join() {
    int s;
    if ((s = doJoin() & DONE_MASK) != NORMAL)
        reportException(s);
    return getRawResult();
}

计算1~20亿的和:

class MyForkJoin extends RecursiveTask<Long> {
    private long start, end, tmp = 1000_0000L;

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

    @Override
    protected Long compute() {
        if (end - start < tmp) {
            long sum = 0L;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else {
            long mid = (start + end) / 2;
            ForkJoinTask<Long> task1 = new MyForkJoin(start, mid);
            ForkJoinTask<Long> task2 = new MyForkJoin(mid + 1, end);
            task1.fork();
            task2.fork();
            // invokeAll(task1,task2);
            return task1.join() + task2.join();
        }

    }
}
public class ForkJoinDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ForkJoinPool pool = new ForkJoinPool();
        ForkJoinTask<Long> task = new MyForkJoin(1L, 20_0000_0000L);
        System.out.println(pool.invoke(task));
        System.out.println(pool.submit(task).get());
    }
}

25、异步回调 CompletableFuture ( java.util.concurrent.CompletableFuture )

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> 
//没有返回值的异步回调
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "   runAsync=>Void");
});
System.out.println(Thread.currentThread().getName());
completableFuture.get();


//有返回值的异步回调
CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "   runAsync=>String");
    int i = 1 / 0;
    return Thread.currentThread().getName();
});
completableFuture1.whenComplete((t, u) -> {
    System.out.println("t:   " + t);
    System.out.println("u:   " + u);
}).exceptionally(Throwable::toString).get();

执行结果:
在这里插入图片描述

26、JMM ( java memory model ):Java内存模型

在这里插入图片描述

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)

    • lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态
    • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
    • read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
    • load(载入):作用于工作内存的变量,它把read操作的变量从主存中放入工作内存中
    • use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
    • assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
    • store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
    • write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

JMM对这八种指令的使用,制定了如下规则:

    • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
    • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
    • 不允许一个线程将没有assign的数据从工作内存同步回主内存
    • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
    • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
    • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
    • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
    • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

27、volatile

  • 保证可见性
// main线程将num赋为1,但是子线程并不知道,所以会处于死循环
// private static int num = 0;

// volatile可以保证num在主他线程发生变化时通知子线程,终止循环
volatile private static int num = 0;

public static void main(String[] args) throws InterruptedException {
 new Thread(() -> {
     while (num == 0) {
     }
 }).start();
 TimeUnit.MILLISECONDS.sleep(1000);
 num = 1;
 System.out.println(num);
}
  • 不保证原子性
// 不管有没有volatile,最后结果都有可能不是20000
volatile private static int num = 0;
//AtomicInteger可以保证原子性
//private static AtomicInteger num = new AtomicInteger();

//synchronized或lock都可以保证原子性
public static /* synchronized */ void add() {
 num++; //自增操作在JVM并不是原子操作
}

public static void main(String[] args) throws InterruptedException {
 //理论上num=20000
 //实际上大概率num!=20000
 for (int i = 0; i < 20; i++) {
     new Thread(() -> {
         for (int j = 0; j < 1000; j++) {
             add();
         }
     }).start();
 }
 while (Thread.activeCount() > 2) {
     Thread.yield();
 }
 System.out.println("num:" + num);
}
  • 禁止指令重排

源代码 -> 编译器优化 -> 指令并行重排 -> 内存系统重排 -> 执行

public class L {
 //加上 volatile 可以防止指令重排 
 /* volatile */  private static int a = 0;
 /* volatile */  private static int b = 0;
 /* volatile */  private static int x = 0;
 /* volatile */  private static int y = 0;

 public static void main(String[] args) throws InterruptedException {
     int i = 0;
     while (true) {
         a = 0;
         b = 0;
         x = 0;
         y = 0;
         Thread t1 = new Thread(() -> {
             b = 1;
             y = a;
         });

         Thread t2 = new Thread(() -> {
             a = 1;
             x = b;
         });

         t1.start();
         t2.start();

         while (Thread.activeCount() > 2) {
             Thread.yield();
         }

         if (x == 0 && y == 0) {
             System.out.println("第 " + i + " 次时发生了指令重排");
             break;
         } else if (x == 1 && y == 0) {
             System.out.println("第 " + i + " 次时线程1先执行");
         } else if (x == 0 && y == 1) {
             System.out.println("第 " + i + " 次时线程2先执行");
         } else if (x == 1 && y == 1) {
             System.out.println("第 " + i + " 次时线程1和线程2交替执行");
         }
         i++;
     }
 }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

28、atomic ( java.util.concurrent.atomic )

java.util.concurrent.atomic包下的类都可以保证volatile不能保证的原子性

在这里插入图片描述

28.1、compareAndSet ( CAS )

比较并交换

public final boolean compareAndSet(int expect, int update) {
    //                             期望值  ↑  ,  ↑  更新值
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

如果期望值达到了就更新,否则就不更新

AtomicInteger atomicInteger = new AtomicInteger(2020);
System.out.println(atomicInteger.get()); //2020
System.out.println(atomicInteger.compareAndSet(2020, 2021)); //true
System.out.println(atomicInteger.get()); //2021
System.out.println(atomicInteger.compareAndSet(2020, 2022)); //false
System.out.println(atomicInteger.get()); //2021

28.2、AtomicReference ( java.util.concurrent.atomic.AtomicReference )

28.2.1、ABA问题

在单线程中,这样的CAS操作是没有问题的:

AtomicReference<String> atomicReference = new AtomicReference<>("A");
System.out.println(atomicReference.get()); // A
System.out.println(atomicReference.compareAndSet("A", "C")); // true
System.out.println(atomicReference.get()); // C

即便是中间的"A"曾经短暂的变为了其他值(“B”),也不会影响最终的结果:

AtomicReference<String> atomicReference = new AtomicReference<>("A");
System.out.println(atomicReference.get()); // A
System.out.println(atomicReference.compareAndSet("A", "B")); // true
System.out.println(atomicReference.get()); // B
System.out.println(atomicReference.compareAndSet("B", "A")); // true
System.out.println(atomicReference.get()); // A
System.out.println(atomicReference.compareAndSet("A", "C")); // true
System.out.println(atomicReference.get()); // C

但是在多线程的情况下,这样是有问题的:

AtomicReference<String> atomicReference = new AtomicReference<>("A");
System.out.println(Thread.currentThread().getName() + "    " + atomicReference.get()); // main    A

// 狸猫换太子
Thread t1 = new Thread(() -> {
    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "    " + atomicReference.compareAndSet("A", "B") + "    " + atomicReference.get()); // Thread-0    true    B
    System.out.println(Thread.currentThread().getName() + "    " + atomicReference.compareAndSet("B", "A") + "    " + atomicReference.get()); // Thread-1    true    A
});

t1.start();
t1.join();

// 此"A"非彼"A"
System.out.println(Thread.currentThread().getName() + "    " + atomicReference.compareAndSet("A", "C")+ "    " + atomicReference.get()); // main    true    C

最后的atomicReference.compareAndSet("A", "C")操作想要的"A"其实应该是main线程中的"A"而不是Thread-0线程的"A",但是在这里也交换成功了,这其实不是我们想要的结果

28.3、AtomicStampedReference ( java.util.concurrent.atomic.AtomicStampedReference )

在SQL中,ABA问题可以通过乐观锁解决

在java中:(java实现乐观锁)

public AtomicStampedReference(V initialRef, int initialStamp) {
    pair = Pair.of(initialRef, initialStamp);
    //             初始值 ↑   ,  ↑ 初始时间戳(版本号)
}
public boolean compareAndSet(V   expectedReference, //期望值
                             V   newReference,      //新值
                             int expectedStamp,     //期望时间戳(版本号)
                             int newStamp           //更新时间戳(版本号)
                            ) {      
    Pair<V> current = pair;
    return
        expectedReference == current.reference &&
        expectedStamp == current.stamp &&
        ((newReference == current.reference &&
          newStamp == current.stamp) ||
         casPair(current, Pair.of(newReference, newStamp)));
}
28.3.1、解决ABA问题

用带时间戳(版本控制)的原子引用类AtomicStampedReference来解决ABA问题:

AtomicStampedReference<String> atomicStampedReference = new AtomicStampedReference<>("A", 1);
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "    " + atomicStampedReference.getReference() + "    " + stamp); // main    A    1

Thread t1 = new Thread(() -> {
    try {
        TimeUnit.MILLISECONDS.sleep(100);
        System.out.println(Thread.currentThread().getName() + "    " + atomicStampedReference.compareAndSet("A", "B", atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1) + "    " + atomicStampedReference.getReference() + "    " + atomicStampedReference.getStamp()); // Thread-0    true    B    2
        System.out.println(Thread.currentThread().getName() + "    " + atomicStampedReference.compareAndSet("B", "A", atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1) + "    " + atomicStampedReference.getReference() + "    " + atomicStampedReference.getStamp()); // Thread-0    true    A    3
    } catch (Exception e) {
        e.printStackTrace();
    }
});
       
t1.start();
t1.join();
System.out.println(Thread.currentThread().getName() + "    " + atomicStampedReference.compareAndSet("A", "C", stamp, stamp + 1) + "    " + atomicStampedReference.getReference() + "    期待的版本号:" + stamp + "   实际的版本号:" + atomicStampedReference.getStamp()); // main    false    A    期待的版本号:1   实际的版本号:3

发现了 此"A"非彼"A"(版本号对不上),修改失败

29、locks ( java.util.concurrent.locks )

29.1、Lock ( java.util.concurrent.locks.Lock )

public interface Lock {
    /* ... */
    void lock();
    void unlock();
    /* ... */
}

使用方法:

lock.lock();
try {
    /* ... */
} catch (Exception e) {
    e.printStackTrace();
} finally {
    lock.unlock();
}
29.1.1、ReentrantLock ( java.util.concurrent.locks.ReentrantLock )

可重入锁

public class ReentrantLock implements Lock, java.io.Serializable {
    /* ... */
    //默认是非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }

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

公平锁和非公平锁:

  • 非公平锁:
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

新进线程判断当前锁的占用状态为0后,会直接compareAndSetState尝试取获取锁,而不是进入等待池去排队,这样就可能导致在等待池中排队的线程长时间处于阻塞状态(非公平锁就是纵容插队),但是这样也有一个好处就是效率会高一点快一点


  • 公平锁:
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

新进线程判断当前锁的占用状态为0后,会继续通过hasQueuedPredecessors判断当前的等待池(等待队列)中是否有排队的线程,如果没有才会尝试去获取锁,否则就去排队。这样遵循了FIFO原则,但是效率会低一点慢一点,因为会增加上下文切换与等待线程状态变换的时间

可重入锁的理解:

进了自己家大门,就自动获取到了自己家里小门的锁;同时,要出门的话,需要先锁上家里的小门,再锁上家里的大门(前提是家里只能进一个人)

synchronized就是这个原理

public class ReentrantLockSync {
    public static void main(String[] args) {
        enterDoor ed = new enterDoor();
        new Thread(() -> ed.enterBigDoor(), "线程1").start();
        new Thread(() -> ed.enterBigDoor(), "线程2").start();
    }
}

class enterDoor {
    public synchronized void enterBigDoor() {
        System.out.println(Thread.currentThread().getName() + "   进入大门");
        enterSmallDoor();
    }

    public synchronized void enterSmallDoor() {
        System.out.println(Thread.currentThread().getName() + "   进入小门");
    }
}

在这里插入图片描述

线程1进入大门后同时也拿到了小门的锁,此时线程2是不可能进门的,只能在门外等着(阻塞)

ReentrantLock()可以看地更直观:

public class ReentrantLockLock {
    public static void main(String[] args) {
        enterDoor ed = new enterDoor();
        new Thread(() -> ed.enterBigDoor(), "线程1").start();
        new Thread(() -> ed.enterBigDoor(), "线程2").start();
    }
}

class enterDoor {
    ReentrantLock lock = new ReentrantLock();

    public void enterBigDoor() {
        lock.lock(); //第一步
        try {
            System.out.println(Thread.currentThread().getName() + "   进入大门");
            enterSmallDoor();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); //第四步
            System.out.println(Thread.currentThread().getName() + "   离开大门");
        }
    }

    public void enterSmallDoor() {
        lock.lock(); //第二部
        try {
            System.out.println(Thread.currentThread().getName() + "   进入小门");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); //第三步
            System.out.println(Thread.currentThread().getName() + "   离开小门");
        }
    }
}

在这里插入图片描述

29.2、ReadWriteLock ( java.util.concurrent.locks.ReadWriteLock )

读写锁

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}
29.2.1、ReentrantReadWriteLock ( java.util.concurrent.locks.ReentrantReadWriteLock )

可重入读写锁

与可重入锁一样,可重入读写锁也提供公平和非公平两种策略,默认非公平策略

可重入读写锁多了两个方法,分别是读锁readLock()和写锁writeLock()

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable{
    /* ... */
    
    private final ReentrantReadWriteLock.ReadLock readerLock;
    private final ReentrantReadWriteLock.WriteLock writerLock;
    
    public ReentrantReadWriteLock() {
        this(false);
    }
    
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
    
    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock readLock()  { return readerLock; }
    
    /* ... */
}

举例:

这里有一个笔记类Note:

class Note {
    private String content;

    public Note(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

这是一个不安全的写操作:

class WithoutRWLock {
    volatile private Note note;

    public WithoutRWLock(Note note) {
        this.note = note;
    }

    public void write(String s) {
        System.out.println(Thread.currentThread().getName() + "   开始写入 " + s);
        note.setContent(s);
        System.out.println(Thread.currentThread().getName() + "   写入" + s + "完成");

    }

    public void read() {
        System.out.println(Thread.currentThread().getName() + "   开始读取");
        String s = note.getContent();
        System.out.println(Thread.currentThread().getName() + "   读取完成,读取到 " + s);
    }
}

运行出来的结果就是:

public static void main(String[] args) {
    Note note = new Note("");
    WithoutRWLock withoutRWLock = new WithoutRWLock(note);
    for (int i = 0; i < 50; i++) {
        int finalI = i;
        new Thread(() -> withoutRWLock.write("note" + finalI), "写线程" + i).start();
    }

    for (int i = 0; i < 50; i++) {
        new Thread(() -> withoutRWLock.read(), "读线程" + i).start();
    }
}

运行结果:

写线程0   开始写入 note0
写线程1   开始写入 note1
写线程1   写入note1完成
写线程0   写入note0完成
写线程2   开始写入 note2
写线程2   写入note2完成
写线程3   开始写入 note3
写线程3   写入note3完成
写线程4   开始写入 note4
写线程4   写入note4完成
写线程6   开始写入 note6
写线程6   写入note6完成
写线程7   开始写入 note7
写线程7   写入note7完成
写线程8   开始写入 note8
写线程8   写入note8完成
写线程9   开始写入 note9
写线程9   写入note9完成
写线程5   开始写入 note5
写线程5   写入note5完成
写线程10   开始写入 note10
写线程10   写入note10完成
写线程11   开始写入 note11
写线程11   写入note11完成
写线程12   开始写入 note12
写线程13   开始写入 note13
写线程13   写入note13完成
写线程12   写入note12完成
写线程15   开始写入 note15
写线程15   写入note15完成
写线程16   开始写入 note16
写线程17   开始写入 note17
写线程16   写入note16完成
写线程17   写入note17完成
写线程18   开始写入 note18
写线程18   写入note18完成
写线程19   开始写入 note19
写线程19   写入note19完成
写线程20   开始写入 note20
写线程20   写入note20完成
写线程21   开始写入 note21
写线程22   开始写入 note22
写线程21   写入note21完成
写线程22   写入note22完成
写线程23   开始写入 note23
写线程23   写入note23完成
写线程24   开始写入 note24
写线程24   写入note24完成
写线程25   开始写入 note25
写线程25   写入note25完成
写线程26   开始写入 note26
写线程26   写入note26完成
写线程27   开始写入 note27
写线程28   开始写入 note28
写线程27   写入note27完成
写线程28   写入note28完成
写线程29   开始写入 note29
写线程29   写入note29完成
写线程30   开始写入 note30
写线程31   开始写入 note31
写线程31   写入note31完成
写线程30   写入note30完成
写线程14   开始写入 note14
写线程14   写入note14完成
写线程32   开始写入 note32
写线程32   写入note32完成
写线程33   开始写入 note33
写线程33   写入note33完成
写线程34   开始写入 note34
写线程34   写入note34完成
写线程35   开始写入 note35
写线程35   写入note35完成
写线程36   开始写入 note36
写线程36   写入note36完成
写线程37   开始写入 note37
写线程37   写入note37完成
写线程38   开始写入 note38
写线程38   写入note38完成
写线程39   开始写入 note39
写线程39   写入note39完成
写线程40   开始写入 note40
写线程40   写入note40完成
写线程41   开始写入 note41
写线程41   写入note41完成
写线程43   开始写入 note43
写线程43   写入note43完成
写线程44   开始写入 note44
写线程44   写入note44完成
读线程0   开始读取
读线程0   读取完成,读取到 note44
写线程46   开始写入 note46
写线程46   写入note46完成
写线程45   开始写入 note45
写线程45   写入note45完成
写线程49   开始写入 note49
写线程47   开始写入 note47
写线程47   写入note47完成
写线程48   开始写入 note48
写线程48   写入note48完成
读线程1   开始读取
读线程2   开始读取
读线程30   开始读取
读线程30   读取完成,读取到 note48
读线程3   开始读取
读线程3   读取完成,读取到 note48
读线程4   开始读取
读线程5   开始读取
读线程5   读取完成,读取到 note48
读线程6   开始读取
读线程6   读取完成,读取到 note48
读线程7   开始读取
读线程7   读取完成,读取到 note48
读线程8   开始读取
读线程8   读取完成,读取到 note48
读线程9   开始读取
读线程9   读取完成,读取到 note48
读线程12   开始读取
读线程12   读取完成,读取到 note48
读线程29   开始读取
读线程10   开始读取
读线程13   开始读取
读线程13   读取完成,读取到 note48
读线程14   开始读取
读线程14   读取完成,读取到 note48
读线程15   开始读取
读线程15   读取完成,读取到 note48
读线程36   开始读取
读线程17   开始读取
读线程16   开始读取
读线程18   开始读取
读线程18   读取完成,读取到 note48
读线程20   开始读取
读线程20   读取完成,读取到 note48
读线程19   开始读取
读线程19   读取完成,读取到 note48
读线程21   开始读取
读线程22   开始读取
读线程22   读取完成,读取到 note48
读线程23   开始读取
读线程23   读取完成,读取到 note48
读线程24   开始读取
读线程24   读取完成,读取到 note48
读线程25   开始读取
读线程25   读取完成,读取到 note48
读线程26   开始读取
读线程26   读取完成,读取到 note48
读线程27   开始读取
读线程27   读取完成,读取到 note48
读线程28   开始读取
读线程28   读取完成,读取到 note48
写线程42   开始写入 note42
读线程21   读取完成,读取到 note48
读线程16   读取完成,读取到 note48
读线程17   读取完成,读取到 note48
读线程36   读取完成,读取到 note48
读线程35   开始读取
读线程35   读取完成,读取到 note42
读线程34   开始读取
读线程34   读取完成,读取到 note42
读线程10   读取完成,读取到 note48
读线程29   读取完成,读取到 note48
读线程33   开始读取
读线程32   开始读取
读线程32   读取完成,读取到 note42
读线程31   开始读取
读线程4   读取完成,读取到 note48
读线程2   读取完成,读取到 note48
读线程1   读取完成,读取到 note48
读线程11   开始读取
读线程11   读取完成,读取到 note42
写线程49   写入note49完成
读线程31   读取完成,读取到 note42
读线程33   读取完成,读取到 note42
写线程42   写入note42完成
读线程37   开始读取
读线程37   读取完成,读取到 note42
读线程38   开始读取
读线程38   读取完成,读取到 note42
读线程39   开始读取
读线程39   读取完成,读取到 note42
读线程40   开始读取
读线程40   读取完成,读取到 note42
读线程41   开始读取
读线程41   读取完成,读取到 note42
读线程42   开始读取
读线程42   读取完成,读取到 note42
读线程43   开始读取
读线程43   读取完成,读取到 note42
读线程44   开始读取
读线程44   读取完成,读取到 note42
读线程46   开始读取
读线程46   读取完成,读取到 note42
读线程47   开始读取
读线程47   读取完成,读取到 note42
读线程48   开始读取
读线程48   读取完成,读取到 note42
读线程49   开始读取
读线程49   读取完成,读取到 note42
读线程45   开始读取
读线程45   读取完成,读取到 note42

可以看到,在读和写的过程中都有线程插队,写线程写的过程中被写线程插队可能会出现数据不一致的情况;读线程在读的过程中被写线程插队可能会出现脏读的情况


读写锁使它变安全:

class WithRWLock {
    volatile private Note note;
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public WithRWLock(Note note) {
        this.note = note;
    }

    public void write(String s) {
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "   开始写入 " + s);
            note.setContent(s);
            System.out.println(Thread.currentThread().getName() + "   写入" + s + "完成");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    public void read() {
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "   开始读取");
            String s = note.getContent();
            System.out.println(Thread.currentThread().getName() + "   读取完成,读取到 " + s);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

运行程序查看结果:

public static void main(String[] args) {
    Note note = new Note("");
    WithRWLock withRWLock = new WithRWLock(note);
    for (int i = 0; i < 50; i++) {
        int finalI = i;
        new Thread(() -> withRWLock.write("note" + finalI), "写线程" + i).start();
    }

    for (int i = 0; i < 50; i++) {
        new Thread(() -> withRWLock.read(), "读线程" + i).start();
    }
}
写线程0   开始写入 note0
写线程0   写入note0完成
写线程1   开始写入 note1
写线程1   写入note1完成
写线程3   开始写入 note3
写线程3   写入note3完成
写线程2   开始写入 note2
写线程2   写入note2完成
写线程4   开始写入 note4
写线程4   写入note4完成
写线程6   开始写入 note6
写线程6   写入note6完成
写线程8   开始写入 note8
写线程8   写入note8完成
写线程9   开始写入 note9
写线程9   写入note9完成
写线程10   开始写入 note10
写线程10   写入note10完成
写线程12   开始写入 note12
写线程12   写入note12完成
写线程13   开始写入 note13
写线程13   写入note13完成
写线程11   开始写入 note11
写线程11   写入note11完成
写线程14   开始写入 note14
写线程14   写入note14完成
写线程16   开始写入 note16
写线程16   写入note16完成
写线程17   开始写入 note17
写线程17   写入note17完成
写线程5   开始写入 note5
写线程5   写入note5完成
写线程7   开始写入 note7
写线程7   写入note7完成
写线程18   开始写入 note18
写线程18   写入note18完成
写线程19   开始写入 note19
写线程19   写入note19完成
写线程20   开始写入 note20
写线程20   写入note20完成
写线程22   开始写入 note22
写线程22   写入note22完成
读线程1   开始读取
读线程1   读取完成,读取到 note22
写线程23   开始写入 note23
写线程23   写入note23完成
写线程24   开始写入 note24
写线程24   写入note24完成
写线程25   开始写入 note25
写线程25   写入note25完成
写线程26   开始写入 note26
写线程26   写入note26完成
写线程27   开始写入 note27
写线程27   写入note27完成
写线程28   开始写入 note28
写线程28   写入note28完成
写线程29   开始写入 note29
写线程29   写入note29完成
写线程31   开始写入 note31
写线程31   写入note31完成
写线程32   开始写入 note32
写线程32   写入note32完成
写线程33   开始写入 note33
写线程33   写入note33完成
写线程34   开始写入 note34
写线程34   写入note34完成
写线程36   开始写入 note36
写线程36   写入note36完成
写线程37   开始写入 note37
写线程37   写入note37完成
写线程38   开始写入 note38
写线程38   写入note38完成
写线程39   开始写入 note39
写线程39   写入note39完成
写线程40   开始写入 note40
写线程40   写入note40完成
写线程41   开始写入 note41
写线程41   写入note41完成
写线程43   开始写入 note43
写线程43   写入note43完成
读线程49   开始读取
读线程49   读取完成,读取到 note43
写线程45   开始写入 note45
写线程45   写入note45完成
写线程44   开始写入 note44
写线程44   写入note44完成
写线程15   开始写入 note15
写线程15   写入note15完成
写线程42   开始写入 note42
写线程42   写入note42完成
读线程34   开始读取
读线程33   开始读取
读线程33   读取完成,读取到 note42
读线程34   读取完成,读取到 note42
读线程30   开始读取
读线程30   读取完成,读取到 note42
读线程31   开始读取
读线程31   读取完成,读取到 note42
读线程43   开始读取
读线程43   读取完成,读取到 note42
读线程32   开始读取
读线程32   读取完成,读取到 note42
读线程47   开始读取
读线程21   开始读取
读线程21   读取完成,读取到 note42
读线程16   开始读取
读线程16   读取完成,读取到 note42
读线程35   开始读取
读线程35   读取完成,读取到 note42
读线程36   开始读取
读线程36   读取完成,读取到 note42
读线程17   开始读取
读线程17   读取完成,读取到 note42
读线程37   开始读取
读线程37   读取完成,读取到 note42
读线程18   开始读取
读线程18   读取完成,读取到 note42
读线程20   开始读取
读线程20   读取完成,读取到 note42
读线程19   开始读取
读线程19   读取完成,读取到 note42
读线程38   开始读取
读线程38   读取完成,读取到 note42
读线程22   开始读取
读线程39   开始读取
读线程39   读取完成,读取到 note42
读线程23   开始读取
读线程24   开始读取
读线程24   读取完成,读取到 note42
读线程40   开始读取
读线程42   开始读取
读线程42   读取完成,读取到 note42
读线程25   开始读取
读线程25   读取完成,读取到 note42
读线程28   开始读取
读线程44   开始读取
读线程44   读取完成,读取到 note42
读线程26   开始读取
读线程26   读取完成,读取到 note42
读线程27   开始读取
读线程27   读取完成,读取到 note42
读线程45   开始读取
读线程45   读取完成,读取到 note42
读线程29   开始读取
读线程29   读取完成,读取到 note42
读线程48   开始读取
读线程48   读取完成,读取到 note42
读线程46   开始读取
读线程28   读取完成,读取到 note42
读线程40   读取完成,读取到 note42
读线程23   读取完成,读取到 note42
读线程22   读取完成,读取到 note42
读线程47   读取完成,读取到 note42
读线程46   读取完成,读取到 note42
写线程35   开始写入 note35
写线程35   写入note35完成
写线程30   开始写入 note30
写线程30   写入note30完成
读线程15   开始读取
读线程15   读取完成,读取到 note30
读线程14   开始读取
读线程13   开始读取
读线程13   读取完成,读取到 note30
读线程8   开始读取
读线程8   读取完成,读取到 note30
读线程7   开始读取
读线程7   读取完成,读取到 note30
读线程12   开始读取
读线程12   读取完成,读取到 note30
读线程3   开始读取
读线程3   读取完成,读取到 note30
读线程4   开始读取
读线程4   读取完成,读取到 note30
读线程5   开始读取
读线程10   开始读取
读线程10   读取完成,读取到 note30
读线程14   读取完成,读取到 note30
读线程5   读取完成,读取到 note30
写线程21   开始写入 note21
写线程21   写入note21完成
写线程48   开始写入 note48
写线程48   写入note48完成
读线程0   开始读取
读线程0   读取完成,读取到 note48
写线程47   开始写入 note47
写线程47   写入note47完成
读线程6   开始读取
读线程6   读取完成,读取到 note47
读线程11   开始读取
读线程9   开始读取
读线程9   读取完成,读取到 note47
读线程11   读取完成,读取到 note47
写线程46   开始写入 note46
写线程46   写入note46完成
写线程49   开始写入 note49
写线程49   写入note49完成
读线程2   开始读取
读线程41   开始读取
读线程41   读取完成,读取到 note49
读线程2   读取完成,读取到 note49

可以看到,加锁后,读和写的过程中都不会有写线程再插队了

29.3、Condition ( java.util.concurrent.locks.Condition )

与Object类下的wait()、notify()/notifyAll()对应,Condition接口下也有与之对应的await()、signal()/signalAll()

public interface Condition {
    /* ... */
    void await() throws InterruptedException;
    void signal();
    void signalAll();
    /* ... */
}
Condition condition = lock.newCondition();
condition.await(); //等待
condition.signal();
condition.signalAll(); //唤醒

修改14.3的例子(JUC版):

public class ConditionDemo {
    private volatile List<String> syncList;
    private Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    public ConditionDemo() {
        this.syncList = Collections.synchronizedList(new LinkedList<>());
    }

    public String removeElement() {
        lock.lock();
        try {
            while (this.syncList.isEmpty()) {
                System.out.println(Thread.currentThread().getName() + "  " + "列表为空...");
                condition.await();
                System.out.println(Thread.currentThread().getName() + "  " + "等待中...");
            }
            return Thread.currentThread().getName() + "  " + "移除元素: " + syncList.remove(0);
        } catch (Exception e) {
            return Thread.currentThread().getName() + "  " + e.toString();
        } finally {
            lock.unlock();
        }
    }

    public void addElement(String element) {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "  " + "addElement 方法 开始");
            syncList.add(element);
            System.out.println(Thread.currentThread().getName() + "  " + "添加元素: " + element);
            /*****************************************/
            condition.signal();
            // condition.signalAll();
            /*****************************************/
            System.out.println(Thread.currentThread().getName() + "  " + "添加元素完成,通知...");
            System.out.println(Thread.currentThread().getName() + "  " + "addElement 方法 结束");
        } catch (Exception e) {
            System.out.println(Thread.currentThread().getName() + "  " + e.toString());
        } finally {
            lock.unlock();
        }

    }

    public static void main(String[] args) throws InterruptedException {
        final ConditionDemo demo = new ConditionDemo();

        Thread threadA1 = new Thread(() -> {
            try {
                System.out.println(demo.removeElement());
            } catch (Exception e) {
                System.out.println(Thread.currentThread().getName() + "  " + e.toString());
            }
        }, "threadA1");
        threadA1.start();
        Thread.sleep(500);

        Thread threadA2 = new Thread(() -> {
            try {
                System.out.println(demo.removeElement());
            } catch (Exception e) {
                System.out.println(Thread.currentThread().getName() + "  " + e.toString());
            }
        }, "threadA2");
        threadA2.start();
        Thread.sleep(500);

        Thread threadA3 = new Thread(() -> {
            try {
                System.out.println(demo.removeElement());
            } catch (Exception e) {
                System.out.println(Thread.currentThread().getName() + "  " + e.toString());
            }
        }, "threadA3");
        threadA3.start();
        Thread.sleep(500);

        Thread threadB1 = new Thread(() -> demo.addElement("B1"), "threadB1");
        threadB1.start();
        Thread threadB2 = new Thread(() -> demo.addElement("B2"), "threadB2");
        threadB2.start();
        Thread.sleep(1000);

        threadA1.interrupt();
        threadA2.interrupt();
        threadA3.interrupt();
    }
}

比较addElement方法中signal()和signalAll()的区别:

  • signal(): 会唤醒在等待队列中等待最长时间的线程(队首线程)

在这里插入图片描述

  • signalAll(): 会唤醒在等待队列中的所有线程,不过,即便是唤醒全部,线程也不会去抢夺CPU的资源,而是依旧去排队

在这里插入图片描述

在这里插入图片描述

Condition实现精准通知唤醒保证线程依次排队执行:

普通的线程执行顺序是取决于CPU的,并且即便设置了优先级也不能绝对保证线程的执行顺序:

在这里插入图片描述
在这里插入图片描述

可以通过Condition为每个线程都设置一个状态:

public class ConditionDemo2 {
    private static Lock lock = new ReentrantLock();
    private static Condition condition1 = lock.newCondition();
    private static Condition condition2 = lock.newCondition();
    private static Condition condition3 = lock.newCondition();
    private static Condition condition4 = lock.newCondition();
    private static Condition condition5 = lock.newCondition();
    private static Condition condition6 = lock.newCondition();
    private static int number = 1;

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                while (number != 1) {
                    condition1.await();
                }
                System.out.println(Thread.currentThread().getName());
                number = 2;
                condition2.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "线程1");

        Thread t2 = new Thread(() -> {
            lock.lock();
            try {
                while (number != 2) {
                    condition2.await();
                }
                System.out.println(Thread.currentThread().getName());
                number = 3;
                condition3.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "线程2");

        Thread t3 = new Thread(() -> {
            lock.lock();
            try {
                while (number != 3) {
                    condition3.await();
                }
                System.out.println(Thread.currentThread().getName());
                number = 4;
                condition4.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "线程3");

        Thread t4 = new Thread(() -> {
            lock.lock();
            try {
                while (number != 4) {
                    condition4.await();
                }
                System.out.println(Thread.currentThread().getName());
                number = 5;
                condition5.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "线程4");

        Thread t5 = new Thread(() -> {
            lock.lock();
            try {
                while (number != 5) {
                    condition5.await();
                }
                System.out.println(Thread.currentThread().getName());
                number = 6;
                condition6.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "线程5");
        
        Thread t6 = new Thread(() -> {
            lock.lock();
            try {
                while (number != 6) {
                    condition6.await();
                }
                System.out.println(Thread.currentThread().getName());
                number = 1;
                condition1.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "线程6");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        t6.start();
    }
}

在这里插入图片描述

30、自旋锁

AtomicInteger的getAndIncrement()应用的就是自旋锁 :

Unsafe类

java无法操作内存,通过调用C++来操作内存

Unsafe类中的方法都是native的,java就是通过这个类来操作内存的

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

在这里插入图片描述

举个基本的例子:

public class SpinLockDemo {

    public static void main(String[] args) {
        SpinLock lock = new SpinLock();
        new Thread(() -> {
            lock.myLock();
            System.out.println(Thread.currentThread().getName() + "  加锁");
            try {
                TimeUnit.MICROSECONDS.sleep(101);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.myUnlock();
                System.out.println(Thread.currentThread().getName() + "  解锁");
            }
        },"线程1").start();


        new Thread(() -> {
            lock.myLock();
            System.out.println(Thread.currentThread().getName() + "  加锁");
            try {
                TimeUnit.MICROSECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.myUnlock();
                System.out.println(Thread.currentThread().getName() + "  解锁");
            }
        },"线程2").start();
    }
}

class SpinLock {
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

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

    //解锁
    public void myUnlock() {
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
    }
}

执行结果:
在这里插入图片描述

运行结果就是,线程1加锁后,线程2一直在while循环中自旋,直到线程1执行完成并解锁后,线程2才可以加锁、执行、解锁

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值