JUC, Lock, ThreadPool

1. JUC多线程及并发包

1.1 谈谈你对volatile的理解

  • volatile是Java虚拟机提供的轻量级的同步机制(保证内存可见性,不保证原子性,禁止指令重排)

可见性:

可能存在一个线程AAA修改了共享变量X的值还未写回主内存中时,另外一个线程BBB又对内存中的共享变量X进行操作,但此时AAA线程工作内存中的共享变量X对线程B来说并不可见。这种工作内存与主内存同步延迟现象就造成了可见性问题。(volatile保证了变量同步,解决了可见性问题,基于缓存一致协议(MESI)来实现可见性)。

原子性:

number++; // 在多线程下是非线程安全的,如何不加synchronized解决?

    AtomicInteger atomicInteger = new AtomicInteger();
    public void addAtomic(){
        atomicInteger.getAndIncrement(); // CAS
    }

有序性:

计算机在执行程序时,为了提高性能,编译器和处理器常常会做指令重排,一把分为以下3中

处理器在进行重新排序是必须要考虑指令之间的数据依赖性。单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致;多线程环境中线程交替执行,由于编译器优化重排的存在,两线程使用的变量能否保持一致性是无法确定的,结果无法预测。

 

  • 谈谈JMM

JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念 并不真实存在,它描述的是一组规则或规范,通过规范定制了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式,保证可见性,原子性,有序性

JMM关于同步规定:
1.线程解锁前,必须把共享变量的值刷新回主内存
2.线程加锁前,必须读取主内存的最新值到自己的工作内存
3.加锁解锁是同一把锁

由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方成为栈空间)。工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存。主内存是共享内存区域,所有线程都可访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行:首先要将变量从主内存拷贝到自己的工作空间,然后对变量进行操作,操作完成再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存储存着主内存中的变量副本拷贝。因此不同的线程无法访问对方的工作内存,线程间的通讯(传值) 必须通过主内存来完成,其简要访问过程如下图:

 

  •  你在哪些地方用到过volatile?
// 单例模式DCL代码
public class SingletonDemo {

    private static volatile SingletonDemo instance=null;
    private SingletonDemo(){
        System.out.println(Thread.currentThread().getName()+"\t 构造方法");
    }

    /**
     * 双重检测机制
     * @return
     */
    public static SingletonDemo getInstance(){
        if(instance==null){
            synchronized (SingletonDemo.class){
                if(instance==null){
                    instance=new SingletonDemo();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 1; i <=10; i++) {
            new Thread(() ->{
                SingletonDemo.getInstance();
            },String.valueOf(i)).start();
        }
    }
}
DCL(双端检锁) 机制不一定线程安全,原因是有指令重排的存在,加入volatile可以禁止指令重排
  原因在于某一个线程在执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化.
instance=new SingletonDem(); 可以分为以下步骤(伪代码)
 
memory=allocate();//1.分配对象内存空间
instance(memory);//2.初始化对象
instance=memory;//3.设置instance的指向刚分配的内存地址,此时instance!=null 
 
步骤2和步骤3不存在数据依赖关系.而且无论重排前还是重排后程序执行的结果在单线程中并没有改变,因此这种重排优化是允许的.
memory=allocate();//1.分配对象内存空间
instance=memory;//3.设置instance的指向刚分配的内存地址,此时instance!=null 但对象还没有初始化完.
instance(memory);//2.初始化对象
但是指令重排只会保证串行语义的执行一致性(单线程) 并不会关心多线程间的语义一致性
所以当一条线程访问instance不为null时,由于instance实例未必完成初始化,也就造成了线程安全问题.

1.2 CAS

CAS的全称为Compare-And-Swap ,它是一条CPU并发原语.它的功能是判断内存某个位置的值是否为预期值,如果是则更新为新的值,这个过程是原子的.
 
CAS并发原语体现在Java语言中就是sun.misc.UnSafe类中的各个方法.调用UnSafe类中的CAS方法,JVM会帮我实现CAS汇编指令.这是一种完全依赖于硬件 功能,通过它实现了原子操作,再次强调,由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许中断,也即是说CAS是一条原子指令,不会造成所谓的数据不一致的问题.

/**
 * Description
 *
 *  什么是CAS ? ===> compareAndSet 比较并交换
 *  
 **/
public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);
        System.out.println(atomicInteger.compareAndSet(5, 2020)+"\t current"+atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(5, 2000)+"\t current"+atomicInteger.get());
    }
}

底层原理是什么?自旋锁,UnSafe类

var1 AtomicInteger对象本身.
var2 该对象值的引用地址
var4 需要变动的数值(步长)
var5 是用过var1 var2找出主内存中真实的值
用该对象当前的值与var5比较
如果相同,更新var5的值并且返回true
如果不同,继续取值然后比较,直到更新完成
===================================
假设线程A和线程B两个线程同时执行getAndAddInt操作(分别在不同的CPU上):
 
1.AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据JMM模型,线程A和线程B各自持有一份值为3的value的副本分别到各自的工作内存.
 
2.线程A通过getIntVolatile(var1,var2) 拿到value值3,这是线程A被挂起.
 
3.线程B也通过getIntVolatile(var1,var2) 拿到value值3,此时刚好线程B没有被挂起并执行compareAndSwapInt方法比较内存中的值也是3 成功修改内存的值为4 线程B打完收工 一切OK.
 
4.这是线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的数值和内存中的数字4不一致,说明该值已经被其他线程抢先一步修改了,那A线程修改失败,只能重新来一遍了.
 
5.线程A重新获取value值,因为变量value是volatile修饰,所以其他线程对他的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt方法进行比较替换,直到成功.

UnSafe:

1.UnSafe是CAS的核心类 由于Java 方法无法直接访问底层 ,需要通过本地(native)方法来访问,UnSafe相当于一个后门,基于该类可以直接操作特额定的内存数据.UnSafe类在于sun.misc包中,其内部方法操作可以向C的指针一样直接操作内存,因为Java中CAS操作的助兴依赖于UnSafe类的方法.注意UnSafe类中所有的方法都是native修饰的,也就是说UnSafe类中的方法都是直接调用操作底层资源执行响应的任务

2.变量ValueOffset,便是该变量在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的

3.变量value和volatile修饰,保证了多线程之间的可见性.

 CAS的缺点:

1.循环时间长开销很大

2.只能保证一个共享变量的原子性

3. 引出来ABA问题(原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗?)

ABA问题的产生

 1.3 原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗

原子引用,AtomicReferenceDemo:

/**
 * Description:
 *
 **/
@Getter@Setter@AllArgsConstructor@ToString
class User{
    private String name;
    private int age;
}
public class AtomicReferenceDemo {
    public static void main(String[] args) {
        User zs = new User("zs", 22);
        User ls = new User("ls", 22);
        AtomicReference<User> userAtomicReference = new AtomicReference<>();
        userAtomicReference.set(zs);
        System.out.println(userAtomicReference.compareAndSet(zs, ls)+"\t"+userAtomicReference.get().toString());
        System.out.println(userAtomicReference.compareAndSet(zs, ls)+"\t"+userAtomicReference.get().toString());
    }
}

/**
 * Description: ABA问题的解决
 *
 **/
public class ABADemo {
    private static AtomicReference<Integer> atomicReference=new AtomicReference<>(100);
    private static AtomicStampedReference<Integer> stampedReference=new AtomicStampedReference<>(100,1);
    public static void main(String[] args) {
        System.out.println("===以下是ABA问题的产生===");
        new Thread(()->{
            atomicReference.compareAndSet(100,101);
            atomicReference.compareAndSet(101,100);
        },"t1").start();

        new Thread(()->{
            //先暂停1秒 保证完成ABA
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(atomicReference.compareAndSet(100, 2019)+"\t"+atomicReference.get());
        },"t2").start();
        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("===以下是ABA问题的解决===");

        new Thread(()->{
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t 第1次版本号"+stamp+"\t值是"+stampedReference.getReference());
            //暂停1秒钟t3线程
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

            stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t 第2次版本号"+stampedReference.getStamp()+"\t值是"+stampedReference.getReference());
            stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t 第3次版本号"+stampedReference.getStamp()+"\t值是"+stampedReference.getReference());
        },"t3").start();

        new Thread(()->{
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t 第1次版本号"+stamp+"\t值是"+stampedReference.getReference());
            //保证线程3完成1次ABA
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean result = stampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"\t 修改成功否"+result+"\t最新版本号"+stampedReference.getStamp());
            System.out.println("最新的值\t"+stampedReference.getReference());
        },"t4").start();
    }
 

1.4 我们知道ArrayList是线程不安全,请编写一个不安全的案例并给出解决方案

**
 * Description: 集合类不安全的问题
 *
 **/
public class ContainerNotSafeDemo {
    /**
     * 笔记
     * 写时复制 copyOnWrite 容器即写时复制的容器 往容器添加元素的时候,不直接往当前容器object[]添加,而是先将当前容器object[]进行
     * copy 复制出一个新的object[] newElements 然后向新容器object[] newElements 里面添加元素 添加元素后,
     * 再将原容器的引用指向新的容器 setArray(newElements);
     * 这样的好处是可以对copyOnWrite容器进行并发的读,而不需要加锁 因为当前容器不会添加任何元素.所以copyOnwrite容器也是一种
     * 读写分离的思想,读和写不同的容器.
     *     public boolean add(E e) {
     *         final ReentrantLock lock = this.lock;
     *         lock.lock();
     *         try {
     *             Object[] elements = getArray();
     *             int len = elements.length;
     *             Object[] newElements = Arrays.copyOf(elements, len + 1);
     *             newElements[len] = e;
     *             setArray(newElements);
     *             return true;
     *         } finally {
     *             lock.unlock();
     *         }
     *     }
     * @param args
     */
    public static void main(String[] args) {
        List<String> list= new CopyOnWriteArrayList<>();
        for (int i = 1; i <=30; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(1,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
        /**
         * 1.故障现象
         *  java.util.ConcurrentModificationException
         * 2.导致原因
         *    并发争抢修改导致
         * 3.解决方案
         *  3.1 new Vector<>()
         *  3.2 Collections.synchronizedList(new ArrayList<>());
         *  3.3 new CopyOnWriteArrayList<>();
         * 4.优化建议
         *  4.1 List线程copyOnWriteArrayList
         *  4.2 Set线程CopyOnwriteArraySet
         *  4.3 Map线程ConcurrentHashMap
         */
    }
 

1.5 公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁

  • 公平锁和非公平锁

公平锁:是指多个线程按照申请锁的顺序来获取锁类似排队打饭 先来后到
非公平锁:是指在多线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取到锁,在高并发的情况下,有可能造成优先级反转或者饥饿现象

并发包ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或者非公平锁 默认是非公平锁 。Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁,非公平锁的优点在于吞吐量比公平锁大。对于synchronized而言也是一种非公平锁。

  • 可重入锁(又名递归锁)

ReentrantLock/synchronized就是一个典型的可重入锁,可重入锁最大的作用就是避免死锁。

package cn.atguigu.interview.study.thread;
class Phone{
    public synchronized void sendSms() throws Exception{
        System.out.println(Thread.currentThread().getName()+"\tsendSms");
        sendEmail();
    }
    public synchronized void sendEmail() throws Exception{
        System.out.println(Thread.currentThread().getName()+"\tsendEmail");
    }

}
/**
 * Description:
 *  可重入锁(也叫做递归锁)
 *  指的是同一线程外层函数获得锁后,内层递归函数仍然能获取该锁的代码
 *  在同一线程外外层方法获取锁的时候,在进入内层方法会自动获取锁
 *
 *  也就是说,线程可以进入任何一个它已经标记的锁所同步的代码块
 *
 **/
public class ReenterLockDemo {
    /**
     * t1 sendSms
     * t1 sendEmail
     * t2 sendSms
     * t2 sendEmail
     * @param args
     */
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            try {
                phone.sendSms();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"t1").start();
        new Thread(()->{
            try {
                phone.sendSms();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"t2").start();
    }
}
package cn.atguigu.interview.study.thread;

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

class Phone implements Runnable {
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        get();
    }

    private void get() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\tget");
            set();
        } finally {
            lock.unlock();
        }
    }

    private void set() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\tset");
        } finally {
            lock.unlock();
        }
    }
}

/**
 * Description:
 *  可重入锁(也叫做递归锁)
 *  指的是同一线程外层函数获得锁后,内层递归函数仍然能获取该锁的代码
 *  在同一线程外外层方法获取锁的时候,在进入内层方法会自动获取锁
 *
 *  也就是说,线程可以进入任何一个它已经标记的锁所同步的代码块
 *
 **/
public class ReenterLockDemo {
    /**
     * Thread-0 get
     * Thread-0 set
     * Thread-1 get
     * Thread-1 set
     *
     * @param args
     */
    public static void main(String[] args) {
        Phone phone = new Phone();
        Thread t3 = new Thread(phone);
        Thread t4 = new Thread(phone);
        t3.start();
        t4.start();

    }
}
 
 
  • 自旋锁(SpinLock)

/**
 * @author Shawn
 * @date 2020/6/20 10:28
 * @title Function
 */
public class SpinLockDemo {
    AtomicReference<Thread> ar = new AtomicReference<>();
    AtomicInteger ai = new AtomicInteger(1);

    public void myLock() {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + "\t come in ...");
        while (!ar.compareAndSet(null, thread)) {
            System.out.println(thread.getName() + "\t" + ai.getAndIncrement());
        }
    }

    public void myUnlock() {
        Thread thread = Thread.currentThread();
        ar.compareAndSet(thread, null);
        System.out.println(thread.getName() + "\t come out ...");
    }

    public static void main(String[] args) {
        SpinLockDemo spinLockDemo = new SpinLockDemo();

        new Thread(() -> {
            spinLockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.myUnlock();
        }, "AAA").start();

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

        new Thread(() -> {
            spinLockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.myUnlock();
        }, "BBB").start();

    }
}
  • 独占锁(写)/共享锁(读)/互斥锁

/**
 * 资源类
 */
class MyCaChe {
    /**
     * 保证可见性
     */
    private volatile Map<String, Object> map = new HashMap<>();
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

    /**
     * 写
     *
     * @param key
     * @param value
     */
    public void put(String key, Object value) {
        reentrantReadWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t正在写入" + key);
            //模拟网络延时
            try {
                TimeUnit.MICROSECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t正在完成");
        } finally {
            reentrantReadWriteLock.writeLock().unlock();
        }
    }

    /**
     * 读
     *
     * @param key
     */
    public void get(String key) {
        reentrantReadWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t正在读取");
            //模拟网络延时
            try {
                TimeUnit.MICROSECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t正在完成" + result);
        } finally {
            reentrantReadWriteLock.readLock().unlock();
        }
    }

    public void clearCaChe() {
        map.clear();
    }

}

/**
 * Description:
 * 多个线程同时操作 一个资源类没有任何问题 所以为了满足并发量
 * 读取共享资源应该可以同时进行
 * 但是
 * 如果有一个线程想去写共享资源来  就不应该有其他线程可以对资源进行读或写
 * <p>
 * 小总结:
 * 读 读能共存
 * 读 写不能共存
 * 写 写不能共存
 * 写操作 原子+独占 整个过程必须是一个完成的统一整体 中间不允许被分割 被打断
 *
 **/
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCaChe myCaChe = new MyCaChe();
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myCaChe.put(temp + "", temp);
            }, String.valueOf(i)).start();
        }
        for (int i = 1; i <= 5; i++) {
            int finalI = i;
            new Thread(() -> {
                myCaChe.get(finalI + "");
            }, String.valueOf(i)).start();
        }
    }
}
 

1.6 CountDownLatch/CyclicBarrier/Semaphore使用过吗?

  • CountDownLatch:让一些线程阻塞直到另外一些完成后才被唤醒。

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

public class CountDownLatchDemo {
    public static void main(String[] args) throws Exception {
        closeDoor();
    }

   /**
     * 关门案例
     * @throws InterruptedException
     */
    private static void closeDoor() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t" + "上完自习");
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t班长锁门离开教室");
    }

  
} 
/**
 * @author Shawn
 * @date 2020/6/20 12:54
 * @title Function
 */
public enum CountryEnum {
    ONE(1, "齐"),
    TWO(2, "楚"),
    THREE(3, "燕"),
    FOUR(4, "赵"),
    FIVE(5, "魏"),
    SIX(6, "韩");

    @Getter
    private Integer retCode;
    @Getter
    private String retMessage;

    CountryEnum(Integer retCode, String retMessage) {
        this.retCode = retCode;
        this.retMessage = retMessage;
    }

    public static CountryEnum forEachCountryEnum(int index) {
        CountryEnum[] values = CountryEnum.values();
        for (CountryEnum value : values) {
            if (index == value.getRetCode()) return value;
        }
        return null;
    }
}


/**
 * @author Shawn
 * @date 2020/6/20 12:27
 * @title 让一些线程阻塞,直到另一些线程完成一系列操作后才被唤醒:
 * CountDownLatch主要有两个方法,当一个或多个线程调用await()方法时,调用线程会被阻塞。
 * 其他线程调用countDown()方法会将计数器减 1,调用countDown()方法的线程不会阻塞。
 * 当计数器的值变为 0,因调用await()方法被阻塞的线程会被唤醒,继续执行。
 */
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        closeDoor();
    }

    private static void closeDoor() throws InterruptedException {
        CountDownLatch count = new CountDownLatch(6);

        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t 国,被灭");
                count.countDown();
            }, CountryEnum.forEachCountryEnum(i).getRetMessage()).start();
        }

        count.await();
        System.out.println(Thread.currentThread().getName() + "\t 秦,统华夏");
    }
}
  • CyclicBarrier
/**
 * @author Shawn
 * @date 2020/6/20 13:26
 * @title Function 集齐7颗龙珠就能召唤神龙
 * CyclicBarrier的字面意思是可循环(Cyclic) 使用的屏障(barrier).
 * 它要做的事情是,让一组线程到达一个屏障(也可以叫做同步点)时被阻塞,
 * 直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,
 * 线程进入屏障通过CyclicBarrier的await()方法.
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("***** 召唤神龙 *****");
        });

        for (int i = 1; i <= 7; i++) {
            final int tempInt = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t 收集到第:" + tempInt + "龙珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}
  • Semaphore: 信号量的主要用于两个目的:一个是用于多个共享资源的相互排斥使用;另一个用于并发资源数的控制。
/**
 * @author Shawn
 * @date 2020/6/20 13:36
 * @title Function 抢车位,多个线程抢夺多个资源。
 * 信号量的主要用于两个目的:
 *      一个是用于多个共享资源的相互排斥使用
 *      另一个用于并发资源数的控制
 */
public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3); // 模拟3个停车位
        // 模拟6部车
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "\t 抢到车位");
                    // 模拟停车
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "\t 离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
    }
}

1.7 阻塞队列知道吗?

  • 什么是阻塞队列?

阻塞队列,顾名思义,首先它是一个队列,而一个阻塞队列在数据结构中所起的作用大致如图所示:
 

线程1往阻塞队列中添加元素,二线程2从队列中移除元素
线程1往阻塞队列中添加元素,二线程2从队列中移除元素

当阻塞队列是空时,从队列中获取元素的操作将会被阻塞.
当阻塞队列是满时,往队列中添加元素的操作将会被阻塞.

同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他线程从队列中移除一个或者多个元素或者全清空队列后使队列重新变得空闲起来并后续新增.

  • 为什么用?有什么好处?

在多线程领域:所谓阻塞,在某些情况下会挂起(wait())线程(即线程阻塞),一旦条件满足,被挂起的线程又会被自动唤醒(notifyAll())
 
为什么需要使用BlockingQueue
 
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,即不需要wait()和notify(),因为BlockingQueue都一手给你包办好了。在concurrent包 发布以前,在多线程环境下,我们每个程序员都必须自己去控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。

  • BlockingQueue的核心方法

抛出异常add() 当阻塞队列满时,再往队列里面add插入元素会抛IllegalStateException: Queue full
remove() 当阻塞队列空时,再往队列Remove元素时候回抛出NoSuchElementException
特殊值offer() 插入方法,成功返回true 失败返回false
poll() 移除方法,成功返回元素,队列里面没有就返回null
一直阻塞put() 当阻塞队列满时,生产者继续往队列里面put元素,队列会一直阻塞直到put数据or响应中断退出
take()当阻塞队列空时,消费者试图从队列take元素,队列会一直阻塞消费者线程直到队列可用.
超时退出当阻塞队列满时,队列会阻塞生产者线程一定时间,超过后限时后生产者线程就会退出

/**
 * @author Shawn
 * @date 2020/6/20 15:48
 * @title Function - 阻塞队列
 * <p>
 * 抛出异常
 * 当阻塞队列满时,再往队列里面add插入元素会抛IllegalStateException: Queue full
 * 当阻塞队列空时,再往队列Remove元素时候回抛出NoSuchElementException
 * 特殊值
 * 插入方法,成功返回true 失败返回false
 * 移除方法,成功返回元素,队列里面没有就返回null
 * 一直阻塞
 * 当阻塞队列满时,生产者继续往队列里面put元素,队列会一直阻塞直到put数据or响应中断退出
 * 当阻塞队列空时,消费者试图从队列take元素,队列会一直阻塞消费者线程直到队列可用.
 * 超时退出
 * 当阻塞队列满时,队列会阻塞生产者线程一定时间,超过后限时后生产者线程就会退出
 */
public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        /*System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));

        System.out.println(blockingQueue.element());

        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());*/

        /*System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        System.out.println(blockingQueue.offer("d"));

        System.out.println(blockingQueue.element());

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());*/

        /*blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        System.out.println("=============");
        blockingQueue.put("d");

        System.out.println(blockingQueue.element());

        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());*/

        System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.offer("b", 2L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.offer("c", 2L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.offer("d", 2L, TimeUnit.SECONDS));

        System.out.println(blockingQueue.element());

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
    }
}
  • 架构梳理+种类分析

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

LinkedBlockingDeque: 由链表结构组成的有界(但大小默认值Integer>MAX_VALUE)阻塞队列.

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

DelayQueue: 使用优先级队列实现的延迟无界阻塞队列.

SynchronousQueue:不存储元素的阻塞队列,也即是单个元素的队列.

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

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

/**
 * @author Shawn
 * @date 2020/6/20 16:04
 * @title Function - SynchronousQueue没有容量
 * <p>
 * 与其他 BlockingQueue不同, SynchronousQueue是一个不存储元素的BlockingQueue
 * 每个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然.
 */
public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();

        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "\t put 1.");
                blockingQueue.put("1");
                System.out.println(Thread.currentThread().getName() + "\t put 2.");
                blockingQueue.put("2");
                System.out.println(Thread.currentThread().getName() + "\t put 3.");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "AAA").start();

        new Thread(() -> {
            try {
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "BBB").start();
    }
}
  • 阻塞队列应用在哪里?

生产者消费者模式,线程池,消息中间件

企业级高并发开发的秘诀:高内聚低耦合的前提下,线程操纵资源类,判断+干活+唤醒通知,严防多线程并发状态下的虚假唤醒。

/**
 * @author Shawn
 * @date 2020/6/20 16:19
 * @title Function
 * 题目:一个初始值为 0 的变量,两个线程对其进行替换操作,一个加 1,一个减 1,来 5 轮
 * 步骤:
 * 1. 线程   操作  资源类
 * 2. 判断   干活  通知
 * 3. 防止虚假唤醒机制,while
 */

class ShareData {
    private int number = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void increment() throws Exception {

        lock.lock();
        try {
            // 1. 判断(防止虚假唤醒机制)
            while (number != 0) {
                // 等待,不能生产
                condition.await();
            }
            // 2. 干活
            number++;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            // 3. 通知唤醒
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void decrement() throws Exception {

        lock.lock();
        try {
            // 1. 判断
            while (number == 0) {
                // 等待,不能生产
                condition.await();
            }
            // 2. 干活
            number--;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            // 3. 通知唤醒
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

}

public class ProdConsTraditionalDemo {
    public static void main(String[] args) {
        ShareData shareData = new ShareData();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    shareData.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "AAA").start();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    shareData.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "BBB").start();
    }
}
/**
 * @author Shawn
 * @date 2020/6/20 17:35
 * @title Function
 * <p>
 * volatile/CAS/AtomicInteger/BlockQueue/线程交互/原子引用
 */
class ShareResource {
    private volatile boolean FLAG = true; // 默认开启,进行生产/消费
    private AtomicInteger atomicInteger = new AtomicInteger();

    BlockingQueue<String> blockingQueue = null;

    public ShareResource(BlockingQueue<String> blockingQueue) {
        this.blockingQueue = blockingQueue;
        System.out.println(blockingQueue.getClass().getName()); // 通过反射拿到类名和包名
    }

    public void myProd() throws Exception {
        String data = null;
        boolean retValue;
        while (FLAG) {
            data = atomicInteger.incrementAndGet() + "";
            retValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
            if (retValue) {
                System.out.println(Thread.currentThread().getName() + "\t 阻塞队列插入 " + data + " 成功");
            } else {
                System.out.println(Thread.currentThread().getName() + "\t 阻塞队列插入 " + data + " 失败");
            }
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "\t 停止生产,FLAG = FALSE ");
    }

    public void myCons() throws Exception {
        String result = null;
        boolean retValue;
        while (FLAG) {
            result = blockingQueue.poll(2L, TimeUnit.SECONDS);
            if (result == null || result.equalsIgnoreCase("")) {
                FLAG = false;
                System.out.println(Thread.currentThread().getName() + "\t 超过2s未取到值,消费退出 ");
                System.out.println("\n\r");
                return;
            }
            System.out.println(Thread.currentThread().getName() + "\t 消费队列消费 " + result + " 成功");
        }
        System.out.println(Thread.currentThread().getName() + "\t 停止生产,FLAG = FALSE ");
    }

    public void stop() throws Exception {
        this.FLAG = false;
    }
}

public class ProdConsBlockingQueueDemo {
    public static void main(String[] args) throws Exception {
        ShareResource resource = new ShareResource(new ArrayBlockingQueue<>(10));

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t 生产线程启动");
            try {
                resource.myProd();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "Prod").start();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t 消费线程启动");
            try {
                resource.myCons();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "Cons").start();

        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("\t 5s时间已到,活动结束");
        resource.stop();
    }
}

==================output===============
java.util.concurrent.ArrayBlockingQueue
Prod	 生产线程启动
Cons	 消费线程启动
Prod	 阻塞队列插入 1 成功
Cons	 消费队列消费 1 成功
Prod	 阻塞队列插入 2 成功
Cons	 消费队列消费 2 成功
Prod	 阻塞队列插入 3 成功
Cons	 消费队列消费 3 成功
Prod	 阻塞队列插入 4 成功
Cons	 消费队列消费 4 成功
Cons	 消费队列消费 5 成功
Prod	 阻塞队列插入 5 成功
	 5s时间已到,活动结束
Prod	 停止生产,FLAG = FALSE 
Cons	 超过2s未取到值,消费退出 
  • synchronized 和 lock 的区别?用新的lock有什么好处,请举例说明

1.原始构成
Synchronized是关键字属于JVM层面
monitorenter(底层是通过monitor对象来完成,其实wait和notify等方法也依赖于monitor对象完成,只有在同步块或方法中,才能调用wait和notify)
monitorexist

lock是具体类(java.lang.concurrent.Lock.lock)是api层面的类

2.使用方法
Synchronized 不需要用户手动去释放锁,当Synchronized代码执行完后,系统会自动让线程释放对锁的占用

reentranLock需要用户手动去释放锁,若没有主动释放锁,则会可能导致出现死锁。需要lock()和unlock()方法来配合try finally语句块来完成

3.等待是否可中断
synchronized不可中断,除非抛出异常或者正常执行完成。
reentranLock可中断,1.设置超时方法tryLock(long time,timeUnit unit);
lockInterruptibly()代码块中,调用interrupt()方法可中断

4.加锁是否公平
synchronized非公平锁
ReentranLock两者都可以,默认非公平锁,构造方法可传入boolean值,true为公平锁,false为非公平锁。

5,锁绑定多个条件condition
synchronized没有
ReentranLock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不像synchronized,要么随机唤醒一个线程要么唤醒全部线程。

/**
 * @author Shawn
 * @date 2020/6/20 17:00
 * @title Function
 * 题目:多线程之间按顺序调用, 实现 A->B->C 三个线程启动, 要求如下:
 * AA打印5次, BB打印10次, CC打印15次
 * 紧接着
 * AA打印5次, BB打印10次, CC打印15次
 * ......
 * 来10轮
 */

class ShareResources {
    private int number = 1;  // A:1   B:2   C:3
    private Lock lock = new ReentrantLock();
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    public void print5() {
        lock.lock();
        try {
            // 1、判断
            while (number != 1) {
                c1.await();
            }

            // 2、干活
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }

            // 3、通知
            number = 2;
            c2.signal();

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

    public void print10() {
        lock.lock();
        try {
            // 1、判断
            while (number != 2) {
                c2.await();
            }

            // 2、干活
            for (int i = 1; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }

            // 3、通知
            number = 3;
            c3.signal();

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

    public void print15() {
        lock.lock();
        try {
            // 1、判断
            while (number != 3) {
                c3.await();
            }

            // 2、干活
            for (int i = 1; i <= 15; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }

            // 3、通知
            number = 1;
            c1.signal();

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

}

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

        ShareResources shareResources = new ShareResources();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                shareResources.print5();
            }
        }, "AAA").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                shareResources.print10();
            }
        }, "BBB").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                shareResources.print15();
            }
        }, "CCC").start();
    }
}

1.8 线程池用过吗?ThreadPoolExecutor谈谈你的理解?

  • callable接口
/**
 * @author Shawn
 * @date 2020/6/20 18:26
 * @title Function
 * Runnable 和 Callable 接口的区别
 * 1. 实现Callable接口的任务线程能返回执行结果,而实现Runnable接口的任务线程不能返回执行结果
 * 注意点:Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞线程直到获取“未来任务”的结果,当不调用此方法时,主线程不会阻塞
 * <p>
 * 2. Callable接口实现类中run()方法允许将异常向上抛出,也可以直接在内部处理(try...catch); 而Runnable接口实现类中run()方法的异常必须在内部处理掉,不能向上抛出
 * <p>
 * 3. 方法不一样,一个call(), 一个run()
 */
class MyThread implements Runnable {

    @Override
    public void run() {

    }
}

class MyThread2 implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("==============callable thread===============");
        return 1024;
    }
}

public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<>(new MyThread2());
        Thread thread = new Thread(futureTask,"AAA");
        thread.start();

        int result01=100;
        while(!futureTask.isDone()){

        }
        int result02=futureTask.get(); // 要求获得callable线程的计算结果,如果没有计算完成会导致阻塞,直到计算完成(建议放在最后,其他线程任务之后)
        System.out.println("Result: " + (result01+result02));
    }
}
  • 为什么使用线程池,优势是什么?

线程池做的工作主要是控制运行的线程的数量,处理过程中将任务加入队列,然后在线程创建后启动这些任务,如果线程超过了最大数量,超出的数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行.
 
他的主要特点为:线程复用,控制最大并发数,管理线程.
 
第一: 降低资源消耗.通过重复利用自己创建的线程降低线程创建和销毁造成的消耗.
第二: 提高响应速度.当任务到达时,任务可以不需要等到线程和粗昂就爱你就能立即执行.
第三: 提高线程的可管理性.线程是稀缺资源,如果无限的创阿金,不仅会消耗资源,还会较低系统的稳定性,使用线程池可以进行统一分配,调优和监控.

  • 线程池如何使用?

Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类.

  •  创建线程池的方法有哪些?

Executors.newFixedThreadPool(int): 执行一个长期的任务,性能好很多

主要特点如下:
1.创建一个定长线程池,可控制线程的最大并发数,超出的线程会在队列中等待.
2.newFixedThreadPool创建的线程池corePoolSize和MaxmumPoolSize是 相等的,它使用的的LinkedBlockingQueue

Executors.newSingleThreadExecutor():用唯一的工作线程来执行任务,保证所有任务都按照指定顺序执行.

主要特点如下:
1.创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务都按照指定顺序执行.
2.newSingleThreadExecutor将corePoolSize和MaxmumPoolSize都设置为1,它使用的的LinkedBlockingQueue

 

 Executors.newCachedThreadPool():执行很多短期异步的小程序或者负载较轻的服务器

主要特点如下:
1.创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则创建新线程.
2.newCachedThreadPool将corePoolSize设置为0,MaxmumPoolSize设置为Integer.MAX_VALUE,它使用的是SynchronousQueue,也就是说来了任务就创建线程运行,如果线程空闲超过60秒,就销毁线程

/**
 * @author Shawn
 * @date 2020/6/21 16:49
 * @title Function
 * 创建线程的4中方法:
 *      1. 继承Thread
 *      2. 实现Runnable
 *      3. 实现Callable
 *      4. 线程池创建
 */
public class ThreadPoolDemo {
    public static void main(String[] args) {

         ExecutorService threadPool = Executors.newFixedThreadPool(5);// 一池 5 个处理线程
        // ExecutorService threadPool = Executors.newSingleThreadExecutor();// 一池 1 个处理线程
        // ExecutorService threadPool = Executors.newCachedThreadPool();// 一池 N 个处理线程


        try {
            // 模拟 10 个用户办理业务,每个用户是一个来自外部的请求线程
            for (int i = 1; i <= 10; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t 办理业务");
                });
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }

    }
}

 Executors.newScheduledThreadPool():是唯一一个有延迟执行和周期重复执行的线程池。它的核心线程池固定,非核心线程的数量没有限制,但是闲置时会立即会被回收。

 Executors.newWorkStealingThreadPool():java8新出,由于能够合理的使用CPU进行对任务操作(并行操作),所以适合使用在很耗时的任务。最明显的用意就是它是一个并行的线程池,参数中传入的是一个线程并发的数量,这里和之前就有很明显的区别,前面4种线程池都有核心线程数、最大线程数等等,而这就使用了一个并发线程数解决问题。从介绍中,还说明这个线程池不会保证任务的顺序执行,也就是 WorkStealing 的意思,抢占式的工作。

  • 线程池几个重要参数介绍?

1.corePoolSize:线程池中的常驻核心线程数

  • 在创建了线程池后,当有请求任务来之后,就会安排核心池中的线程去执行请求任务,近似理解为今日当值线程
  • 当线程池中的线程数目达到corePoolSize后,就会把到达的任务放入到缓存队列当中.

2.maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值大于等于1

3.keepAliveTime:多余的空闲线程存活时间,当空间时间达到keepAliveTime值时,多余的线程会被销毁直到只剩下corePoolSize个线程为止

  • 默认情况下:只有当线程池中的线程数大于corePoolSize时keepAliveTime才会起作用,知道线程中的线程数不大于corepoolSIze,

4.unit:keepAliveTime的单位

5.workQueue:任务队列,被提交但尚未被执行的任务.

6.threadFactory:表示生成线程池中工作线程的线程工厂,用户创建新线程,一般用默认即可

7.handler:拒绝策略,表示当线程队列满了并且工作线程大于等于线程池的最大显示 数(maxnumPoolSize)时如何来拒绝.

  • 说说线程池的底层工作原理?

 

1.9 线程池用过吗?生产上你是如何设置合理参数

  • 线程池的拒绝策略请你谈谈

等待队列也已经排满了,再也塞不下新的任务了,同时,线程池的max也到达了,无法接续为新任务服务,这时我们需要拒绝策略机制合理的处理这个问题。

  • JDK内置的拒绝策略(以下内置策略均实现了RejectExecutionHandler接口)

AbortPolicy(默认):直接抛出RejectedException异常阻止系统正常运行

CallerRunPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是

DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交

DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常.如果允许任务丢失,这是最好的拒绝策略

  • 你在工作中单一的/固定数的/可变你的三种创建线程池的方法,你用哪个多?超级大坑

答案是一个都不用,我们生产上只能使用自定义的,Executors中JDK给你提供了为什么不用?

参考阿里巴巴java开发手册
 
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。 
 
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

  • 你在工作中是如何创建线程池的,是否自定义过线程池使用
public class MyThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                1L,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<Runnable>(3),
                Executors.defaultThreadFactory(),
                //默认抛出异常
                //new ThreadPoolExecutor.AbortPolicy()
                //回退调用者
                //new ThreadPoolExecutor.CallerRunsPolicy()
                //处理不来的不处理
                //new ThreadPoolExecutor.DiscardOldestPolicy()
                new ThreadPoolExecutor.DiscardPolicy()
        );
        //模拟10个用户来办理业务 没有用户就是来自外部的请求线程.
        try {
            for (int i = 1; i <= 10; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t 办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
        //threadPoolInit();
    }

    private static void threadPoolInit() {
        /**
         * 一池5个处理线程
         */
        //ExecutorService threadPool= Executors.newFixedThreadPool(5);
        /**
         * 一池一线程
         */
        //ExecutorService threadPool= Executors.newSingleThreadExecutor();
        /**
         * 一池N线程
         */
        ExecutorService threadPool = Executors.newCachedThreadPool();
        //模拟10个用户来办理业务 没有用户就是来自外部的请求线程.
        try {
            for (int i = 1; i <= 20; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t 办理业务");
                });
                try {
                    TimeUnit.MICROSECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}
  • 合理配置线程池你是如何考虑的?

CPU密集型

System.out.println(Runtime.getRuntime().availableProcessors());查看CPU核数

 IO密集型

 

 1.10 死锁编码及定位分析

  • 产生死锁的4个必要条件:
  1. 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  2. 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  4. 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。

    如何避免线程死锁

  1. 互斥条件: 这个破坏不了, 因为临界资源就是需要互斥访问, 一旦没有互斥资源就会线程不安全.
  2. 破坏请求与保持条件: 一次性申请所有资源.
  3. 破坏不剥夺条件: 占用部分资源的线程进一步申请其他资源时, 如果申请不到, 可以主动释放它占有的资源.
  4. 破坏循环等待条件: 靠按序申请资源来预防. 按某一顺序申请资源, 释放资源则反序释放.
避免死锁的几个常见方法。
避免一个线程同时获取多个锁。
避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
尝试使用定时锁,使用 lock.tryLock timeout )来替代使用内部锁机制。
对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
class HoldThread implements Runnable {

    private String lockA;
    private String lockB;

    public HoldThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized (lockA) {
            System.out.println(Thread.currentThread().getName() + "\t 自己持有锁" + lockA + "尝试获得" + lockB);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB) {
                System.out.println(Thread.currentThread().getName() + "\t 自己持有锁" + lockB + "尝试获得" + lockA);
            }
        }
    }
}

/**
 * Description:
 * 死锁是指两个或者以上的进程在执行过程中,
 * 因争夺资源而造成的一种相互等待的现象,
 * 若无外力干涉那他们都将无法推进下去
 *
 **/
public class DeadLockDemo {
    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";
        new Thread(new HoldThread(lockA, lockB), "threadAAA").start();
        new Thread(new HoldThread(lockB, lockA), "threadBBB").start();
    }
}
 
  • 怎么进行定位?-- 2步(1.jps -l 获取进程id; 2.jstack 进程id 获取栈信息)

jps命令定位进程编号

 jstack找到死锁查看

### Java JUC 并发多线程基础知识与示例 #### 1. Java JUC 概述 Java 提供了一个强大的并发编程工具包 `java.util.concurrent`(简称 JUC),它包含了多种用于实现高效、可靠并发程序的组件。这些组件主要包括线程池、并发集合、同步器以及原子变量等[^1]。 #### 2. 线程池的应用 线程池是一种管理线程生命周期的有效机制,可以减少频繁创建和销毁线程带来的开销。下面是一个简单的线程池使用示例: ```java package com.example.threadpool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(10); // 创建固定大小为10的线程池 Runnable task = () -> System.out.println(Thread.currentThread().getName() + " is running."); for (int i = 0; i < 100; i++) { executor.submit(task); } executor.shutdown(); // 停止接收新任务并等待现有任务完成 } } ``` 上述代码展示了如何利用线程池执行大量任务,而无需手动管理每个线程的生命周期[^2]。 #### 3. 同步控制:synchronized 和 Lock 接口 为了防止多个线程同时访问共享资源而导致数据不一致,JUC 提供了两种主要的同步方式——基于关键字 `synchronized` 的内置锁和支持更复杂场景的 `Lock` 接口。 ##### 使用 synchronized 实现同步 `synchronized` 是一种简单易用的方式,适用于大多数基本需求。以下是其典型用法的一个例子: ```java class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } ``` 这里定义了一个计数器类,在方法上加上 `synchronized` 可以确保每次只有一个线程能修改或读取 `count` 属性[^4]。 ##### 使用 ReentrantLock 进一步增强灵活性 如果需要更多高级特性比如尝试锁定而不阻塞或者设置超时时间,则可以选择 `ReentrantLock` 类型的对象作为显式的锁替代默认隐式锁。 ```java import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class AdvancedCounter { private final Lock lock = new ReentrantLock(); private int value = 0; public void add(int delta) { lock.lock(); // 获取锁 try { value += delta; } finally { lock.unlock(); // 确保释放锁 } } public int getValue() { lock.lock(); try { return value; } finally { lock.unlock(); } } } ``` 这段代码展示的是通过显示声明锁来进行更加精细的操作控制[^5]。 #### 4. 并发容器简介 除了传统的集合框架外,JUC 还引入了一些专门设计用来支持高并发环境下的特殊版本的数据结构,例如 `ConcurrentHashMap`, `CopyOnWriteArrayList` 等。它们能够在保持较高性能的同时维持内部一致性状态更新操作的安全性。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值