JUC学习记录(基础篇)

目录

1.什么是JUC

1.1 进程与线程

1.2线程的状态

1.3wait和sleep

1.4并发和并行

1.5 用户线程和守护线程

2.Local接口

2.1 Synchronized

2.1.0 synchronized8种情况

2.1.1 Synchronized的作用范围

2.1.2 Synchronized实现买票的例子

2.1.3 多线程编程步骤

2.2 什么是Lock接口

2.3 使用Lock实现买票例子

2.4 创建线程的多种方式

3.线程间通信

3.1 通信举例

3.1.1 synchronized代码实现

3.1.2 虚假唤醒

3.1.3 lock代码实现

3.2 定制化通信 

3.2.1 案例

3.2.2 代码实现

4.集合的线程安全

4.1 集合线程不安全演示

4.2解决方案-Vector

4.3解决方案-Collections

4.4解决方案-CopyOnWriteArrayList(写时复制)

 4.5 set map的线程安全问题

5.多线程锁

5.1公平锁和非公平锁

5.2可重入锁

5.3死锁

6.Callable&Future接口

7.JUC三大辅助类

7.1减少计数CountDownLatch

7.2循环栅栏CyclicBarrier

7.3信号灯Semaphore

8.读写锁

8.1锁简介

8.1.1悲观锁和乐观锁

8.1.2表锁和行锁

8.1.3读锁和写锁

8.2读写锁基本操作

8.3锁的演变

8.4锁的降级

9.阻塞队列 

9.1阻塞队列概述

9.2阻塞队列架构

9.3阻塞队列分类

9.4阻塞队列核心方法 

10.ThreadPool线程池

10.1线程池概述

10.2线程池架构

10.3线程池使用方式

10.4线程池底层原理

10.5线程池的参数

10.6线程池底层工作流程

10.7自定义线程池

11.Fork/join

12.CompletableFuture


1.什么是JUC

1.1 进程与线程

进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程是资源分配的最小单位。

线程:系统分配处理器时间资源的最小单元,或者说进程之内独立执行的一个单元执行流。线程是程序执行的最小单位。

1.2线程的状态

线程状态枚举类Thread.State 中有5个状态

NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED

1.3wait和sleep

区别:

(1)sleep方法是Thread的静态方法,wait方法是Object的方法,任何对象实例都能调用。

(2)sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用他的前提是当前线程占有锁(即代码在synchronized中)

(3)他们都会被interrupted方法中断

1.4并发和并行

串行:只有一个人并且同一时间只执行一件任务

并行:多个人同时做不同的任务

并发: 同一时刻多个线程在访问同一个资源。例如春运抢票

1.5 用户线程和守护线程

用户线程:自定义线程

主线程结束,用户线程还在运行,jvm不会结束。

  public static void main(String[] args) {
      //用户线程
        Thread aa = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() 
                        + "::" + Thread.currentThread().isDaemon());
            while (true) {

            }
        }, "aa");
        aa.start();

        //主线程
        System.out.println(Thread.currentThread().getName()+"==over");
    }

守护线程:垃圾回收

没有用户线程,只有守护线程时,jvm会结束。

 public static void main(String[] args) {
      //用户线程
        Thread aa = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() 
                        + "::" + Thread.currentThread().isDaemon());
            while (true) {

            }
        }, "aa");
        //设置成守护线程
        aa.setDaemon(true);
        aa.start();

        //主线程
        System.out.println(Thread.currentThread().getName()+"==over");
    }

2.Local接口

2.1 Synchronized

2.1.0 synchronized8种情况

案例说明:

公共资源类phone中有三个方法,其中打印短信(---sendSMS),和打印邮件(---sendEmail)是由synchronized修饰的。

有两个线程,第一个线程执行打印短信方法,并且在执行完sleep 100ms,然后是第二个线程执行打印邮件的方法。

现在分情况验证:

  1. 正常执行:---sendSMS         ---sendEmail        因为中间停留了100ms所以一定是打印短信先执行。
  2. 在打印短信方法中先停4s再打印: ---sendSMS         ---sendEmail       
  3. 两部手机,在打印短信方法中先停4s再打印 :      ---sendEmail         ---sendSMS    
  4. 两个静态方法,一部手机: ---sendSMS         ---sendEmail       
  5. 两个静态方法,两部手机: ---sendSMS         ---sendEmail       
  6. 一个静态方法,一个普通方法,一部手机: ---sendEmail         ---sendSMS    

分析:

  • 1,2情况是因为被synchronized修饰普通方法锁住的对象是(this)也就是phone对象实例,所以进入打印短信方法后,phone对象被锁住,无法再进入打印邮件方法,所以等打印短信方法执行完再打印的邮件。
  • 2,3情况是因为两部手机就是两个对象,打印短信锁住的是phone1对象,并不影响phone2对象,所以在打印短信等待4秒时候,邮件就已经打印完成
  • 3,4,5情况是因为静态方法锁住是phone.class字节码文件,不是某一个类的实例,所以不管是一个手机还是两个手机都被锁住
  • 4,6情况是因为静态方法,和不同方法锁住的对象并不相同,所以先打印的邮件,后打印的短信。

总结:java中的每一个对象都可以作为锁,具体表现以下3中形式

  1. 对于普通同步方法,锁的是当前对象(如果两个对象调用此方法,只会锁住一个对象);
  2. 对于静态方法,锁的是当前类的class对象(不同的对象调用也只会锁住一个);
  3. 对于同步代码块,锁的是synchronized括号里面配置的对象。

2.1.1 Synchronized的作用范围

  • 修饰方法
  • 修饰静态方法(修饰类)
  • 修饰代码块

被修饰的代码块称为同步代码块,作用范围是大括号中的内容{}。

修饰的方法,其作用范围是整个方法,作用对象是调用该代码块的对象(虽然可以修饰方法,但是synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承)

修饰静态方法,其作用范围是整个静态方法,作用的对象是这个类的所有对象(修饰一个类也同理)

修饰方法

如果在父类中的某个方法使用了 synchronized 关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized 关键字才可以。或者在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步

一个线程对应一个对象 synchronized 实例方法,其他线程不能对该对象的其他 synchronized 方法访问(一个对象只有一把锁)。大概意思是当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,但其他线程可以访问该对象的其他非synchronized方法两个不同对象访问不同个东西,不会产生歧义,但是如果同一个对象访问同一个东西会有歧义而且不加锁
 

两个线程中同一个对象访问,加了锁机制不会出错

public class aa implements Runnable{{
    //共享资源(临界资源)
    static int i=0;

    /**
     * synchronized 修饰实例方法
     */
    public synchronized void add(){
        i++;
    }
    
    @Override
    public void run() {
        for(int j=0;j<10;j++){
            add();
        }
    }
    public static void main(String[] args)
    {
    	aa b=new aa();
        Thread m1=new Thread(b);
        Thread m2=new Thread(b);
        m1.start();
        m2.start();
        m1.join();
        m2.join();
        System.out.println(i);
    }
}

 但如果两个线程两个不同对象访问,加了锁的机制还是会出错

aa b=new aa();
aa c=new aa();
Thread m1=new Thread(b);
Thread m2=new Thread(c);

 需要将其方法添加为静态方法即可

因为两个不同对象,访问同一个锁会出错,定义为静态方法,即当前类的对象锁,不会出现歧义对象

修饰静态方法

对静态方法加锁,锁的是当前类的class对象锁 ??

public static synchronized void add(){
        i++;
    }

修饰代码块

如果一个代码块被 synchronized 修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况

  • 1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
  • 2)线程执行发生异常,此时 JVM 会让线程自动释放锁。

那么如果这个获取锁的线程由于要等待 IO 或者其他原因(比如调用 sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,但一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过 Lock 就可以办到

2.1.2 Synchronized实现买票的例子

package com.erp.platform.VO;

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

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.VO
 * @date 2022/2/9 17:04
 */
class Ticked {
    private Integer num = 30;
    Lock lock = new ReentrantLock();

    public void sale() {
        lock.lock();
        try {
            if (num > 0) {
                int a = 31 - (num--);
                System.out.println(Thread.currentThread().getName()
                                     + ": 卖出: " + a + "剩下:" + num);
            }
        } finally {
            lock.unlock();
        }

    }
}

public class Demo {
    public static void main(String[] args) {
        Ticked ticked = new Ticked();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 40; i++) {
                    ticked.sale();
                }
            }
        }, "售票员1").start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 40; i++) {
                    ticked.sale();
                }
            }
        }, "售票员2").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 40; i++) {
                    ticked.sale();
                }
            }
        }, "售票员3").start();
    }
}

2.1.3 多线程编程步骤

  1. 创建公共资源类,创建属性和操作方法
  2. 判断,干活,通知其他线程
  3. 创建多线程调用资源类的方法
  4. 防止虚假唤醒问题

2.2 什么是Lock接口

Lock接口是util包中的接口,已知实现类ReentrantLock,ReentrantReadWriteLock,ReadLock,ReentrantReadWriteLock,WriteLock

lock和synchronized最大的不同是synchronized不需要用户手动释放锁,是由系统自动释放锁的占用,而lock必须由用户手动释放锁,如果没有主动释放锁可能会造成死锁的现象。

线程竞争激烈时候,lock性能更优。

2.3 使用Lock实现买票例子

new Lock的时候一定要在类中,如果在方法中new,可能会出现多人卖出同一张票。

   static class Ticked {
        private Integer num = 30;
        Lock lock = new ReentrantLock();

        public void sale() {
            lock.lock();
            try {
                if (num > 0) {
                    int a = 31 - (num--);
                    System.out.println(Thread.currentThread().getName() 
                                        + ": 卖出: " + a + "剩下:" + num);
                }
            } finally {
                lock.unlock();
            }

        }
    }

    public static void main(String[] args) {
        Ticked ticked = new Ticked();
        Thread a = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 40; i++) {
                    ticked.sale();
                }
            }
        }, "售票员1");


        Thread b = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 40; i++) {
                    ticked.sale();
                }
            }
        }, "售票员2");

        Thread c = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 40; i++) {
                    ticked.sale();
                }
            }
        }, "售票员3");
        a.start();
        b.start();
        c.start();
    }

2.4 创建线程的多种方式

3.线程间通信

3.1 通信举例

实现两个线程对一个值得加减,如果数值为0时线程AA就增加1,如果数值为1时线程BB就减去1;

3.1.1 synchronized代码实现

package com.erp.platform.VO;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.VO
 * @date 2022/2/7 16:29
 */
public class ThreadDemo {
    static class Share{
        private int number = 0;

        // +1的方法
        public synchronized void incr() throws InterruptedException {
            // 判断, 干活, 通知
            if (number!=0){
                // 如果不是0,等待
                this.wait();
            }
            // 如果是0,就+1
            number++;
            System.out.println(Thread.currentThread().getName()+"::"+number);

            this.notifyAll();

        }
        // -1的方法
        public synchronized void decr() throws InterruptedException {
            if (number != 1) {
                this.wait();
            }
            number -- ;
            System.out.println(Thread.currentThread().getName()+"::"+number);

            this.notifyAll();
        }
    }

    public static void main(String[] args) {
        Share share = new Share();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 20; i++) {
                        share.incr();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 20; i++) {
                        share.decr();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();
    }
}

3.1.2 虚假唤醒

当增加到4个线程时候会出现这种状况: 

AA::1
BB::0
CC::1
DD::0
CC::1
AA::2
CC::3
DD::2
CC::3
AA::4
CC::5
DD::4

这是虚假唤醒问题引起的。

wait()方法的特点是在那里睡就会在哪里醒。

            if (number!=0){
                // 如果不是0,等待
                this.wait();
            }

            number++;
            。。。。。

所以当线程再次被唤醒时候就会直接不进行判断而进行下一步操作。

 所以规定wait()的使用要在while中。(sleep可能会造成死锁)

static class Share{
        private int number = 0;

        // +1的方法
        public synchronized void incr() throws InterruptedException {
            // 判断, 干活, 通知
            while (number!=0){
                // 如果不是0,等待
                this.wait();
            }
            // 如果是0,就+1
            number++;
            System.out.println(Thread.currentThread().getName()+"::"+number);

            this.notifyAll();

        }
        // -1的方法
        public synchronized void decr() throws InterruptedException {
            while (number != 1) {
                this.wait();
            }
            number -- ;
            System.out.println(Thread.currentThread().getName()+"::"+number);

            this.notifyAll();
        }
    }

3.1.3 lock代码实现

package com.erp.platform.VO;

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

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.VO
 * @date 2022/2/7 16:29
 */
public class ThreadDemo {
    static class Share{
        private int number = 0;
        Lock lock = new ReentrantLock();
        private Condition condition = lock.newCondition();

        // +1的方法
        public void incr() throws InterruptedException {
            lock.lock();


            // 判断, 干活, 通知
            try {
                while (number != 0) {
                    // 如果不是0,等待
                    condition.await();
                }
                // 如果是0,就+1
                number++;
                System.out.println(Thread.currentThread().getName() 
                                                    + "::" + number);
                condition.signalAll();
            } finally {
                lock.unlock();
            }
        }
        // -1的方法
        public void decr() throws InterruptedException {
            lock.lock();
            try {
                while (number != 1) {
                   condition.await();
                }
                number--;
                System.out.println(Thread.currentThread().getName() + 
                                                            "::" + number);
                condition.signalAll();
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        Share share = new Share();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 20; i++) {
                        share.incr();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 20; i++) {
                        share.decr();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 20; i++) {
                        share.incr();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 20; i++) {
                        share.decr();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"DD").start();
    }
}

3.2 定制化通信 

3.2.1 案例

线程AA,BB,CC依次打印5次,10次,15次,循环3次

思路:

3.2.2 代码实现

synchronized 只有一个队列,不管是因为竞争不到锁,还是因为某个条件没达到而阻塞,它们阻塞后都是放到一个等待队列里。

但是lock与Condition结合,可以创建多个条件队列,因为不同的条件不满足而阻塞的线程都可以放到不同的队列里, 这样就可以做到按需排队,按需通知。

package com.erp.platform.VO;

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

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.VO
 * @date 2022/2/8 8:52
 */

class ShareResource {
    private int flag = 1;
    Lock lock = new ReentrantLock();
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    public void print5(int loop) throws InterruptedException {
        lock.lock();
        try {
            while (flag != 1) {
                c1.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() +
                         ":: 打印次数:" + (i + 1) + ":: flag :" + flag +
                        "::" + " 轮数" + loop);
            }
            flag = 2;
            c2.signal();
        } finally {
            lock.unlock();
        }
    }

    public void print10(int loop) throws InterruptedException {
        lock.lock();
        try {
            while (flag != 2) {
                c2.await();
            }
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + 
                            ":: 打印次数:" + (i + 1) + ":: flag :" + flag +
                        "::" +  " 轮数" + loop);
            }
            flag = 3;
            c3.signal();
        } finally {
            lock.unlock();
        }
    }

    public void print15(int loop) throws InterruptedException {
        lock.lock();
        try {
            while (flag != 3) {
                c3.await();
            }
            for (int i = 0; i < 15; i++) {
                System.out.println(Thread.currentThread().getName() + 
                           ":: 打印次数:" + (i + 1) + ":: flag :" + flag +
                        "::" +  " 轮数" + loop);
            }
            flag = 1;
            c1.signal();
        } finally {
            lock.unlock();
        }
    }
}

public class ThreadDemo2 {
    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 1; i < 11; i++) {
                        shareResource.print5(i);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AA").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 1; i < 11; i++) {
                        shareResource.print10(i);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "BB").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 1; i < 11; i++) {
                        shareResource.print15(i);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "CC").start();
    }
}

 Condition相关知识:lock和condition

4.集合的线程安全

4.1 集合线程不安全演示

package com.erp.platform.VO;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.VO
 * @date 2022/2/8 14:04
 */
public class TreadListDemo {
    public static void main(String[] args) {
        List list = new ArrayList();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    list.add(new Random().nextInt(100));
                    System.out.println(list);
                }
            }, "" + i).start();
        }
    }
}

 

4.2解决方案-Vector

package com.erp.platform.VO;

import java.util.List;
import java.util.Random;
import java.util.Vector;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.VO
 * @date 2022/2/8 14:04
 */
public class TreadListDemo {
    public static void main(String[] args) {
        List list = new Vector();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    list.add(new Random().nextInt(100));
                    System.out.println(list);
                }
            }, "" + i).start();
        }
    }
}

 vactor集合的方法是由synchronized修饰的,是线程安全的,此解决方案古老,并不推荐使用。

4.3解决方案-Collections

package com.erp.platform.VO;

import java.util.*;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.VO
 * @date 2022/2/8 14:04
 */
public class TreadListDemo {
    public static void main(String[] args) {
        List<Integer> list = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    list.add(new Random().nextInt(100));
                    System.out.println(list);
                }
            }, "" + i).start();
        }
    }
}

 Collections类中有 synchronizedList()方法,可以实现arrylist的线程安全。

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

4.4解决方案-CopyOnWriteArrayList(写时复制)

package com.erp.platform.VO;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.VO
 * @date 2022/2/8 14:04
 */
public class TreadListDemo {
    public static void main(String[] args) {
        List<Integer> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    list.add(new Random().nextInt(100));
                    System.out.println(list);
                }
            }, "" + i).start();
        }
    }
}

 写时复制技术:在多线程读此集合时,集合时共享的(都可以读)。但是在做写操作时,会先复制出一个相同的集合,在集合中写操作,多个线程写时候会判断快照版本号,以实现线程安全。在写完后会再替换原来的集合。

        List<Integer> list = new CopyOnWriteArrayList<>();

 4.5 set map的线程安全问题

set和map也有对应的解决方案,分别是Collections类,和写时复制技术

        Set<Integer> set = new CopyOnWriteArraySet<>();
        Set<Integer> set1 = Collections.synchronizedSet(new HashSet<>());

        Map<Integer, Integer> map = new ConcurrentHashMap<>();
        Map<Integer, Integer> map1 = 
                             Collections.synchronizedMap(new HashMap<>());
       

5.多线程锁

链接:锁的种类

5.1公平锁和非公平锁

可重入锁ReentrantLock的有参构造


    /**
     * 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();
    }

参数表示,如果是true表示是公平锁,如果是false表示是非公平锁。

 公平锁:可能会使线程饿死(长时间拿不到资源),但是效率高

非公平锁:阳光普照(都能拿到资源),但是效率相对低

5.2可重入锁

synchronized是可重入锁

package com.erp.platform.VO;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.VO
 * @date 2022/2/9 14:38
 */
public class SynDemo {
    public synchronized void add() {
        add();
    }

    public static void main(String[] args) {
        SynDemo synDemo = new SynDemo();
        synDemo.add();
    }
}

执行方法后

 栈溢出异常,所以证明synchronized是可重入锁,如果不是可重入锁,则会卡在第二次调用add方法上。就不会出现栈溢出异常。

 ReentrantLock也是可重入锁。

5.3死锁

什么是死锁?

两个或者多个线程在执行过程中,因为争夺资源而造成一种相互等待的现象,如果没有外力干涉,他们无法再执行下去。

 产生死锁的原因:

  1. 系统资源不足
  2. 进程推进顺序不合适
  3. 资源分配不当

死锁代码演示: 

package com.erp.platform.VO;

import java.util.concurrent.TimeUnit;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.VO
 * @date 2022/2/9 15:12
 */
public class LockDemo {
   static Object a = new Object();
   static Object b = new Object();

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (a){
                    System.out.println("a锁获取b资源");
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (b){
                        System.out.println("已获取b资源");
                    }
                }
            }
        },"1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (b){
                    System.out.println("b锁获取a资源");
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (a){
                        System.out.println("已获取a资源");
                    }
                }
            }
        },"2").start();
    }
}

6.Callable&Future接口

创建线程的方法

  1. 创建Thread类
  2. 实现Runable接口
  3. 实现Callable接口
  4. 线程池

Runable接口和Callable接口区别:

  • Callable接口有返回值
  • Callable接口的call方法会引发异常,run方法不会
  • Callable接口要重写call方法

利用Callable接口创建线程:

Callable的使用和Runable方法有所区别

Tread的构造方法中没有Tread(Callable callable)方法,所以需要一个既和Runable有关系,又与Callable有关系的类,FutureTask类。

FutureTask实现了Runable接口,并且其构造方法可以传递Callable
 

Callable创建线程

package com.erp.platform.VO;

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

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.VO
 * @date 2022/2/9 16:06
 */

class MyTread1 implements Runnable{

    @Override
    public void run() {

    }
}

class MyTread2 implements Callable{

    @Override
    public Integer call() throws Exception {
        return 10101;
    }
}

public class CallableDemo {
    public static void main(String[] args) {
        FutureTask task1 = new FutureTask(new Callable() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName()
                                                      +":进入callable");
                return 100;
            }
        });
        new Thread(task1,"a").start();

        FutureTask task2 = new FutureTask(new MyTread2() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName()+
                                                         ":进入callable");
                return 100;
            }
        });
        new Thread(task2,"b").start();
    }
}

Callable简单使用

  • boolean cancel(boolean mayInterruptInRunning) 取消一个任务,并返回取消结果。参数表示是否中断线程。
  • boolean isCancelled() 判断任务是否被取消
  • Boolean isDone() 判断当前任务是否执行完毕,包括正常执行完毕、执行异常或者任务取消。
  • V get()   获取任务执行结果,任务结束之前会阻塞。
  • V get(long timeout, TimeUnit unit)   在指定时间内尝试获取执行结果。若超时则抛出超时异常
package com.erp.platform.VO;

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

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.VO
 * @date 2022/2/9 16:06
 */

class MyTread1 implements Runnable{

    @Override
    public void run() {

    }
}

class MyTread2 implements Callable{

    @Override
    public Integer call() throws Exception {
        return 10101;
    }
}

public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, 
        InterruptedException {
        FutureTask task2 = new FutureTask(new MyTread2() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName()+
                                                        ":进入callable");
                return 100;
            }
        });
        new Thread(task2,"b").start();
        
        while (!task2.isDone()){
            System.out.println("wait...........");
        }

        System.out.println(task2.get());

        System.out.println(Thread.currentThread().getName()+":已经结束");
    }
}

FutureTask使用举例:

  • 有4个同学,1同学计算1+2+3+4,2同学计算1+2+.....+10000,3同学计算20+21+22,4同学计算100+200+300。
  • 这时同学2的计算量比较大,FutureTask单开启线程给2同学计算,先汇总1,3,4同学的计算数据,等2同学完成后再调用get汇总全部。

FutureTask只会做一次汇总。即使第二次调用get方法,也不会再重新计算一遍

package com.erp.platform.VO;

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

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.platform.VO
 * @date 2022/2/9 16:06
 */

class MyTread1 implements Runnable {

    @Override
    public void run() {

    }
}

class MyTread2 implements Callable {

    @Override
    public Integer call() throws Exception {
        return null;
    }
}

public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, 
InterruptedException {
        FutureTask task = new FutureTask(new MyTread2() {
            @Override
            public Integer call() throws Exception {
                int result = 0;
                System.out.println(Thread.currentThread().getName() + 
                                                        ":进入callable");
                for (int i = 0; i < 10000; i++) {
                    result += i;
                }
                System.out.println("b同学计算完成" + result);
                return result;
            }
        });
        new Thread(task, "b").start();
        int a = 1 + 2 + 3 + 4;
        int c = 20 + 21 + 22;
        int d = 100 + 200 + 300;

        int s = a + c + d;
        System.out.println("三位同学汇总" + s);

        int b = (int) task.get();
        int sum = s + b;
        System.out.println("四位同学汇总:" + sum);
        System.out.println("统计b同学数据" + task.get());
        System.out.println(Thread.currentThread().getName() + ":已经结束");
    }
}

 

可以看出第二次get()返回数据时并没有再次进入线程做计算。 

7.JUC三大辅助类

7.1减少计数CountDownLatch

CountDownLatch类中可以设置一个计数器,可以通过调用countDown方法进行-1操作,如果计数器大于0时会执行await方法,只有计数器等于0后才会执行下面的逻辑。

  • CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞。
  • 其他线程调用countDown方法会使计数器-1(调用countDown方法线程不会阻塞)
  • 当计数器值变为0时,因为await方法阻塞的线程会被唤醒,继续执行。

代码演示:有6名同学值日,当值日的同学完成后,班长会执行锁门操作。

不使用CountDownLatch:

package com.erp.payroll.test.VO;

import java.util.concurrent.CountDownLatch;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/10 10:03
 */
public class CountDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch count = new CountDownLatch(6);
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() +
                                                     "同学已经完成值日");
 
            }, String.valueOf(i)).start();
        }

        System.out.println(Thread.currentThread().getName() + ":班长锁门");
    }
}
0同学已经完成值日
1同学已经完成值日
2同学已经完成值日
main:班长锁门
4同学已经完成值日
5同学已经完成值日
3同学已经完成值日

进程已结束,退出代码为 0

使用 CountDownLatch:

package com.erp.payroll.test.VO;

import java.util.concurrent.CountDownLatch;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/10 10:03
 */
public class CountDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch count = new CountDownLatch(6);
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() +
                                                         "同学已经完成值日");
                count.countDown();
            }, String.valueOf(i)).start();
        }
        count.await();
        System.out.println(Thread.currentThread().getName() + 
                                                            ":班长锁门");
    }
}

0同学已经完成值日
3同学已经完成值日
2同学已经完成值日
1同学已经完成值日
5同学已经完成值日
4同学已经完成值日
main:班长锁门

进程已结束,退出代码为 0

7.2循环栅栏CyclicBarrier

CyclicBarrier类可以使一组线程等待,当这一组线程最后一个线程(可以设置数量)完成后,就会同时唤醒。

代码演示:集齐七龙珠之后会召唤神龙

package com.erp.payroll.test.VO;

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

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/10 10:03
 */
public class CountDemo {
   private static int num = 7;
    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(num, new Runnable() {
            @Override
            public void run() {
                System.out.println("=====集齐七龙珠,召唤神龙");
            }
        });

        for (int i = 1; i < 7; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+
                                                        "号龙珠被收集");
                try {
                    cyclicBarrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
        
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+
                                                            "龙珠被收集");
            try {
                cyclicBarrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"最后").start();
    }
}
2号龙珠被收集
5号龙珠被收集
6号龙珠被收集
4号龙珠被收集
1号龙珠被收集
3号龙珠被收集
最后龙珠被收集
=====集齐七龙珠,召唤神龙

7.3信号灯Semaphore

Semaphore是计数信号量,信号量维护了一个许可集,在许可可以使用之前(停车位不满)会阻塞每一个acquire(),然后获取该许可,release()方法(一辆汽车驶离停车位)添加一个许可,从而释放一个正在阻塞的获取者(等待停车的汽车)。

代码演示:3个停车位停10辆车,每辆随机停一定时间

package com.erp.payroll.test.VO;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/10 10:03
 */
public class CountDemo {

    public static void main(String[] args) throws InterruptedException {
     
        //设置信号量
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i < 11; i++) {
            new Thread(() -> {
                try {
                    //抢占
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + 
                                                                "车已经停靠");
                    //随机停车时间
                    new Random().nextInt(10);
                    //驶离
                    System.out.println(Thread.currentThread().getName() + 
                                                      "已经驶离=============");
                 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //驶离后才会释放
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
      
    }
}

 与CountDownLatch结合使用:

package com.erp.payroll.test.VO;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/10 10:03
 */
public class CountDemo {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch count = new CountDownLatch(10);
        //设置信号量
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i < 11; i++) {
            new Thread(() -> {
                try {
                    //抢占
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() 
                                                            + "车已经停靠");
                    //随机停车时间
                    new Random().nextInt(10);
                    //驶离
                    System.out.println(Thread.currentThread().getName() 
                                                + "已经驶离=============");
                    count.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //驶离后才会释放
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
        count.await();
        System.out.println("车已经全部驶离");
    }
}
3车已经停靠
1车已经停靠
2车已经停靠
3已经驶离=============
1已经驶离=============
2已经驶离=============
5车已经停靠
4车已经停靠
5已经驶离=============
6车已经停靠
7车已经停靠
4已经驶离=============
7已经驶离=============
6已经驶离=============
10车已经停靠
8车已经停靠
10已经驶离=============
9车已经停靠
8已经驶离=============
9已经驶离=============
车已经全部驶离

8.读写锁

8.1锁简介

8.1.1悲观锁和乐观锁

悲观锁:每次被调用都会认为是写操作,都会先加锁,不让别的线程调用。

效率低,不支持并发。

乐观锁:每次被调用都会认为是读操作,每次修改都会修改一个版本号,如果同时被修改,后操作的会出现版本号不一致,然后提交失败。

8.1.2表锁和行锁

表锁:当操作某条数据时候,整个表就会加锁,不允许别的线程操作表里的数据

行锁:只会锁当前一行的数据,表里的其他数据不会受影响。行锁可能会造成死锁。

8.1.3读锁和写锁

读锁:共享锁 ,可能会发生死锁

发生死锁分析:线程a和线程b对同一条数据进行读操作(共享锁),线程a读取之后对数据要做修改(排它锁),写操作时是不允许有其他锁的,所以就会等待b读完,但是b也想在读完之后对数据进行修改,所以a,b相互等待对方读完,造成死锁

写锁: 排它锁,可能会发生死锁

发生死锁分析:线程a同时对数据1和数据2进行修改,再对数据1修改完成准备对数据2进行修改时候,线程b对数据2进行了修改,而线程2是要对数据2和数据1进行修改的,然后再对数据1进行修改,而此时数据1有线程a的排它锁,无法修改。这样就造成了死锁。

8.2读写锁基本操作

代码演示:

未使用读写锁:

package com.erp.payroll.test.VO;

import com.sun.jersey.core.util.StringIgnoreCaseKeyComparator;

import javax.ws.rs.PUT;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/10 10:03
 */
class ReadWritDemo {
    private volatile Map<String, Object> map = new HashMap<>();

    public void put(String k, Object v) {
        try {
            System.out.println(Thread.currentThread().getName() + 
                                        "开始添加数据!!!!!!" + k);
            map.put(k, v);
            TimeUnit.MILLISECONDS.sleep(300);
            System.out.println(Thread.currentThread().getName() 
                                    + "数据已经添加完成======" + k);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public Object get(String k) {
        Object o = null;
        try {
            System.out.println(Thread.currentThread().getName() 
                                        + "!!!!!开始拿取数据" + k);
            o = map.get(k);
            // TimeUnit.MILLISECONDS.sleep(300);
            System.out.println(Thread.currentThread().getName() 
                                        + "========数据拿取结束" + k);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return o;
    }
}

public class CountDemo {
    public static void main(String[] args) {
        ReadWritDemo readWritDemo = new ReadWritDemo();
        for (int i = 0; i < 3; i++) {
            final String num = String.valueOf(i);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    readWritDemo.put(num, "1234");
                }
            }, "writ").start();
        }

        for (int i = 0; i < 3; i++) {
            final String num = String.valueOf(i);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    readWritDemo.get(num);
                }
            }, "read").start();
        }
    }
}

演示结果:

writ开始添加数据!!!!!!1
writ开始添加数据!!!!!!2
writ开始添加数据!!!!!!0
read!!!!!开始拿取数据0
read========数据拿取结束0
read!!!!!开始拿取数据2
read!!!!!开始拿取数据1
read========数据拿取结束2
read========数据拿取结束1
writ数据已经添加完成======1
writ数据已经添加完成======0
writ数据已经添加完成======2

进程已结束,退出代码为 0

 未添加完成就已经拿取结束。

使用读写锁:

package com.erp.payroll.test.VO;

import com.sun.jersey.core.util.StringIgnoreCaseKeyComparator;

import javax.ws.rs.PUT;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/10 10:03
 */
class ReadWritDemo {
    private volatile Map<String, Object> map = new HashMap<>();
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    public void put(String k, Object v) {
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() 
                                    + "开始添加数据!!!!!!" + k);
            map.put(k, v);
            TimeUnit.MILLISECONDS.sleep(300);
            System.out.println(Thread.currentThread().getName() 
                                        + "数据已经添加完成======" + k);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

    public Object get(String k) {
        lock.readLock().lock();
        Object o = null;
        try {
            System.out.println(Thread.currentThread().getName() 
                                        + "!!!!!开始拿取数据" + k);
            o = map.get(k);
            // TimeUnit.MILLISECONDS.sleep(300);
            System.out.println(Thread.currentThread().getName() 
                                        + "========数据拿取结束" + k);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
        return o;
    }
}

public class CountDemo {
    public static void main(String[] args) {
        ReadWritDemo readWritDemo = new ReadWritDemo();
        for (int i = 0; i < 3; i++) {
            final String num = String.valueOf(i);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    readWritDemo.put(num, "1234");
                }
            }, "writ").start();
        }

        for (int i = 0; i < 3; i++) {
            final String num = String.valueOf(i);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    readWritDemo.get(num);
                }
            }, "read").start();
        }
    }
}
writ开始添加数据!!!!!!0
writ数据已经添加完成======0
writ开始添加数据!!!!!!1
writ数据已经添加完成======1
writ开始添加数据!!!!!!2
writ数据已经添加完成======2
read!!!!!开始拿取数据0
read========数据拿取结束0
read!!!!!开始拿取数据1
read!!!!!开始拿取数据2
read========数据拿取结束1
read========数据拿取结束2

进程已结束,退出代码为 0

 可以看出写锁是互斥的;读锁是共享的,可以多个一起取。

附:

在并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题

链接:volatile关键字解析


并发编程-可见性

  可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

  举个简单的例子,看下面这段代码:

1

2

3

4

5

6

//线程1执行的代码

int i = 0;

i = 10;

//线程2执行的代码

j = i;

   假若执行线程1的是CPU1,执行线程2的是CPU2。由上面的分析可知,当线程1执行 i =10这句时,会先把i的初始值加载到CPU1的高速缓存中,然后赋值为10,那么在CPU1的高速缓存当中i的值变为10了,却没有立即写入到主存当中。

  此时线程2执行 j = i,它会先去主存读取i的值并加载到CPU2的缓存当中,注意此时内存当中i的值还是0,那么就会使得j的值为0,而不是10.

  这就是可见性问题,线程1对变量i修改了之后,线程2没有立即看到线程1修改的值。


可见性

  对于可见性,Java提供了volatile关键字来保证可见性。

  当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

  而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

  另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。


8.3锁的演变

读写锁:一个资源可以被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程,读写互斥,读读共享。

无锁synchronized和ReentrantLock

读写锁

ReentrantReadWriteLock

多线程抢夺资源混乱每个都是独占的,每次只能一个线程进行操作写锁只能一个进行操作,但是读锁可以多人进行共享

 读写锁缺点:

  • 造成锁饥饿,一直读,没有写(读是共享的,可能会出现一直有读线程占用资源,写线程拿不到写锁。)

8.4锁的降级

锁降级指的是将写锁降级为读锁。

 注意:读锁不能升级为写锁

代码演示:

package com.erp.payroll.test.VO;

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

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/11 14:53
 */
class Demo {
    ReadWriteLock lock = new ReentrantReadWriteLock();

    private volatile Map<String, Object> map = new HashMap<>();

    public Object update(String k, Object v) throws Exception {
        Object o = null;
        lock.writeLock().lock();
        try {

            System.out.println(Thread.currentThread().getName() 
                                    + "开始添加数据!!!!!!" + k);
            map.put(k, v);
            System.out.println(Thread.currentThread().getName()
                                     + "数据已经添加完成======" + k);

            lock.readLock().lock();
        } finally {
            lock.writeLock().unlock();
            // 锁降级结束,降级为读锁
        }
        TimeUnit.MILLISECONDS.sleep(500);
        System.out.println(Thread.currentThread().getName() 
                                    + "!!!!!开始拿取数据" + k);
        o = map.get(k);
        System.out.println(Thread.currentThread().getName() 
                                        + "========数据拿取结束" + k);

        System.out.println(Thread.currentThread().getName() 
                                    + "当前线程消费数据完成,释放读锁");
        lock.readLock().unlock();
        return o;
    }

}

public class ReadDemo {
    public static void main(String[] args) {
        Demo demo = new Demo();
        for (int i = 0; i < 100; i++) {
            final String num = String.valueOf(i);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        demo.update(num, "1234");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }, String.valueOf(i)).start();
        }
    }
}

上述代码没有得到想要的效果。(有待变化)

9.阻塞队列 

9.1阻塞队列概述

阻塞队列(BlockingQueue)是一个共享队列,拥有着队列的先进先出的特性,由队列的一端输入,从另一端输出。

9.2阻塞队列架构

所谓阻塞是在某些情况下会挂起线程,一旦条件满足,被挂起的线程又会自动被唤起。

而使用BlockingQueue就不需要关心什么时候需要阻塞线程,什么时候需要唤起线程。这些都由BlockingQueue进行处理。

9.3阻塞队列分类

BlockingQueue是一个接口,其下有好多实现类。

ArrayBlockingQueue(常用)

此队列是基于数组实现的阻塞队列,在ArrayBlockingQueue内部维护了一个定长的数组,以便缓存队列中的数据对象。

LinkeBlockingQueue(常用)

基于链表的阻塞队列,其内部也维持着有界的(大小默认为integer.MAX_VALUE)阻塞队列。

DelayQueue

优先级阻塞队列中的元素只有当其指定的延迟时间到了,才能从队列中获取到该元素。

是一个没有大小限制的队列,生产者插入数据永远不会被阻塞,只有消费者才会被阻塞。

PriorityBlockingQueue

支持优先级排序的无界阻塞队列

注意:生产者生产数据的速度绝对不能快于消费者消费数据的速度。

SynchronousQueue

是无缓冲的队列,生产者要把数据给到消费者否则会阻塞。

9.4阻塞队列核心方法 

核心方法:

 代码演示:

add,remove

package com.erp.payroll.test.VO;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/15 9:40
 */
public class QueueDemo {

    public static void main(String[] args) {
        BlockingQueue queue = new ArrayBlockingQueue(3);
        System.out.println(queue.add("a"));
        System.out.println(queue.add("b"));
        System.out.println(queue.add("c"));
        // System.out.println(queue.add("q"));
        // System.out.println(queue.element());

        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        // System.out.println(queue.remove());
    }

}
true
true
true
a
b
c

 错误演示:

package com.erp.payroll.test.VO;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/15 9:40
 */
public class QueueDemo {

    public static void main(String[] args) {
        BlockingQueue queue = new ArrayBlockingQueue(3);
        System.out.println(queue.add("a"));
        System.out.println(queue.add("b"));
        System.out.println(queue.add("c"));
        System.out.println(queue.add("q"));
        System.out.println(queue.element());

        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        // System.out.println(queue.remove());
    }

}

 

 

当超过队列最大值时会报错,当队列中没有数据时候,remove方法也会报错。

offer,poll

package com.erp.payroll.test.VO;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/15 9:40
 */
public class QueueDemo {

    public static void main(String[] args) {
        BlockingQueue queue = new ArrayBlockingQueue(3);
        System.out.println(queue.offer("a"));
        System.out.println(queue.offer("b"));
        System.out.println(queue.offer("c"));
        System.out.println(queue.offer("q"));
        System.out.println(queue.element());

        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}
true
true
true
false
a
a
b
c
null

put,take

package com.erp.payroll.test.VO;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/15 9:40
 */
public class QueueDemo {

    public static void main(String[] args) {
        BlockingQueue queue = new ArrayBlockingQueue(3);
        try {
            queue.put("a");
            queue.put("b");
            queue.put("c");
            queue.put("q");

            queue.take();
            queue.take();
            queue.take();
            queue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 会出现一直阻塞状态。

offer(time),poll(time)

会根据设置时间结束阻塞

package com.erp.payroll.test.VO;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/15 9:40
 */
public class QueueDemo {

    public static void main(String[] args) {
        BlockingQueue queue = new ArrayBlockingQueue(3);
        System.out.println(queue.offer("a"));
        System.out.println(queue.offer("b"));
        System.out.println(queue.offer("c"));
        try {
            System.out.println(queue.offer("q",3L, TimeUnit.SECONDS));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(queue.element());

        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}

 3秒内是阻塞状态,3秒后直接false

true
true
true
false
a
a
b
c
null

10.ThreadPool线程池

10.1线程池概述

线程池是一种线程的使用模式,线程过多会带来调度开销,进而性影响缓存局部和整体性能,而线程池维护多个线程,等待着监督管理者分配可并发执行的任务,避免了短时间任务时线程创建和销毁的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。

执行过程:线程池只是控制线程的运行数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过最大数量,超出数量的任务排队等候,等其他线程执行完毕之后,在从队列中取出任务来执行。

主要特点:

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统稳定性,使用线程池可以进行统一分配,调度和优化。
  • java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor类

10.2线程池架构

10.3线程池使用方式

多种线程池:

  • Executors.newFixedThreadPool(int):一池N线程
  • Executors.newSingleThreadExecutor():一池一线程,单任务执行
  • Executors.newCachedThreadPool():线程池根据需求创建线程,可扩容,遇强则强

Executors.newFixedThreadPool(int):

package com.erp.payroll.test.VO;

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

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/16 10:46
 */
public class ThreadPoolDemo {
    public static void main(String[] args) {
        //一池五线程
        ExecutorService threadPool_1 = Executors.newFixedThreadPool(5);


        //10个任务

        try {
            for (int i = 1; i <=10 ; i++) {
                threadPool_1.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName()
                                                                 +"执行任务");
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool_1.shutdown();
        }
    }
}
pool-1-thread-2执行任务
pool-1-thread-5执行任务
pool-1-thread-3执行任务
pool-1-thread-4执行任务
pool-1-thread-1执行任务
pool-1-thread-4执行任务
pool-1-thread-3执行任务
pool-1-thread-1执行任务
pool-1-thread-2执行任务
pool-1-thread-5执行任务

 五个线程多次执行。


Executors.newSingleThreadExecutor():

package com.erp.payroll.test.VO;

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

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/16 10:46
 */
public class ThreadPoolDemo {
    public static void main(String[] args) {
        //一池一线程
        ExecutorService threadPool_1 = Executors.newSingleThreadExecutor();


        //10个任务

        try {
            for (int i = 1; i <=10 ; i++) {
                threadPool_1.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName()+
                                                                   "执行任务");
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool_1.shutdown();
        }
    }
}
pool-1-thread-1执行任务
pool-1-thread-1执行任务
pool-1-thread-1执行任务
pool-1-thread-1执行任务
pool-1-thread-1执行任务
pool-1-thread-1执行任务
pool-1-thread-1执行任务
pool-1-thread-1执行任务
pool-1-thread-1执行任务
pool-1-thread-1执行任务

 一个线程执行任务。


Executors.newCachedThreadPool():

package com.erp.payroll.test.VO;

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

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/16 10:46
 */
public class ThreadPoolDemo {
    public static void main(String[] args) {
        //可变化线程池
        ExecutorService threadPool_1 = Executors.newCachedThreadPool();

        //10个任务

        try {
            for (int i = 1; i <=10 ; i++) {
                threadPool_1.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName()+
                                                                   "执行任务");
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool_1.shutdown();
        }
    }
}
pool-1-thread-1执行任务
pool-1-thread-5执行任务
pool-1-thread-4执行任务
pool-1-thread-3执行任务
pool-1-thread-2执行任务
pool-1-thread-7执行任务
pool-1-thread-6执行任务
pool-1-thread-8执行任务
pool-1-thread-9执行任务
pool-1-thread-2执行任务

可根据任务创建多个线程 

10.4线程池底层原理

三个线程池都其源码都是通过ThreadPoolExecutor() 实现的。不同的是其内部的参数。

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




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




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

10.5线程池的参数

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

参数:

  • int corePoolSize:常驻线程数量(常驻线程)
  • int maximumPoolSize:最大线程相数量
  • long keepAliveTime:线程存活时间(非核心线程空闲时存活时间)
  • TimeUnit unit:线程存活时间单位
  • BlockingQueue<Runnable> workQueue:阻塞队列
  • ThreadFactory threadFactory:线程工厂
  • RejectedExecutionHandler handler:拒绝策略

10.6线程池底层工作流程

jdk内置的拒绝策略:(当全部线程被占用,并且阻塞队列也满了时候,则执行拒绝策略)

  • AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行
  • CallerRunsPolicy:该策略不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
  • DiscardOlderstPolicy:抛弃队列中等待最久的任务,然后把当前任务加入到队列中尝试再次提交当前任务。
  • DiscardPolicy:该策略默默的丢弃无法处理的任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种策略。

10.7自定义线程池

阿里巴巴java开发手册说明,线程池不允许使用Executors去创建,而是通过TreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,避免资源耗尽的风险。

说明:Executors返回线程对象的弊端如下:

  • FixedThreadPool和SingleThreadExecutor:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM错误
  • CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会堆积大量的线程,从而导致OOM错误

自定义线程池(threadPool_2 ): 

package com.erp.payroll.test.VO;

import java.util.concurrent.*;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/16 10:46
 */
public class ThreadPoolDemo {
    public static void main(String[] args) {
        //可变化线程池
        ExecutorService threadPool_1 = Executors.newCachedThreadPool();

        //自定义线程池
        ExecutorService threadPool_2 = new ThreadPoolExecutor(2,
                5,
                2L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        //10个任务

        try {
            for (int i = 1; i <=10 ; i++) {
                threadPool_1.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName()+
                                                                    "执行任务");
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool_1.shutdown();
        }
    }
}

11.Fork/join分支合并框架

fork/join框架可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出。要继承RecursiveTask接口

Fork/Join框架要完成的两件事情:

  • Fork:把一个复杂任务进行拆分,大事化小
  • Join:把分拆任务的结果进行合并

案例演示:1到100相加减,把1-100进行二分法拆分,最大和最小相差不大于10时候进行相加。

package com.erp.payroll.test.VO;

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

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/16 15:14
 */
class MyTask extends RecursiveTask<Integer> {
    //拆分的差值不能超过10
    private static final Integer VALUE = 10;
    private int begin;//拆分开始值
    private int end;//拆分结束值
    private int result;//返回结果

    //创建有参构造函数
    public MyTask(int begin, int end) {
        this.begin = begin;
        this.end = end;
    }

    /**
     * The main computation performed by this task.
     *
     * @return the result of the computation
     */
    //拆分合并的过程
    @Override
    protected Integer compute() {
        //判断相加的两个数值是否大于10
        if ((end - begin) <= VALUE) {
            for (int i = begin; i <= end; i++) {
                result = result + i;
            }
        } else {//进一步拆分
            //获取中间值
            int middle = (begin + end) / 2;
            //拆分左边
            MyTask task1 = new MyTask(begin, middle);
            //拆分右边
            MyTask task2 = new MyTask(middle + 1, end);
            //调用方法拆分
            task1.fork();
            task2.fork();
            //调用方法合并
            result = task1.join() + task2.join();
        }
        return result;
    }
}

public class ForkJoinDemo {
    public static void main(String[] args) throws ExecutionException, 
                                                InterruptedException {
        //创建MyTask对象
        MyTask myTask = new MyTask(0, 100);
        //创建分支合并池对象
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Integer> submit = forkJoinPool.submit(myTask);
        //获取最终合并之后的结果
        Integer result = submit.get();
        System.out.println(result);
        //关闭池对象
        forkJoinPool.shutdown();
    }
}

12.CompletableFuture异步回调

同步和异步:

方法一和方法二是同步,执行完方法一才可以执行方法二。

方法二和方法三是异步,两个方法可以同时执行。

异步调用并不是要减少线程的开销, 它的主要目的是让调用方法的主线程不需要同步等待在这个函数调用上, 从而可以让主线程继续执行它下面的代码.与此同时, 系统会通过从ThreadPool中取一个线程来执行。

异步与多线程,从辩证关系上来看,异步和多线程并不时一个同等关系,异步是目的,多线程只是我们实现异步的一个手段.什么是异步:异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回.实现异步可以采用多线程技术或则交给另外的进程来处理

链接:多线程与异步回调

package com.erp.payroll.test.VO;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * @author guang
 * @version V1.0
 * @Package com.erp.payroll.test.VO
 * @date 2022/2/16 16:43
 */
public class Completable {
    public static void main(String[] args) throws Exception {
        //异步回调,没有返回值
        CompletableFuture<Void> completableFuture1 = 
                    CompletableFuture.runAsync(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+
                                                    "异步回调没有返回值");
            }
        });
        completableFuture1.get();
        
        //异步回调,有返回值
        CompletableFuture<Integer> completableFuture2 = 
                                CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+
                                                "异步回调,有返回值");
            return 100;
        });
        completableFuture2.whenComplete((t,u)->{
            System.out.println(t);//返回值
            System.out.println(u);//错误信息
        }).get();
    }
}

报错时候:

        //异步回调,有返回值
        CompletableFuture<Integer> completableFuture2 
                            = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() +
                                                     "异步回调,有返回值");
            int a = 1 / 0;
            return 100;
        });
        completableFuture2.whenComplete((t, u) -> {
            System.out.println(t);//返回的值
            System.out.println(u);//报错时返回的错误
        }).get();
ForkJoinPool.commonPool-worker-9异步回调没有返回值
ForkJoinPool.commonPool-worker-9异步回调,有返回值
null
java.util.concurrent.CompletionException: 
          java.lang.ArithmeticException: / by zero

bilibili:bilibili

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值