JUC并发编程

JUC并发编程

  • java.util.concurrent
  • java.util.concurrent.atomic
  • java.util.concurrent.locks

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 */
            }
        }
    }
	// native是本地方法,底层是c++,java不能直接操作硬件
    private native void start0();

并发和并行

并发:多线程操作同一个资源

  • 单核CPU模拟出多条线程,快速交替,实际上是产生的假象。

并行

  • CPU多核,多个线程可以同时执行;线程池。

线程有几种状态?

源码

    public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
         // 新建状态(线程一旦被创建就进入了新建状态)
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
         // 可运行状态(调用了start方法之后就变成可运行状态)
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
         // 阻塞状态(线程因为某种原因放弃了CPU的权利),暂时停止运行,直到线程进入可运行(runnable)状
         /// 态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
         // 一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
		// (二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
		// (三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called {@code Object.wait()}
         * on an object is waiting for another thread to call
         * {@code Object.notify()} or {@code Object.notifyAll()} on
         * that object. A thread that has called {@code Thread.join()}
         * is waiting for a specified thread to terminate.
         */
         // 等待
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
         // 超时等待
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
         // 终止
        TERMINATED;
    }

1、wait和sleep的区别

  • 他们来自不同的类,wait来自Object类,sleep来自Thread类
	// 一般使用TimeUnit方式让线程休眠
	TimeUnit.HOURS.sleep(11);

2、关于锁的释放
1、wait会释放锁,sleep不会释放锁。

3、使用的范围是不同的

  • wait必须使用在同步代码块中不需要捕获异常
  • sleep必须要捕获异常。

LOCK锁

传统Synchribuzed

package com.xiaohei.synchronizedTest;

public class SynchronizedTest {
    public static void main(String[] args) {
        SendTickets tickets = new SendTickets();

        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                tickets.sale();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                tickets.sale();
            }
        },"B").start();

        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                tickets.sale();
            }
        },"C").start();

    }

}

// 模拟卖票
class SendTickets {
    private static int TICKET_NUMBERS = 30;

    public synchronized void sale() {
        if (TICKET_NUMBERS > 0){
            System.out.println(Thread.currentThread().getName()+"卖出了"+(TICKET_NUMBERS--)+"票,剩余"+TICKET_NUMBERS);
        }
    }

}

LOCK锁
三种实现类:ReentrantLock 可重入锁, ReentrantReadWriteLock.ReadLock 读锁, ReentrantReadWriteLock.WriteLock 写锁
new ReentrantLock() 默认实现为非公平锁。new ReentrantLock(true)实现公平锁
1、公平锁能保证:老的线程排队使用锁,新线程仍然排队使用锁
2、非公平锁保证:老的线程排队使用锁;但是无法保证新线程抢占已经在排队的线程的锁

	// 非公平锁 默认
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
     // 公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
package com.xiaohei.locktTest;

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

public class LockTest {
    public static void main(String[] args) {

        Ticket tickets = new Ticket();

        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                tickets.sale();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                tickets.sale();
            }
        },"B").start();

        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                tickets.sale();
            }
        },"C").start();

    }

}

// 模拟卖票

/**
 *  lock三部曲
 *  1、new ReentrantLock();
 *  2、 lock.lock() // 加锁
 *  3.lock.unlock() 解锁
 *  */
class Ticket {
    private int TICKET_NUMBERS = 30;
    
    Lock lock = new ReentrantLock();

    public void sale() {
        
        lock.lock(); // 枷锁
        lock.tryLock(); // 尝试获取锁
        try {
            if (TICKET_NUMBERS > 0){
                System.out.println(Thread.currentThread().getName()+"卖出了"+(TICKET_NUMBERS--)+"票,剩余"+TICKET_NUMBERS);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }

}

synchronized和Lock的区别

  1. Synchronized内置的Java关键字,Lock是一个Java类
  2. Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
  3. Synchronized会自动释放锁,lock必须要手动释放锁,如果不释放,会造成死锁
  4. Synchronzied 线程1(获得锁,阻塞),线程2(一直等); Lock锁不一定会一直等待下去(lock,tryLock() // 尝试获取锁)
  5. Synchronized 可重入锁,不可以中断的,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置)
  6. Synchronized适合锁少量代码同步问题,Lock适合锁大量的同步代码;

生产者和消费者问题

面试问题:

  • 单例模式
  • 8种排序算法
  • 生产者消费者问题
  • 死锁问题

生产者消费者问题

// 两个线程米有问题,如果在加两个线程C和D呢
package com.xiaohei.pc;

public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }
}

class Data {
    private int number = 0;

    // +1
    public synchronized void increment() throws InterruptedException {
        if (number!=0){
            // 等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        // 通知可以消费
        this.notifyAll();
    }

    // -1
    public synchronized void decrement() throws InterruptedException {
        if (number==0){
            // 等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        // 通知可以消费
        this.notifyAll();
    }
}

// 使用While代替If防止虚假唤醒
package com.xiaohei.pc;

public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }
}

class Data {
    private int number = 0;

    // +1
    public synchronized void increment() throws InterruptedException {
        while (number!=0){
            // 等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        // 通知可以消费
        this.notifyAll();
    }

    // -1
    public synchronized void decrement() throws InterruptedException {
        while (number==0){
            // 等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        // 通知可以消费
        this.notifyAll();
    }
}

JUC生产者和消费者问题(LOCK)

与synchnoized的却别:synchronized是使用wait和notify,Lock锁通过lock创建Condition对象,通过condition对象调用await方法和singal方法等待和唤醒。

Condition优势: 实现的精准通知唤醒线程
if换成while条件判断,在多线程条件下会产生虚假唤醒的情况。

package com.xiaohei.pc;

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

public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }
}

class Data2 {
    private int number = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    // +1
    public void increment() throws InterruptedException {

        lock.lock();
        try {
            while (number!=0){
                // 等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            // 通知可以消费
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    // -1
    public synchronized void decrement() throws InterruptedException {

        lock.lock();
        try {
            while (number==0){
                // 等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            // 通知可以消费
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

实现顺序执行任务链

Condition优势: 实现的精准通知唤醒线程

package com.xiaohei.pc;

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

public class C {
    public static void main(String[]  args) {
        Data3 data = new Data3();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printA();
            }
        },"A").start();

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

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

class Data3{
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int flag = 1;

    public void printA(){
        lock.lock();
        try {
            while (flag != 1){
                // 等待
                condition1.await();
            }
            flag = 2;
            condition2.signal();
            System.out.println(Thread.currentThread().getName()+"AAAA=>>>AAAAAA");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB(){
        lock.lock();
        try {
            while (flag != 2){
                // 等待
                condition2.await();
            }
            flag = 3;
            condition3.signal();
            System.out.println(Thread.currentThread().getName()+"BBB=>>>BBBBB");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC(){
        lock.lock();
        try {
            while (flag != 3){
                // 等待
                condition3. ();
            }
            flag = 1;
            condition1.signal();
            System.out.println(Thread.currentThread().getName()+"BBB=>>>CCCC");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

什么是锁,如何判断锁是谁?8锁现象

锁只会锁对象还有Class
synchronized锁的是方法调用者
直接上代码
两个线程是先打印打电话还是先打印发短信?

  1. 第1锁:锁的是同一个Phone对象,
    个人理解:因为锁的是当前Phone对象,程序运行开始,首先是A线程拿到方法执行权,然后进入阻塞状态,线程B进入可运行状态调用callPhone()方法,但是由于锁住了同一个对象,A不释放锁,B是无法执行,所以是先调用sendEmail()在调用callPhone()方法。两个方法是同一个锁,谁先拿到谁先执行。
    此时有个疑问?调用Sleep()方法,睡眠结束后不是不释放锁吗,为什么B方法还能获取到锁?是因为锁的不是Phone对象吗?那Sleep锁的是谁呢?
package com.xiaohei.lock8;

import java.util.concurrent.TimeUnit;
// 1、发短信 2、打电话
public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(()->{
            phone.sendEmail();
        },"A").start();
 
        try {
            TimeUnit.SECONDS.sleep(1L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone.callPhone();
        },"B").start();
    }
}

class Phone{
	// 都是同一个锁,谁先拿到谁执行。
    public synchronized void sendEmail(){
        System.out.println("发短信");
    }

    public synchronized void callPhone(){
        System.out.println("打电话");
    }
}

第2个锁

package com.xiaohei.lock8;

import java.util.concurrent.TimeUnit;

// 线程A调用打电话同步方法,线程B调用普通不同步方法,先输出什么?
// 1、biubiubiu  2、发短信
public class Lock2 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();

        new Thread(()->{
            phone.sendEmail();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 调用biu方法
        new Thread(()->{
            phone.biu();
        },"B").start();
    }
}

class Phone2{
    public synchronized void sendEmail(){
         try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void callPhone(){
        System.out.println("打电话");
    }

    // **没有加锁,不是同步方法,不受锁的影响,不需要A执行完在执行B**
    public void biu(){
        System.out.println("biubiubiu");
    }

}

第3个锁
两个对象,两个同步方法, 打电话方法加了延时为了结果明贤

package com.xiaohei.lock8;

import java.util.concurrent.TimeUnit;
// 结果:先输出打电话,在输出发短信.
// 因为是两个对象,synchronized锁的是调用方,此时是两把锁,锁不一样,结果按照时间来。没有延时就不一定的。
public class Test1 {
    public static void main(String[] args) {
    	
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();

        new Thread(()->{
            phone1.sendEmail();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(2L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone1.callPhone();
        },"B").start();

    }
}

class Phone{
    public synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void callPhone(){
        System.out.println("打电话");
    }

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

第4锁
增加了两个静态同步方法
static 静态方法,类一加载就出现。锁的是 Class 模板
一个类只有一个Class对象
测试案例: 两个对象

package com.xiaohei.lock8;

import java.util.concurrent.TimeUnit;

public class Test1 {
    public static void main(String[] args) {
    // 两个对象只有一个Class,谁先拿到谁先执行
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();

        new Thread(()->{
            phone1.sendEmail();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(2L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.callPhone();
        },"B").start();

    }
}

// 两个对象,两个调用者,锁的是Class对象,因为两个对象来自同一个Class所以谁先拿到谁先执行
class Phone{
    public static synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public static synchronized void callPhone(){
        System.out.println("打电话");
    }

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

第5锁
一个静态同步方法,一个普通同步方法,一个对象
结果:先输出打电话,在输出发短信啊,因为一个是Class锁,一个是调用者锁(对象锁),是两个锁

package com.xiaohei.lock8;

import java.util.concurrent.TimeUnit;

public class Test1 {
    public static void main(String[] args) {
        Phone phone1 = new Phone();
//        Phone phone2 = new Phone();

        new Thread(()->{
            phone1.sendEmail();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(2L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone1.callPhone();
        },"B").start();

    }
}

class  Phone{

    // static锁,锁的是Class
    public static void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    // 锁的是调用者 不是一个锁
    public synchronized void callPhone(){
        System.out.println("打电话");
    }

}

总结

  • new this对象 调用者
  • Class 类模板锁 ,只有一个

多线程下集合类不安全类

在并发下ArrayList是不安全的

解决方案:
1、使用List list = new Vector<>();线程安全
2、使用Collections集合的同步方法, List list = Collections.synchronizedList(new ArrayList<>());
3、并发包下: CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();
Vector和CopyOnWriteArrayList区别: Vector底层实现synchronized关键字效率低,CopyOnWriteArrayList使用Lock锁

	CopyOnWriteArrayList<>();底层实现
	
	final Object[] getArray() {
        return array;
    }
    final void setArray(Object[] a) {
        array = a;
    }
    public boolean add(E e) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            setArray(es);
            return true;
        }
    }

HashSet的底层是什么?

    // 创建HashSet
    Set<Object> hashSet = new HashSet<>();
    // 底层实际上是通过创建HshMap实现
    public HashSet() {
        map = new HashMap<>();
    }
    // 添加元素 add方法
    // 直接把元素通过map put进去,实际上就是存的map的key,因为map的key是不可重复的,所以set也是不可重复的。
    public boolean add(E e) {
       return map.put(e, PRESENT)==null;
   }
   // PRESENT参数,是一个固定不变的对象,不可修改。
   private static final Object PRESENT = new Object();
Callable接口(多线程创建方式)
  1. 会抛出异常
  2. 有返回值
  3. 调用方法为call()方法

实现

package com.xiaohei.callableTest;

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

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        Test test = new Test();
        FutureTask<String> futureTask = new FutureTask<>(test); // 适配类

        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start(); // 结果会被缓存 提高效率

        // 返回值
        // V - 此 FutureTask 的 get 方法所返回的结果类型。
        String result = futureTask.get(); // 可能会产生阻塞,因为需要等线程查询结束
        System.out.println(result);

    }
}


class Test implements Callable<String>{

    @Override
    public String call() throws Exception {
        System.out.println("Call");
        return "我是小黑人";
    }
}

为什么Callable接口使用FutureTask来启动线程?
1、启动线程方式通过Thread的构造方法 new Thread().start();
Thread构造方法
2、也可以通过new Thread(new Runable()).start()方法启动
3、看Runable接口的实现类有一个FutureTask类
Runable接口实现类
4、FutureTask的构造方法需要传入Callable接口实现类.
5、这样就形成了new Thread(new FuntureTask(new Callable()).start();启动线程模式
6、Callable和FutureTask中的泛型V标识返回值类型
7、通过futureTask.get(); 方法获取返回值,注意: 这里可能会导致线程阻塞问题,因为需要等待返回结果。
8、多个线程一起执行,结果会有缓存,提高效率

常用辅助类
  • CountDownLatch
package com.xiaohei.countDownLatchDemo;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
		
        CountDownLatch countDownLatch = new CountDownLatch(10);

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"奥利给");
            countDownLatch.countDown();
        }

		// 县城没有执行完,一直会处于等待状态,直到countDownLatch为0,自动释放
		countDownLatch.await();
        System.out.println("奥利给嗷嗷嗷嗷");
    }
}
  • CyclicBarrier(加法计数器)
package com.xiaohei.countDownLatchDemo;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CilicBarrier {
    public static void main(String[] args) {

        // 加法计数器,当所有线程都到达某个屏障点后再进行后续的操作
        CyclicBarrier cyclicBarrier = new CyclicBarrier(10,()->{
            System.out.println("奥利给1111");
        });

        
        // 如果当达到屏障点时,会触发执行任务,然后清空计数器,这里有一个清空操作,当每次在达到阈值时,再次触发逻辑
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"奥利给");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }

    }
}

CountDownLatch和CyclicBarrier的区别

CountDownLatchCyclicBarrier
减计数方式加计数方式
计数为0时释放所有等待线程计数达到指定屏障值时释放所有线程
计数为0时无法重置计数达到指定屏障值时可以重置
调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没有影响调用await()方法计数加1,加1后的值不等于构造方法给的值则线程阻塞
不可重复利用可以重复利用
  • semaphore(信号量)
package com.xiaohei.countDownLatchDemo;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
// 信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。
public class SemaphoreTest {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < 6; i++) {

            int finalI = i;
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+ "进来");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+ "出去");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }

            }).start();
        }
    }
}
阻塞队列 BlockingQueue

Collection下的一个接口
阻塞队列
使用阻塞队列
四组API

方式抛出异常有返回值,不抛出异常阻塞等待超时等待
添加add()offer()pu(t)offer()
移除remove()poll()take()poll()
检查队首元素elment()peek()--

测试

  • 抛出异常,add()和remove()
    // 测试1: 抛出异常
    public static void throwExceptionBlockingDueue(){
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

        //
        System.out.println(arrayBlockingQueue.add("1"));
        System.out.println(arrayBlockingQueue.add("2"));
        System.out.println(arrayBlockingQueue.add("3"));
        // 如果添加元素大于指定阻塞队列大小就会抛出异常:
        // Exception in thread "main" java.lang.IllegalStateException: Queue full
//        System.out.println(arrayBlockingQueue.add("4"));
        System.out.println("===========");

        // 如果移除元素数量超出阻塞队列长度抛出异常:
        // Exception in thread "main" java.util.NoSuchElementException
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
    }
  • 有返回值,不抛出异常,如果添加元素数量超出阻塞队列长度不会抛出异常,而是返回false,如果阻塞队列为空获取元素会返回null, offer()和poll()
    public static void noThrowExpectionBlockingDueue(){
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(arrayBlockingQueue.offer("1"));
        System.out.println(arrayBlockingQueue.offer("2"));
        System.out.println(arrayBlockingQueue.offer("3"));
        // 如果添加元素数量超出阻塞队列长度不会抛出异常,而是返回false
        System.out.println(arrayBlockingQueue.offer("4"));

        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        // 如果添加元素数量超出阻塞队列长度不会抛出异常,而是返回null
        System.out.println(arrayBlockingQueue.poll());

    }
  • 阻塞等待(一直等待) put()和take()方法
    // 测试3: 阻塞等待
    public static void blockingDueueWait() throws InterruptedException {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        arrayBlockingQueue.put("1");
        arrayBlockingQueue.put("2");
        arrayBlockingQueue.put("3");
        // 如果添加元素超过阻塞队列长度,会一直等待
//        arrayBlockingQueue.put("4");

        arrayBlockingQueue.take();
        arrayBlockingQueue.take();
        arrayBlockingQueue.take();
        // 如果阻塞队列为空,获取元素会一直等待
        arrayBlockingQueue.take();
    }
  • 超时等待(超出一定时间自动结束) offer和poll的重载方法
    public static void chaoBlockingDueueWait() throws InterruptedException {
        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
        blockingQueue.offer("1",1, TimeUnit.SECONDS);
        blockingQueue.offer("2",1, TimeUnit.SECONDS);
        blockingQueue.offer("3",1, TimeUnit.SECONDS);
        // 添加元素超出阻塞队列长度,过1秒会自动结束
        blockingQueue.offer("4",1, TimeUnit.SECONDS);

        blockingQueue.poll(1,TimeUnit.SECONDS);
        blockingQueue.poll(1,TimeUnit.SECONDS);
        blockingQueue.poll(1,TimeUnit.SECONDS);
        // 如果阻塞队列元素为空.再移除元素会等待,过1秒会自动结束
        blockingQueue.poll(1,TimeUnit.SECONDS);
    }
  • 获取队首元素
    抛异常方法使用element()
    有返回值,不抛出异常使用take()

线程池

线程池优点

1、降低资源消耗:(通过重复利用已创建的线程降低线程创建和销毁造成的消耗。)
2、提高响应速度:(任务到达时,任务可以不需要等到线程创建就能立即执行)
3、方便管理:(线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控)
线程服用、可以控制最大并发数、便于管理

  • 三大方法
    • Executors.newSingleThreadExecutor(); //创建单一线程池,
      • 特点:只有1个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列。
      • 应用场景:不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作、文件操作等。
package com.xiaohei.threadPool;

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

public class ThreadPoolTest {
    public static void main(String[] args) {
        // 创建单一线程池,
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        // 创建固定大小线程
        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);

        // 创建可缓存线程池, 根据并发数量自动扩容或伸缩, 最大线程数为Intger.Max  21亿
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

        run(executorService);
//        run(newFixedThreadPool);
//        run(cachedThreadPool);

    }

    public static void run(ExecutorService executorService){
        for (int i = 1; i <= 5; i++) {
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName()+"ok");
            });
        }
		// 执行完任务必须要关闭线程池,否则会一直等待
        executorService.shutdown();
    }
}
  • Executors.newFixedThreadPool(5); // 固定大小线程池,传入参数
    • 特点:只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。
    • 应用场景:控制线程最大并发数。
package com.xiaohei.threadPool;

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

public class ThreadPoolTest {
    public static void main(String[] args) {
        // 创建单一线程池,
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        // 创建固定大小线程
        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);

        // 创建可缓存线程池, 根据并发数量自动扩容或伸缩, 最大线程数为Intger.Max  21亿
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

//        run(executorService);
        run(newFixedThreadPool);
//        run(cachedThreadPool);

    }

    public static void run(ExecutorService executorService){
        for (int i = 1; i <= 5; i++) {
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName()+"ok");
            });
        }

        // 执行完任务必须要关闭线程池,否则会一直等待
        executorService.shutdown();
    }
}

// 打印输出结果,五个线程同时执行
D:\environmentConfig\jdk11\bin\java.exe "
pool-2-thread-5ok
pool-2-thread-1ok
pool-2-thread-2ok
pool-2-thread-4ok
pool-2-thread-3ok

Process finished with exit code 0
  • Executors.newCachedThreadPool(); // 可缓存线程池,根据任务数量自动扩容和缩减线程池大小
    • 特点:无核心线程,非核心线程数量无限,执行完闲置60s后回收,任务队列为不存储元素的阻塞队列。
    • 应用场景:执行大量、耗时少的任务。
package com.xiaohei.threadPool;

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

public class ThreadPoolTest {
    public static void main(String[] args) {
        // 创建单一线程池,
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        // 创建固定大小线程
        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);

        // 创建可缓存线程池, 根据并发数量自动扩容或伸缩, 最大线程数为Intger.Max  21亿
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

//        run(executorService);
//        run(newFixedThreadPool);
        run(cachedThreadPool);

    }

    public static void run(ExecutorService executorService){
        for (int i = 1; i <= 10; i++) {
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName()+"ok");
            });
        }

        // 执行完任务必须要关闭线程池,否则会一直等待
        executorService.shutdown();
    }
}

// 执行结果,可以发现,当我们同时开启10个任务时,可缓存线程池可以根据任务数量自动创建适合大小线程池
pool-3-thread-3ok
pool-3-thread-1ok
pool-3-thread-8ok
pool-3-thread-6ok
pool-3-thread-2ok
pool-3-thread-4ok
pool-3-thread-7ok
pool-3-thread-10ok
pool-3-thread-9ok
pool-3-thread-5ok

Process finished with exit code 0

定时线程池(ScheduledThreadPool )
Executors.newScheduledThreadPool(3);
特点:核心线程数量固定,非核心线程数量无限,执行完闲置10ms后回收,任务队列为延时阻塞队列。
应用场景:执行定时或周期性的任务。

  • 三大方法底层实现(线程池的真正实现类是ThreadPoolExecutor,都是调用的其构造方法)

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

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

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

  • 七大参数

手动创建线程池,参数含义
* 1、corePoolSize:核心线程数量,一直启用的线程数
* 2、maximumPoolSize: 最大启用线程数量,只有当阻塞队列中的任务满时,开启最大线程数,当任务不饱和时,失效时间结束,非核心线程被收回
* 3、keepAliveTime:线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将allowCoreThreadTimeout设置为true时,核心线程也会超时回收。
* 4、TimeUnit:指定keepAliveTime参数的时间单位
* 5、BlockingQueue<>(): 通过线程池的execute()方法提交的Runnable对象将存储在该参数中。其采用阻塞队列实现。任务超过核心线程数会放到阻塞队列中,如果阻塞队列任务也满了,会启动maximumPoolSize最大线程数,当启动完毕还有任务进来时,此时采用拒绝策略。
* 6、threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
* 7、handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
* -AbortPolicy()(默认):丢弃任务并抛出RejectedExecutionException异常。
* -CallerRunsPolicy():由调用线程处理该任务。
* -DiscardPolicy():丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
* -DiscardOldestPolicy():丢弃队列最早的未处理任务,然后重新尝试执行任务。
*/

package com.xiaohei.threadPool;

import java.util.concurrent.*;

public class ThreadPoolTest {
    public static void main(String[] args) {

        /**
         * 手动创建线程池,参数含义
         * 1、corePoolSize:核心线程数量,一直启用的线程数
         * 2、maximumPoolSize: 最大启用线程数量,只有当阻塞队列中的任务满时,开启最大线程数,
         * 当任务不饱和时,失效时间结束,非核心线程被收回
         * 3、keepAliveTime:线程闲置超时时长。如果超过该时长,非核心线程就会被回收。
         * 4、TimeUnit:指定keepAliveTime参数的时间单位
         * 5、BlockingQueue<>(): 通过线程池的execute()方法提交的Runnable对象将存储在该参数中。其采用阻塞队列实现。
         * 任务超过核心线程数会放到阻塞队列中,如果阻塞队列任务也满了,会启动maximumPoolSize最大线程数,当启动完毕还有
         * 任务进来时,此时采用拒绝策略。
         * 6、threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
         * 7、handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
         *      -AbortPolicy()(默认):丢弃任务并抛出RejectedExecutionException异常。
         *      -CallerRunsPolicy():由调用线程处理该任务。
         *      -DiscardPolicy():丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
         *      -DiscardOldestPolicy():丢弃队列最早的未处理任务,然后重新尝试执行任务。
         */
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2,
                5,
                2,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy() // 抛出异常RejectedExecutionException
        );

        try {
            for (int i = 1; i <= 10; i++) {
                executor.execute(()->{
                    System.out.println("测试手动创建线程池:"+Thread.currentThread().getName());
                });
            }
        } finally {
            executor.shutdown();
        }
    }

}

// 因为拒绝策略使用的默认的,并且当前任务数量已经超过阻塞队列+最大线程数容量,所以再有任务采用拒绝策略抛出异常.

  • 四大拒绝策略

AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。
CallerRunsPolicy:由调用线程处理该任务。
DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务,如果尝试失败,放弃任务。

线程池使用方法


// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
                                             MAXIMUM_POOL_SIZE,
                                             KEEP_ALIVE,
                                             TimeUnit.SECONDS,
                                             sPoolWorkQueue,
                                             sThreadFactory);
// 向线程池提交任务,lambda表达式
 threadPool.execute(()->{
     // 执行任务
 });
// 关闭线程池
threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
threadPool.shutdownNow(); // 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返
任务队列(workQueue)

任务队列是基于阻塞队列实现的,即采用生产者消费者模式,在Java中需要实现BlockingQueue接口。但Java已经为我们提供了7种阻塞队列的实现:
1、ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
2、LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为Integer.MAX_VALUE。
3、PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现Comparable接口也可以提供Comparator来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
4、DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现Delayed接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
5、SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用take()方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用put()方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
6、LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样FIFO(先进先出),也可以像栈一样FILO(先进后出)。
7、LinkedTransferQueue: 它是ConcurrentLinkedQueue、LinkedBlockingQueue和SynchronousQueue的结合体,但是把它用在ThreadPoolExecutor中,和LinkedBlockingQueue行为一致,但是是无界的阻塞队列。
注意
注意有界队列和无界队列的区别:如果使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略;而如果使用无界队列,因为任务队列永远都可以添加任务,所以设置maximumPoolSize没有任何意义。

对比
类型池内 线程类型池内 线程数量处理特点应用场景
定长线程池
(FixedThreadPool)
核心线程固定1、核心线程处于空闲状态时也不会被回收,除非线程被关闭
2、当所有线程都处于活跃状态时,新的任务会处于等待状态,直到有线程空闲出来
3、任务队列无线大小(超出线程的任务会在队列中等待)
控制线程最大并发数
定时线程池
(ScheduledThreadPool)
核心线程和非核心线程核心线程 固定
非核心线程 无限制
非核心线程闲置时,会立刻被回收执行定时/周期性任务
可缓存线程池
(CachedThreadPool)
非核心线程不固定 无限制优先利用闲置线程处理新任务
(既 重用线程)
无线程可用时,创建新的线程
(既任何任务来了都会立刻执行,不需要等待)
灵活回收空闲线程
(具备超时机制=60s,空闲60s才会回收,全部回收时几乎不占用系统资源)
执行数量多、耗时少的任务。
单线程化线程池
(SingleThreadPool)
核心线程1个保证所有任务按照指定顺序在一个线程中执行
(相当于顺序执行)
单线程
(不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作、文件操作等。)
总结

Executors的4个功能线程池虽然方便,但现在已经不建议使用了,而是建议直接通过使用ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
其实Executors的4个功能线程有如下弊端:
FixedThreadPool和SingleThreadExecutor:主要问题是堆积的请求处理队列均采用LinkedBlockingQueue(可以存储无线个任务),可能会耗费非常大的内存,甚至OOM。
CachedThreadPool和ScheduledThreadPool:主要问题是线程数最大数是Integer.MAX_VALUE(最多处理将近无限个任务),可能会创建数量非常多的线程,甚至OOM。

最大线程应该如何定义?
  1. CPU密集型: 几核CPU,最大线程池就设置多少
  2. IO密集型: 判断程序中十分消耗IO的数量,大于即可,一搬2倍
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2,
                Runtime.getRuntime().availableProcessors(), // 获取CPU核数
                2,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy() // 抛出异常
        );
函数式接口(只有一个方法的接口)
  • Function<T,R>: 函数式接口,
  • Predicate: 断定式接口,返回Boolean类型,需要传入一个参数
  • Consumer: 消费型接口,只有输入,么有返回值
  • Supplier: 供给型接口
  • Function<T,R>: 函数式接口,泛型两个参数,T:传入参数类型,R:方法返回值类型
        // 函数式接口,泛型<T,R> 参数和返回值
        Function<String, String> function = new Function<String, String>() {
            @Override
            public String apply(String s) {
                return s;
            }
        };

        System.out.println(function.apply("123213"));

        // lambda表达式
        Function<String, Boolean> function2 = (str)->{return str.isEmpty();};

        System.out.println(function2.apply(""));
  • Predicate: 断定式接口,返回Boolean类型,需要传入一个参数
package com.xiaohei.functionInterface;

import java.util.function.Predicate;

public class PredicateInterfaceDemo {
    public static void main(String[] args) {
        // 断定式接口,<T> 泛型 传入参数
        Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.isEmpty();
            }
        };

        System.out.println(predicate.test("asdasd"));

        // lambda表达式<T> 传入参数
        Predicate<String> predicateLambda = (str)->{
            System.out.println(str.isEmpty());

            return str.isEmpty();
        };

        predicateLambda.test("");

    }
}
  • Consumer: 消费型接口,没有返回值,需要传入一个参数
package com.xiaohei.functionInterface;

import java.util.function.Consumer;

public class ConusmerInterfaceDemo {
    public static void main(String[] args) {
        // 消费型接口,没有返回值,传入参数
        Consumer<String> consumer = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s+"是不是空:"+s.isEmpty());
            }
        };
        consumer.accept("asda ");

        // lambda表达式
        Consumer<String > consumerLambda = (str)->{
//            System.out.println("ok");
        };

        consumerLambda.accept("asd");

    }
}

  • Supplier: 供给型接口,没有返回值,需要传入一个参数
package com.xiaohei.functionInterface;

import java.util.function.Supplier;

public class SupplierInterfaceDemo {
    public static void main(String[] args) {
        // 供给型接口,没有输入参数,只有返回值
        Supplier<String> supplier = new Supplier<String>() {
            @Override
            public String get() {
                System.out.println("get()1");
                return "11231";
            }
        };
        System.out.println(supplier.get());

        // lambda
        Supplier<String> supplierLambda = ()->{
            System.out.println("sad立刻就爱上了打开静安寺");
          return "122";
        };

        System.out.println(supplierLambda.get());
    }
}
ForkJoin线程池:它可以执行一种特殊的任务:把一个大任务拆成多个小任务并行执行,得到的结果进行合并
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值