Java并发编程 (学习)

本文深入探讨Java并发编程的关键概念,包括volatile关键字的作用与特性、Java内存模型(JMM)的运作机制、线程同步规定及三大特性。文章还详细分析了volatile在单例模式中的应用,CAS操作的底层原理与UnSafe类的关系,以及原子类AtomicInteger的ABA问题解决方案。此外,文中还对比了Synchronized和Lock的不同之处,讲解了线程池的使用与配置,以及阻塞队列和生产者消费者模式的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java 并发编程

请谈谈你对 volatile的理解

volatile是java虚拟机提供的轻量级的同步机制

  • 保证可见性

  • 禁止指令重排

  • 不保证原子性

JMM(JAVA 内存模型) 请谈谈对JMM的理解

基本概念

  • JMM是一种抽象的概念,并不真实存在,它描述的是一种规范或规则,通过这组规范定义了程序中的访问方式。

  • JMM 同步规定

    • 线程解锁前,必须把共享变量的值刷回到主内存

    • 线程加锁前,必须把主内存的最新值拷贝到自己的工作内存中

    • 加锁和解锁是同一把锁

  • 由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存,工作内存是每个线程的私有区域,java内存模型规定所有变量都存储在主内存中,主内存是共享区域,所有线程都可以访问,但线程对变量的操作(读写改等),都必须在自己的工作内存中进行。

  • 首先将变量从主内存中拷贝到自己的工作内存然后对变量进行操作,操作完成后将变量写回到主内存中,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量拷贝副本,因为工作内存是每个线程的私有区域,因此,不同的线程间无法访问对方的工作内存,线程间的通信(传值),必须通过主内存来完成。

  • 内存模型图

  •  

三大特性

  • 可见性

    package com.haha;
    ​
    import java.util.concurrent.TimeUnit;
    ​
    class Data {
        //int a = 0;
        /*
         * Thread-0 hello... 
         * Thread-0 ++后。。。
         * 不加volatile的结果,线程一直阻塞。
         */
         volatile int a = 0;
    ​
        void addOne() {
            this.a += 1;
        }
    }
    ​
    public class VolatileDemo {
    ​
        public static void main(String[] args) {
    ​
            Data data = new Data();
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t hello...");
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                data.addOne();
                System.out.println(Thread.currentThread().getName() + "\t ++后。。。");
            }).start();
            while (data.a == 0) {
                // looping
            }
    ​
            System.out.println(Thread.currentThread().getName() + " job is done...");
        }
    ​
    }
    ​
    /*
     * 加volatile的结果:
     * Thread-0 hello...
     *  Thread-0 ++后。。。
     *   main job is done...
     */
    ​

    如果不加volatile关键字,则主线程会进入死循环,加valatile则主线程可以结束,说明加了volatile关键字的变量,当有一个线程修改了值,会马上被另一个线程感知到,当前值作废,重新从主内存中获取值。对其他线程可见,这就是可见性。

  • 原子性

    package com.haha;

class Data1 { volatile int a = 0;

void addOne() {
    this.a += 1;
}

}

public class VolatileDemo2 {

public static void main(String[] args) {
    test();
}
​
private static void test() {
    Data1 data = new Data1();
    for (int i = 0; i < 20; i++) {
​
        new Thread(() -> {
            for (int j = 0; j < 1000; j++) {
                data.addOne();
            }
        }).start();
    }
    // 默认有main线程和gc线程
    while (Thread.activeCount() > 2) {
        Thread.yield();
    }
    System.out.println(data.a);
}

}

```
package com.haha;
​
import java.util.concurrent.atomic.AtomicInteger;
//解决volatile原子性问题
// AtomicInteger 原子类操作 JUC
class Data1 {
    volatile int a = 0;
    AtomicInteger atomicInteger = new AtomicInteger();
​
    void addAtomic() {
        atomicInteger.getAndIncrement();
    }
​
    void addPlus() {
        a++;
    }
​
    void addOne() {
        this.a += 1;
    }
​
}
​
public class VolatileDemo2 {
​
    public static void main(String[] args) {
        test();
    }
​
    private static void test() {
        Data1 data = new Data1();
        for (int i = 0; i < 20; i++) {
​
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    data.addOne();
                    data.addAtomic();
                }
            }).start();
        }
        // 默认有main线程和gc线程
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(data.a);
        System.out.println(data.atomicInteger);
    }
​
}
​

 

期望结果是20000,但结果达不到20000;

  • 有序性

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

      • 编译器优化重排

      • 指令并行重排

      • 内存系统的重排

    • 单线程环境里确保程序最终执行的结果和代码执行结果一致

    • 处理器在进行重排序时必须考虑指令之间的数据依赖性

    • 多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证用的变量能否一致性是无法确定的,结果无法预测。

    • 代码Demo:

      package com.haha;

    public class ReSortSeqDemo { int a = 0; boolean flag = false;

    public void method01() {
        a = 1; // flag =true;
    ​
        flag = true;
    }
    ​
    public void method02() {
        if (flag) {
            a = a + 3;
            System.out.println("a = " + a);
        }
    }

     

    } //解决办法:用volatile来修饰变量,实现禁止指令重排,从而避免多线程环境下程序出现乱序执行的现象

禁止指令重排

volatile实现禁止指令重排序的优化,从而避免了多线程环境中程序出现乱序的现象

先了解一个概念,内存屏障 (Memory Barrier) 又称内存栅栏,是一个CPU指令,它的作用有两个:

  • 保证特定操作的执行顺序

  • 保证某些变量的内存可见性(利用该特性实现 volatile的内存可见性)

由于编译器和处理器都能执行指令重排优化,如果指令间插入一条Memory Barrier 则会告诉编译器和CPU,不管什么指令都不能使这条Memory Barrier指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后执行重排序优化。内存屏障另一个作用是强制刷出各种CPU缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。

下面是保守策略下,volatile写插入内存屏障后生成的指令序列示意图:

 

下面是保守策略下,volatile读插入内存屏障后生成的指令序列示意图

 

线程安全性保证

  • 工作内存与主内存同步延迟现象导致可见性问题

    • 可以使用syncronzied或volatile关键字解决,它们可以使用一个线程修改后的变量立即对其他线程可见

  • 对于指令重排导致可见性问题和有序性问题

    • 可以利用volatile关键字解决,因为volatile的另一个作用就是禁止指令重排(有序性)优化

 

你在哪些地方用到过 volatile

单例

  • 多线程环境下可能存在的安全问题

package com.haha;
​
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
public class SingletonDemo01 {
    private static SingletonDemo01 instance = null;
​
    private SingletonDemo01() {
        System.out.println(Thread.currentThread().getName() + "\t 构造方法");
    }
​
    // +synchronized
    public static  SingletonDemo01 getInstance() {
        if (instance == null) {
            instance = new SingletonDemo01();
        }
        return instance;
    }
​
    public static void main(String[] args) {
​
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            executorService.execute(() -> SingletonDemo01.getInstance());
        }
        executorService.shutdown();
​
    }
​
}
​
/*
结果:
pool-1-thread-2  构造方法
pool-1-thread-8  构造方法
pool-1-thread-9  构造方法
pool-1-thread-6  构造方法
pool-1-thread-4  构造方法
pool-1-thread-3  构造方法
pool-1-thread-5  构造方法
pool-1-thread-1  构造方法
​
 */
​
​

单例模式下,构造器中的内容会多次输出

  • 双重锁单例

package com.haha;
​
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
public class SingletonDemo2 {
    private static SingletonDemo2 instance = null;
​
    private SingletonDemo2() {
        System.out.println(Thread.currentThread().getName() + "\t 构造方法");
    }
​
    // +synchronized代码块 前面+判断 = 双端检锁机制
    public static SingletonDemo2 getInstance() {
        if (instance == null) {
            synchronized (SingletonDemo2.class) {
​
                if (instance == null) {
​
                    instance = new SingletonDemo2();
                }
            }
        }
        return instance;
    }
​
    public static void main(String[] args) {
​
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            executorService.execute(() -> SingletonDemo2.getInstance());
        }
        executorService.shutdown();
​
    }
​
}
​
// pool-1-thread-1 构造方法
​

 

  •  

    • 如果没有加volatile 就不一定是线程安全的,原因是指令重排的存在,加入volatile可以禁止指令重排。

    • 原因是在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能还没有完成初始化。

    instance=new SingletonDemo2() 可以分三步完成:
    ​
    memory = allocate(); // 分配对象空间
    instance(memory);   // 初始化对象
    instance = memory;   //设置instance指向刚分配的内存地址,此时instance!=null
    • 步骤2 和步骤3 不存在依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种优化时允许的。

    • 发生重排

    memory = allocate();  // 1.分配对象空间
    instance = memory;    // 3.设置instance指向刚分配的内存地址,此时instance != null,但对象还没有初始化完成
    instance(memory);     // 2.初始化对象

     

    • 所以不加 volatile 返回的实例不为空,但可能是未初始化的实例

    package com.haha;
    ​
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    ​
    public class SingletonDemo3 {
        // 禁止指令重排
        private static volatile SingletonDemo3 instance = null;
    ​
        private SingletonDemo3() {
            System.out.println(Thread.currentThread().getName() + "\t 构造方法");
        }
    ​
        // +synchronized代码块 前面+判断 = 双端检锁机制 + 禁止指令重排
        public static SingletonDemo3 getInstance() {
            if (instance == null) {
                synchronized (SingletonDemo3.class) {
    ​
                    if (instance == null) {
    ​
                        instance = new SingletonDemo3();
                    }
                }
            }
            return instance;
        }
    ​
        public static void main(String[] args) {
    ​
            ExecutorService executorService = Executors.newFixedThreadPool(10);
            for (int i = 0; i < 10; i++) {
                executorService.execute(() -> SingletonDemo3.getInstance());
            }
            executorService.shutdown();
    ​
        }
    ​
    }
    ​
    // pool-1-thread-1 构造方法
    ​

     

CAS 你知道么?

compartAndSet

package com.haha;
​
import java.util.concurrent.atomic.AtomicInteger;
​
public class CASDemo {
​
    public static void main(String[] args) {
        // 获取真实值,并替换为相应的值
        AtomicInteger atomicInteger = new AtomicInteger(1020);
        boolean b1 = atomicInteger.compareAndSet(1020, 1024);
        // 期望值,替换值
        System.out.println(b1);// ture
        boolean b2 = atomicInteger.compareAndSet(1020, 1024);
        System.out.println(b2);// false
        atomicInteger.getAndIncrement();
    }
​
}
​

 

CAS 底层原理? 谈谈 UnSafe 的理解?

getAndIncrement()'

public final boolean compareAndSet(int expect,int update){
    return unsafe.compareAndSwapInt(this,valueOffset,expect,update)
}
 public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

引出一个问题:UnSafe 类是什么?

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;
​
    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
​
    static {
        try {
            //获取下面 value 的地址偏移量
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
​
    private volatile int value;
//.....
}
  • UnSafe 是CAS的核心类,由于Java方法无法直接访问底层系统,而需要通过本地方法(native)方法来访问,UnSafe类相当于一个后门,基于该类可以直接操作特定的内存数据。UnSafe类存在于 sun.misc包中(rt.jar),其内部方法操作可以向C指针一样直接操作内存,因为Java中CAS操作执行依赖于UnSafe类。

  • 变量ValueOffset,表示该变量值在内存中的偏移量,因为UnSafe就是根据内存偏移量来获取数据的。

  • 变量value 用volatile来修饰,保证了多线程的内存可见性。

CAS 是什么?

  • CAS 的全称Compare-And-Swap,它是一条CPU 并发源语。

  • 它的功能是判断内存某一位置的值是否为预期值,如果是则更改这个值,这个过程是原子的。

  • CAS 并发原体现在JAVA语言中就是 sun.misc.Unsafe 类中的各个方法。调用 UnSafe类中的CAS 方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖硬件的功能,通过它实现了原子操作。由于CAS是一种系统源语,源语属于操作系统用于范畴,是由若干条指令组成,用于完成某一个功能的过程,并且源语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条原子指令,不会造成所谓的数据不一致的问题。

  • 分析一下 getAndAddInt( )这个方法

  • CAS(CompareAndSwap)

    • 比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存的值一致为止。

  • CAS 应用

    • CAS有3个操作数,内存值V,旧的预期值A,要修改的更新值B,当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做,再次循环,直到成功为止。

// unsafe.getAndAddInt
public final int getAndAddInt(Object obj, long valueOffset, long expected, int val) {
    int temp;
    do {
        temp = this.getIntVolatile(obj, valueOffset);  // 获取快照值
    } while (!this.compareAndSwap(obj, valueOffset, temp, temp + val));  // 如果此时 temp 没有被修改,就能退出循环,否则重新获取
    return temp;
}

CAS 的缺点?

  • 循环时间长开销大

    • 如果CAS失败,就会一直尝试,如果CAS长时间一直不成功,可能给CPU带来很大的开销(比如线程数很多,每次比较都是失败,就会一直循环),所以希望是线程数比较小的场景。

  • 只能保证一个共享变量的原子操作

    • 对于多个共享变量操作时,循环CAS就无法保证原子性。

  • 引出 ABA 问题

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

  • 原子引用

package com.haha;
​
import java.util.concurrent.atomic.AtomicReference;
​
class User {
    private String name;
    private int age;
​
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
​
    @Override
    public String toString() {
        return "User [name=" + name + ", age=" + age + "]";
    }
    
}
​
public class AtomicReferenceDemo {
​
    public static void main(String[] args) {
        User hi = new User("hi", 23);
        User hello = new User("hello", 18);
        AtomicReference<User> atomicReference = new AtomicReference<>();
        atomicReference.set(hi);
        System.out.println(atomicReference.compareAndSet(hi, hello));//true
        System.out.println(atomicReference.get());//User [name=hello, age=18]
    }
​
}
​
  • ABA问题是怎么产生的

package com.haha;
​
import java.util.concurrent.atomic.AtomicReference;
​
public class ABADemo {
    private static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
​
    public static void main(String[] args) {
        new Thread(() -> {
            atomicReference.compareAndSet(100, 101);
            atomicReference.compareAndSet(101, 100);
​
        }).start();
​
        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            atomicReference.compareAndSet(100, 1020);
            System.out.println(atomicReference.get());
​
        }).start();
​
    }
​
}
​

当有一个值从A改为B 又改为A,这就是ABA问题。(首位一致,中间数据有变化)

  • 时间戳原子引用 (解决ABA方案)

package com.haha;
​
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
​
import org.apache.derby.tools.sysinfo;
​
public class ABADemo2 {
​
    private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100, 1);
​
    public static void main(String[] args) {
​
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t 的版本号是:" + stamp);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1);
            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1);
​
        }).start();
​
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t 的版本号是:" + stamp);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            boolean r = atomicStampedReference.compareAndSet(100, 1020, stamp, stamp + 1);
            System.out.println(r);
            System.out.println(atomicStampedReference.getReference());
​
        }).start();
​
    }
​
}
​

 

我们先保证两个线程的初始版本为一致,后面修改是由于版本不一样就会修改失败。

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

  • 故障现象

package com.haha;
​
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
​
public class ContainerDemo {
​
    public static void main(String[] args) {
        //CopyOnWriteArrayList<>()
        List<Integer> list = new ArrayList<>();
        Random random = new Random();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                list.add(random.nextInt(10));
                System.out.println(list);
            }).start();
        }
​
    }
​
}
//java.util.ConcurrentModificationException
​

异常:java.util.ConcurrentModificationException

  • 导致原因

    • 并发修改导致的异常

  • 解决方案

    • new Vector()

    • Collections.synchronizedList(new ArrayList<>());

    • new CopyOnWriteArrayList<>();

  • 优化建议

    • 在读多写少的时候推荐使用CopyOnWriteArrayList这个类

package com.haha;
​
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
​
public class ContainerNotSafeDemo {
​
    public static void main(String[] args) {
​
        // List<String> list =Arrays.asList("a","b","c");
        // list.forEach(System.out::println);
        // List<String> list = new ArrayList<>();// new ArrayList的初始值是ten.
        // list.add("a");
        // list.add("b");
        // list.add("c");
        // for (String element : list) {
        // System.out.println(element);
        // }
        // List<String> list = new ArrayList<>();// new ArrayList的初始值是ten.
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
​
}
​

ArrayList,Set,Map,的并发的写时复制

package com.haha;
​
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
​
import com.sun.jersey.client.impl.CopyOnWriteHashMap;
​
public class ContainerNotSafeDemo {
​
    public static void main(String[] args) {
​
        // List<String> list =Arrays.asList("a","b","c");
        // list.forEach(System.out::println);
        // List<String> list = new ArrayList<>();// new ArrayList的初始值是ten.
        // list.add("a");
        // list.add("b");
        // list.add("c");
        // for (String element : list) {
        // System.out.println(element);
        // }
        // List<String> list = new ArrayList<>();// new ArrayList的初始值是ten.
​
        // List<String> list = new CopyOnWriteArrayList<>();
        // Set<String> list = new CopyOnWriteArraySet<>();
​
        // for (int i = 1; i <= 30; i++) {
        // new Thread(() -> {
        // list.add(UUID.randomUUID().toString().substring(0, 8));
        // System.out.println(list);
        // }, String.valueOf(i)).start();
        // }
        // new HashSet<>().add(); //看源码
​
        // Map<String, String> map = new CopyOnWriteHashMap<>();错误!
​
        Map<String, String> map = new ConcurrentHashMap<>();
​
        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
​
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
                System.out.println(map);
​
            }).start();
        }
    }
​
}
​

 

Java 中锁你知道哪些?请手写一个自旋锁?

公平锁和非公平锁

  • 是什么

    • 公平锁:是指多个线程按照申请的顺序来获取值

    • 非公平锁:是只多个线程获取值的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,可能会造成优先级反转或者饥饿现象

  • 两者的区别

    • 公平锁:在并发环境中,没个线程在获取锁时会先查看此锁维护的的等待队列,如果为空,或者当前线程是等待队列的第一个就占有锁,否则就加入到等待队列,按照FIFO原则获取锁。

    • 非公平锁:一上来就尝试占有锁,如果失败再进行排队(类似公平锁那种方式)。

  • Java ReentrantLock 而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁,非公平锁的优点在于吞吐量比公平锁大。

  • 对Synchronized而言,也是一种非公平锁

package com.haha;
​
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
public class T1 {
​
    volatile int n = 0;
​
    public void add() {
        n++;
    }
​
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();// notFair 非公平锁
​
    }
​
}
​

 

可重入锁和不可重入锁

  • 是什么

  • ReentrantLock/Synchronized就是典型的可重入锁

  • 可重入锁最大的作用是避免死锁

    • 可重入锁:指的是同一个线程外层函数获得锁之后,内层仍然能获取到该锁,在同一个线程在外层方法获取锁时,再进入内层方法会自动获取该锁

    • 不可重入锁:所谓不可重入锁,就是若当前线程执行某个方法已经获得了该锁,name在方法中尝试再次获得取锁时,就会被阻塞,获取不到。

    • 可重入锁:

package com.hi;
​
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
class Phone implements Runnable {
​
    // 线程操作资源类
    public synchronized void sendDX() {
​
        System.out.println(Thread.currentThread().getName() + "\t 发送短信");
        sendEmail();
    }
​
    public synchronized void sendEmail() {
        System.out.println(Thread.currentThread().getName() + "\t *****发送邮件");
​
    }
​
    // ==============================================================================
​
    Lock lock = new ReentrantLock();
​
    @Override
    public void run() {
        try {
            get();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
​
    public void get() throws Exception {
        lock.lock();
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t get()");
            set();
        } finally {
            lock.unlock();
            lock.unlock();
        }
    }
​
    public void set() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t*********** set()");
​
        } finally {
            lock.unlock();
        }
    }
}
​
public class ReentrantLockDemo {
​
    public static void main(String[] args) throws Exception {
        Phone phone = new Phone();
​
        new Thread(() -> {
            phone.sendDX();
        }, "t1").start();
​
        new Thread(() -> {
            phone.sendDX();
        }, "t2").start();
​
        Thread.sleep(3000);
        System.out.println();
        System.out.println();
        System.out.println();
        System.out.println();
​
        Thread t3 = new Thread(phone, "t3");
        Thread t4 = new Thread(phone, "t4");
        t3.start();
        t4.start();
​
    }
​
}
/*
t1   发送短信
t1   *****发送邮件
t2   发送短信
t2   *****发送邮件
​
​
​
​
t3   get()
t3  *********** set()
t4   get()
t4  *********** set()
​
​
*/

上面例子可以说明 ReentrantLock 和Synchronized是可重入锁

不可重入锁:

package com.hi;
​
public class Count {
    NotReentrantLockDemo lock = new NotReentrantLockDemo();
​
    public void print() throws Exception {
        lock.lock();
        doAdd();
    }
​
    private void doAdd() throws Exception {
        lock.lock();
​
        lock.unlock();
    }
​
    public static void main(String[] args) throws Exception {
        Count count = new Count();
        count.print();
    }
​
}
// 线程一直处于阻塞状态
​

当前线程执行print()方法首先获取lock , 接下来执行doAdd()方法就无法执行doAdd()中的逻辑,必须先释放锁。

自旋锁

  • 是指定尝试获取锁的线程不会立即堵塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU.

  • 实现自旋锁

package com.hi;
​
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
​
public class SpinLock2 {
​
    AtomicReference<Thread> atomicReference = new AtomicReference<>();
​
    public void sLock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\t come in O(∩_∩)O哈哈~");
        while (!atomicReference.compareAndSet(null, thread)) {
​
        }
​
    }
​
    public void sUnLock() {
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
        System.out.println(Thread.currentThread().getName() + "\t invoked myUnLock()");
    }
​
    public static void main(String[] args) {
        SpinLock2 spinLock2 = new SpinLock2();
        new Thread(() -> {
            spinLock2.sLock();
            try {
                TimeUnit.SECONDS.sleep(5);、、50000
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            spinLock2.sUnLock();
        }, "AAA").start();
​
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
​
        new Thread(() -> {
            spinLock2.sLock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            spinLock2.sUnLock();
        }, "BBB").start();
​
    }
​
}
​
​
输出结果:
AAA  come in O(∩_∩)O哈哈~
BBB  come in O(∩_∩)O哈哈~
AAA  invoked myUnLock()
BBB  invoked myUnLock()
​

获取所得时候,如果原子应用为空就获取空锁,不为空表示有人获取了锁,就循环等待。

独占锁(写写锁) / 共享锁 (读锁)

  • 是什么?

    • 独占锁:指该锁一次只能被一个线程持有

    • 共享锁:该锁可以被多个线程持有

  • 对于ReentrantLock 和synchonized都是独占锁;对于ReentrantReadWriteLock 其读锁是共享锁而写锁是独占锁,读锁的共享可以保证高效的并发读,读写、写读和写写的过程是互斥的。

  • 读写锁实例:

    实例1:不加读写锁

package com.hi;
​
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();
    // private Lock lock =new ReentrantLock();
​
    public void put(String key, Object value) {
        System.out.println(Thread.currentThread().getName() + "\t正在写入" + key);
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "***写入完成");
​
    }
​
    public void get(String key) {
​
        System.out.println(Thread.currentThread().getName() + "\t正在读取");
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        Object result = map.get(key);
        System.out.println(Thread.currentThread().getName() + "\t读取完成" + result);
​
    }
​
}
​
public class ReadWriteLockDemo01 {
​
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
​
        for (int i = 1; i <= 5; i++) {
            final int ii = i;
            new Thread(() -> {
                myCache.put(ii + "", ii + "");
            }, String.valueOf(i)).start();
        }
​
        for (int i = 1; i <= 5; i++) {
            final int ii = i;
            new Thread(() -> {
                myCache.get(ii + "");
            }, String.valueOf(i)).start();
        }
    }
​
}
/*
2   正在写入2
5   正在写入5
4   正在写入4
1   正在写入1
3   正在写入3
1   正在读取
2   正在读取
3   正在读取
4   正在读取
5   正在读取
2***写入完成
5   读取完成null
5***写入完成
4   读取完成4
3   读取完成3
2   读取完成2
1   读取完成null
1***写入完成
4***写入完成
3***写入完成
 * 
 */
​

实例2:读写锁

package com.hi;
​
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
​
class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();
    // private Lock lock =new ReentrantLock();
    ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
​
    public void put(String key, Object value) {
        rwLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t正在写入" + key);
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "***写入完成");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwLock.writeLock().unlock();
        }
​
    }
​
    public void get(String key) {
        rwLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t正在读取");
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t读取完成" + result);
​
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwLock.readLock().unlock();
        }
​
    }
    
//  public void clearMap(){
//      map.clear();
//  }
​
}
​
public class ReadWriteLockDemo01 {
​
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
​
        for (int i = 1; i <= 5; i++) {
            final int ii = i;
            new Thread(() -> {
                myCache.put(ii + "", ii + "");
            }, String.valueOf(i)).start();
        }
​
        for (int i = 1; i <= 5; i++) {
            final int ii = i;
            new Thread(() -> {
                myCache.get(ii + "");
            }, String.valueOf(i)).start();
        }
    }
​
}
/*
 3  正在写入3
3***写入完成
5   正在写入5
5***写入完成
1   正在写入1
1***写入完成
4   正在写入4
4***写入完成
2   正在写入2
2***写入完成
3   正在读取
1   正在读取
2   正在读取
4   正在读取
5   正在读取
3   读取完成3
1   读取完成1
4   读取完成4
2   读取完成2
5   读取完成5
​
 */
​

CountDownLatch/CyclicBarrier/Semaphore 使用过么?

CountDownLatch

让一些线程堵塞直到另一个线程完成一系列操作后才被唤醒。CountDownLatch主要有两个方法,当一个或多个线程条用await()方法时,调用线程会被阻塞,其他线程调用countDown方法会将计数减一(调用countDown方法的线程不会阻塞),当计数器值变为零时,因调用await方法被堵塞的线程会被唤醒,继续执行。

 

假设我们有这样一个场景,教室里有班长和另外6个同学在教室自习,怎么保证班长等同学都走出教室再关门。(做减法)

before

package com.hi;
​
import java.util.concurrent.CountDownLatch;
​
public class CountDownLatchDemo {
​
    public static void main(String[] args) {
        //CountDownLatch countDownLatch = new CountDownLatch(6);
        
​
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t上完自习,go home ");
                //countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }
//      try {
//          //countDownLatch.await();
//      } catch (InterruptedException e) {
//          // TODO Auto-generated catch block
//          e.printStackTrace();
//      }
        System.out.println(Thread.currentThread().getName() + "\t班长锁门 ");
    }
​
}
/*
2   上完自习,go home 
6   上完自习,go home 
5   上完自习,go home 
1   上完自习,go home 
main    班长锁门 
4   上完自习,go home 
3   上完自习,go home 
​
 
 */
​

 

after:

package com.hi;
​
import java.util.concurrent.CountDownLatch;
​
public class CountDownLatchDemo {
​
    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        
​
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t上完自习,go home ");
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "\t班长锁门 ");
    }
​
}
/*
 1  上完自习,go home 
5   上完自习,go home 
6   上完自习,go home 
2   上完自习,go home 
4   上完自习,go home 
3   上完自习,go home 
main    班长锁门 
 
 */
​

CyclicBarrier

我们假设有这么一个场景,集齐七颗龙珠,召唤神龙。(做加法)

package com.hi;
​
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
​
public class CyclicBarrierDemo {
​
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            // Thread.currentThread().getName()
            System.out.println("————————召唤神龙");
        });
​
        for (int i = 1; i <= 7; i++) {
            final int ii = i;
            new Thread(() -> {
                
                System.out.println(Thread.currentThread().getName() + "\t收集到地" + ii + "颗龙珠");
                        try {
                            cyclicBarrier.await();
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        } catch (BrokenBarrierException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
            }, String.valueOf(i)).start();
        }
​
    }
​
}
/*
 1  收集到地1颗龙珠
7   收集到地7颗龙珠
3   收集到地3颗龙珠
2   收集到地2颗龙珠
5   收集到地5颗龙珠
6   收集到地6颗龙珠
4   收集到地4颗龙珠
————————召唤神龙
​
 */
​

Semaphore

假设我们有3个停车位,6辆车去抢

package com.hi;
​
import java.util.concurrent.Semaphore;
​
public class SemaphoreDemo {
​
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();// 获取一个许可
                    System.out.println(Thread.currentThread().getName() + "\t 抢到车位...");
                    Thread.sleep(3000);
                    System.out.println(Thread.currentThread().getName() + "\t 离开车位...");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } finally {
                    semaphore.release();//释放一个许可
                }
            }, String.valueOf(i)).start();
        }
​
    }
​
}
/*
 3   抢到车位...
2    抢到车位...
1    抢到车位...
3    离开车位...
2    离开车位...
1    离开车位...
4    抢到车位...
5    抢到车位...
6    抢到车位...
4    离开车位...
6    离开车位...
5    离开车位...
​
 */
​

堵塞队列知道么?

阻塞队列有哪些

  • ArrayBlockingQueue: 是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)对元素进行排序。

  • LinkedBlockingQueue:是一个基于链表结构的阻塞队列,此队列按FIFO(先进先出)对元素进行排序,吞吐量通常要高于ArrayBlockingQueue.

  • SynchronousQueue:是一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常高于LinkedBlockingQueue.

什么是阻塞队列 FIFO

  • 阻塞队列,首先是一个队列,而一个阻塞队列在数据结构中所起的作用大致FIFO(脑补)

  • 当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。

  • 当阻塞队列是满时,往队列中添加元素的操作将会被阻塞。

  • 核心方法

    | 方法\行为 | 抛异常 | 特定的值 | 阻塞 | 超时 |
:——-::——-::—————::—-::————————-:
     
插入方法add(o)offer(o)put(o)offer(o, timeout, timeunit)
-------------------------------------------------------
     
移除方法 poll()、remove(o)take()poll(timeout, timeunit)
----------------------------------------------------------
     
检查方法element()peek()  
-------------------------------
     

 

  • 行为解释:

    • 抛异常:如果操作不能马上进行,则抛出异常

    • 特定的值:如果操作不能马上进行,将会返回一个特殊的值,一般是true或false

    • 阻塞:如果操作不能马上进行,操作会被阻塞

    • 超时:如果操作不能马上进行,操作会被阻塞指定的时间,如果指定时间没执行,则返回一个特殊值,一般是true或者false

  • 插入方法:

    • add(E e): 添加成功返回true,失败抛IllegalStateException 异常

    • offer(E e): 成功返回true,如果此队列已满,则返回false.

    • put(E e): 将元素插入此队列的尾部,如果该队列已满,则一直阻塞

  • 删除方法:

    • remove(Object o); 移除指定元素,成功 返回true

    • poll(): 获取并移除此队列的头元素,若队列为空,则返回null

    • take():获取并移除此队列头元素,若没有元素则一直阻塞

  • 检查方法:

    • element(): 获取但不移除此队列的头元素,没有元素则抛异常

    • peek():获取但不移除此队列的头;若队列为空,则返回null

SynchronousQueue

SynchronousQueue,实际上它不是一个真正的队列,因为它不会为队列中元素维护存储空间。与其他队列不同的是,它维护一组线程,这些线程在等待着吧元素加入或移除队列。一个put() 等待一个 take();

​
package com.hi;
​
import java.util.concurrent.SynchronousQueue;
​
public class SynchronousQueueDemo {
​
    public static void main(String[] args) {
​
        SynchronousQueue<Integer> synchronousQueue = new SynchronousQueue<>();
        new Thread(()->{
            try {
                synchronousQueue.put(1);
                Thread.sleep(3000);
                synchronousQueue.put(2);
                Thread.sleep(3000);
                synchronousQueue.put(3);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }).start();
        
        
        new Thread(()->{
            try {
                Integer v1 = synchronousQueue.take();
                System.out.println(v1);
                Integer v2 = synchronousQueue.take();
                System.out.println(v2);
                Integer v3 = synchronousQueue.take();
                System.out.println(v3);
                
                
                
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }).start();
        
    }
​
}
​
​

使用场景

  • 生成者消费者模式

  • 线程池

  • 消息中间件

线程通信生产者,消费者

package com.hi;
​
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
class ShareData {
    private int num = 0;
    private Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
​
    public void increment() {
        lock.lock();
​
        try {
            while (num != 0) {
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName() + "\t" + num);
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
​
    }
​
    public void decrement() {
        lock.lock();
        try {
            //while 和  if 的区别 !  if 线程虚假唤醒!
            while (num == 0) {
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + "\t" + num);
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
​
    }
​
}
​
public class ProdConsumer_TraditionDemo {
​
    public static void main(String[] args) {
​
        ShareData shareData = new ShareData();
​
        for (int i = 1; i <= 5; i++) {
            new Thread(() -> {
                shareData.increment();
​
            }, "AAA").start();
        }
​
        for (int i = 1; i <= 5; i++) {
            new Thread(() -> {
                shareData.decrement();
​
            }, "BBB").start();
        }
        
        
        for (int i = 1; i <= 5; i++) {
            new Thread(() -> {
                shareData.increment();
​
            }, "CCC").start();
        }
​
        for (int i = 1; i <= 5; i++) {
            new Thread(() -> {
                shareData.decrement();
​
            }, "DDD").start();
        } 
    }
}
​
/*AAA   1
BBB 0
AAA 1
BBB 0
AAA 1
BBB 0
AAA 1
BBB 0
CCC 1
BBB 0
...*/

 

Synchronized 和Lock有什么区别?

  •  

  • 原始结构

    • Synchronized 是关键字属于JVM层面,反应在字节码上是Monitorenter和monitorexit,其底层是通过monitor对象来完成的,其实wait/notify等方法也是依赖monitor对象只有在同步方法或同步块中才能调用wait/notify等方法。

    • Lock是具体类(java.until.concurrent.locks.Lock)是 api 层面的锁。

  • 使用方法

    • synchronzied 不需要用户手动的释放锁,当synchronized代码执行完毕后,系统会自动让线程释放对锁的占用。

    • ReentrantLock 则需要用户手动的释放锁,若没有主动释放锁,可能导致死锁的现象,lock()和unlock()方法需要配合try/finally语句来完成。

  • 等待是否可中断

    • synchronized 不可中断,除非抛出异常或正常运行完成。

    • ReentrantLock 可中断,设置超市方法 tryLock(long timeout,timeUnut unit), lockInterruptibly()放代码块中,调用interrupt()方法可中断。

  • 加锁是否公平

    • synchronized 非公平锁

    • ReentrantLock默认是非公平锁,构造方法中可以传入boolean值,true为公平锁,false为非公平锁。

  • 锁可以绑定多个Condition

  • synchronized 没有Condition

  • ReentrantLock 用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个线程,要么唤醒全部线程。

  • 实例:绑定多个Condition

    package com.hi;
    ​
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    ​
    class ShareResource {
        private int num = 1;
        Lock lock = new ReentrantLock();
        Condition c1 = lock.newCondition();
        Condition c2 = lock.newCondition();
        Condition c3 = lock.newCondition();
        Condition c4 = lock.newCondition();
    ​
        public void print5() {
            lock.lock();
            try {
                while (num != 1) {
                    try {
                        c1.await();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
    ​
                for (int i = 1; i <= 5; i++) {
                    System.out.println(Thread.currentThread().getName() + "\t" + i);
                }
    ​
                num = 2;
                c2.signal();
    ​
            } finally {
                lock.unlock();
            }
    ​
        }
    ​
        public void print10() {
            lock.lock();
            try {
                while (num != 2) {
                    try {
                        c2.await();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
    ​
                for (int i = 1; i <= 10; i++) {
                    System.out.println(Thread.currentThread().getName() + "\t" + i);
                }
    ​
                num = 3;
                c3.signal();
    ​
            } finally {
                lock.unlock();
            }
    ​
        }
    ​
        public void print15() {
            lock.lock();
            try {
                while (num != 3) {
                    try {
                        c3.await();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
    ​
                for (int i = 1; i <= 15; i++) {
                    System.out.println(Thread.currentThread().getName() + "\t" + i);
                }
    ​
                num = 1;
                c1.signal();
    ​
            } finally {
                lock.unlock();
            }
    ​
        }
    ​
    }
    ​
    public class SyncAndReentrantLockDemo {
    ​
        public static void main(String[] args) {
    ​
            ShareResource shareResource = new ShareResource();
    ​
            new Thread(() -> {
                for (int i = 1; i <= 10; i++) {
    ​
                    shareResource.print5();
                }
            }, "AAA").start();
    ​
            new Thread(() -> {
                for (int i = 1; i <= 10; i++) {
    ​
                    shareResource.print10();
                }
            }, "BBB").start();
    ​
            new Thread(() -> {
                for (int i = 1; i <= 10; i++) {
    ​
                    shareResource.print15();
                }
            }, "CCC").start();
    ​
        }
    ​
    }
    ​

     

    阻塞队列版生产者和消费者:

    package com.hi;
    ​

import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger;

class Mylulu { private volatile boolean FLAG = true; private AtomicInteger atomicInteger = new AtomicInteger(); BlockingQueue<String> blockingQueue = null;

public Mylulu(BlockingQueue<String> blockingQueue) { this.blockingQueue = blockingQueue; System.out.println(blockingQueue.getClass().getName()); }

// System.out.println(Thread.currentThread().getName()+"\t"); public void prod() throws Exception { String data = null; boolean resVal; while (FLAG) { data = atomicInteger.incrementAndGet() + ""; resVal = blockingQueue.offer(data, 2L, TimeUnit.SECONDS); if (resVal) { System.out.println(Thread.currentThread().getName() + "\t插入队列" + data + "成功"); } else { System.out.println(Thread.currentThread().getName() + "\t插入队列" + data + "失败"); } TimeUnit.SECONDS.sleep(1);

} System.out.println("老板带着小姨子跑路了,停止生产.");

}
  
public void constu() throws Exception {
  
    String result = null;

while (FLAG) { result = blockingQueue.poll(2, TimeUnit.SECONDS); if (result == null || result.equalsIgnoreCase("")) { FLAG = false; System.out.println(Thread.currentThread().getName() + "\t超过两秒,消费退出"); System.out.println(); System.out.println(); return;

} // System.out.println(Thread.currentThread().getName()+"\t");

System.out.println("消费" + result + "成功"); }

}

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

}

public class ProdConsumer_BlockingQueueTest {

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

Mylulu mylulu = new Mylulu(new ArrayBlockingQueue<>(10));

new Thread(() -> { try { System.out.println(Thread.currentThread().getName() + "\t生产线程启动"); mylulu.prod(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }, "生产线程").start();

new Thread(() -> { try { System.out.println(Thread.currentThread().getName() + "\t消费线程启动"); mylulu.constu(); System.out.println(); System.out.println(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }, "消费线程").start();

Thread.sleep(5000); System.out.println(); System.out.println(); System.out.println(); System.out.println("5秒钟后,王八蛋老板带着小姨子跑路了,结束。"); mylulu.stop();

}

}

  
  
  
  ## 线程池用过么?谈谈对ThreadPoolExector的理解?
  
  #### 为什么使用线程池,线程池的优势?
  
  线程池用于多线程处理中,它可以根据系统的情况,可以有效地控制线程执行的数量,优化运行效果。线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数,那么超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
  
  主要特点为:
  
  * 线程复用
  * 控制最大并发数量
  * 管理线程
  
  主要优点为:
  
  * 降低资源消耗,通过重复利用已创建的线程来降低线程创建和销毁造成的消耗。
  * 提高相应的速度,当任务到达时,任务可以不需要等到线程创建就能立即执行。
  * 提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅仅会消耗系统资源,还会降低系统稳定性,使用线程可以进行统一分配,调优和监控。
  
  ### 创建线程的几种方式
  
  * 继承Thread
  * 实现Runnable接口  run() 
  * 实现Callable     call()
  
  ```java
  package com.hi;
  
  import java.util.concurrent.Callable;
  import java.util.concurrent.ExecutionException;
  import java.util.concurrent.FutureTask;
  
  class MyThread1 implements Runnable {
  
    @Override
    public void run() {
        // TODO Auto-generated method stub
  
    }
  }
  
  class MyThread2 implements Callable<Integer> {
  
    @Override
    public Integer call() throws Exception {
  
        System.out.println("********come Callable");
        return 1024;
    }
  }
  
  public class CallableDemo {
  
    public static void main(String[] args) throws Exception, Exception {
  
        // 有两个线程 main , AAA futureTask
  
        FutureTask<Integer> futureTask = new FutureTask<>(new MyThread2());
        // FutureTask<Integer> futureTask2 = new FutureTask<>(new MyThread2());
        Thread t1 = new Thread(futureTask, "AAA");
        new Thread(futureTask, "BBB").start();// futureTask2
        t1.start();
        int result01 = 100;
  
        while (!futureTask.isDone()) {// 类似自旋锁
  
        }
        int result02 = futureTask.get();// 建议放在最后(futureTask.get())
        System.out.println("****result" + (result01 + futureTask.get()));
  
    }
  }
  

 

线程池如何使用?

架构说明

 

编码实现

  package com.hi;
  
  import java.util.concurrent.Executor;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  
  public class MyThreadPoolDemo {
    // 第四种使用java多线程的方式 线程池
    public static void main(String[] args) {
        // 5个处理线程 // 模拟10个用户来办理业务,每个用户就是一个来自外部的线程
        // ExecutorService threadPool = Executors.newFixedThreadPool(5); //
        
        
        //1个处理线程
        //ExecutorService threadPool = Executors.newSingleThreadExecutor();
        
        
        ExecutorService threadPool = Executors.newCachedThreadPool();
        
        
        // 模拟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();
        }
  
    }
  
  }
  

 

  • Executors.newSingleThreadExecutor() :只有一个线程的线程池,因此所有提交的任务是顺序执行

  • Executors.newFixedThreadExecutor():拥有固定线程数的线程池,如果没有任务执行,那么线程会一直等待

  • Executors.newCacheThreadPool():线程池中有很多线需要同时执行,老的可用线程将被新的任务触发重新执行,如果线程超过60秒内没有被执行,那么将被终止并从池中删除

  • Executors.newScheduledThreadPool():用来调度即将执行的任务的线程池

  • Executors.newWorkStealingPool(): newWorkStealingPool适合使用在很耗时的操作,但是newWorkStealingPool不是ThreadPoolExecutor的扩展,它是新的线程池类ForkJoinPool的扩展,但是都是在统一的一个Executors类中实现,由于能够合理的使用CPU进行对任务操作(并行操作),所以适合使用在很耗时的任务中

ThreadPoolExecutor

ThreadPoolExecutor作为java.util.concurrent包对外提供基础实现,以内部线程池的形式对外提供管理任务执行,线程调度,线程池管理等服务。

ThreadPoolExecutor是以上底层(Executors.newSingleThreadExecutor() ...)的底层实现,5个参数,实现了LinkedBlockingQueue<Runnable>() 阻塞队列和SynchronousQueue<Runnable> 。

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

7大参数

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

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

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

  4. unit:keepAliveTime的单位( 时间单位)

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

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

  7. handler:拒绝策略,表示当队列满了并且工作线程大于线程池的最大线程数(miximumPoolSize)时如何来拒绝请求执行的runnable的策略。

参数作用
corePoolSize核心线程池大小
maxmumPoolSize最大线程池大小
keepAliveTime线程池中超过corePoolSize 数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)使得核心线程有效时间
TimeUnitKeepAliveTime时间单位
WorkQueue阻塞队列任务
threadFactory新建线程工厂
RejectedExecutionHandler当提交任务数超过maxmumPoolSize+WorkQueue之和时,任务会交给RejectedExecutionHandler来处理

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

重点讲解: 其中比较容易让人误解的是:corePoolSize,maximumPoolSize,workQueue之间关系。

  1. 当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。

  2. 当线程池达到corePoolSize时,新提交任务将会被放入workQueue中,等待线程池中任务调度执行。

  3. 当workQueue已满,且maxmumPoolSize大于corePoolSize时,新提交任务将会创建新线程执行任务。

  4. 当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理

  5. 当线程池中超过corePoolSIze线程,空闲时间达到keepAliveTime时,关闭空闲线程。

  6. 当设置allowCoreThreadTimeOut(true)时,线程池中 corePoolSize线程空闲时间达到keepAiveTime也将关闭。


 

 

 

 

#### 以下重要 以下重要 以下重要 以下重要 以下重要

  1. 在创建了线程池后,等待提交过来的任务请求。

  2. 当调用了execute()方法添加一个请求任务时,线程池会做如下判断;

    1. 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务

    2. 如果正在运行的线程数量大于corePoolSize,那么会放入任务队列

    3. 如果这时,任务队列也满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务。

    4. 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。

  3. 当一个线程完成任务时,它会从队列中取下一个任务来执行

  4. 当一个线程无事可做超过一定的时间(KeepAliveTime)时,线程池会判断;如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。

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

线程池的拒绝策略你谈谈?

  • 是什么?

    • 等待队列已经满了,再也塞不下新的任务,同时线程池中的线程数达到了最大线程数,无法继续为新任务服务。

  • 拒绝策略

    • AbortPolicy: 处理程序遭到拒绝将抛出运行时RejectedExecutionException

    • CallerRunsPolicy:线程调用运行该任务的 execute 本事。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。

    • DiscardPolicy: 不能执行的任务将被删除

    • DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)

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

如果读者对java中的阻塞队列有所了解的话,看到这里或许就能明白原因了。

java中的BlockingQueue主要有两种实现,分别是ArrayBlockingQueue和LinkedBlockingQueue.

ArrayBlockingQueue是一个用数组实现的有界阻塞队列,必须设置容量。

LinkedBlockingQueue是一个用链表实现的有界阻塞队列,容量可以选择设置,不设置的话,将是一个无边界的阻塞队列,最大长队为Integer.MAX_VALUE.

这里的问题就在于:不设置的话,将是一个无边界阻塞队列,最大长度为Integer.MAX_VALUE.也就是说,如果我们不设置LinkedBlockingQueue容量的话,其默认值为Integer.MAX_VALUE.

而newFixedThreadPool中创建LinkedBlockingQueue时,并未指定容量。此时LinkedBlockingQueue就是一个无边界队列,对于一个无边界队列来说是可以不断的向队列中加入任务的,这种情况下就可能因为任务过多而导致OOM(内存溢出问题)。

上面提到的问题主要体现在newFixedThreadPool和newSingleThreadExecutor两个工厂方法上,并不是说newCachedThreadPool和newScheduleThreadPool这两个方法就安全了,这两种方式创建的最大线程数可能是Integer.MAX_VALUE,而创建这么多线程,必然有可能导致OOM.

你在工作中如何使用线程池的,是否自定义过线程池使用?

自定义线程池

package com.hi;
​
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
​
public class ThreadPoolExecutorDemo {
​
    public static void main(String[] args) {
        new ThreadPoolExecutor(2, 3, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5),
                Executors.defaultThreadFactory(),
                // new ThreadPoolExecutor.DiscardPolicy()
                new ThreadPoolExecutor.DiscardPolicy());// 拒绝策略
    }
}
​

合理配置线程池你是如何考虑的?

  • CPU密集型

    • CPU 密集的意思是该任务需要大量的运算,而且没有阻塞,CPU一直全速运行。

    • CPU密集型任务尽可能的少的线程数量,一般为CPU核数+1个线程的线程池。

  • IO密集型

    • 由于IO密集型任务线程并不是一直在执行任务,可以多分配一点线程数,如CPU*2.

    • 也可以使用公式:CPU核数/(1-阻塞系数);其中阻塞系数在0.8~0.9之间。

 

死锁编码以及定位分析

  • 产生死锁的原因

    • 死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种相互等待的现象,如果无外力的干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。

  • 代码:

package com.hi;
​
import java.util.concurrent.TimeUnit;
​
class HoldLockThread implements Runnable {
    private String lockA;
    private String lockB;
​
    public HoldLockThread(String lockA, String lockB) {
        super();
        this.lockA = lockA;
        this.lockB = lockB;
    }
​
    @Override
    public void run() {
​
        synchronized (lockA) {
            System.out.println(Thread.currentThread().getName() + "\t 自己持有" + lockA + "\t尝试获得" + lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (Exception e) {
                e.printStackTrace();
            }
            synchronized (lockB) {
                System.out.println(Thread.currentThread().getName() + "\t 自己持有" + lockB + "\t尝试获得" + lockA);
            }
        }
​
    }
​
}
​
public class DeadLockDemo {
​
    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";
​
        new Thread(new HoldLockThread(lockA, lockB), "AAA").start();
        new Thread(new HoldLockThread(lockB, lockA), "BBB").start();
​
    }
}
​
  • 解决

    • jps-l 命令查定位进程号

E:\workspacenew1\ShiXun\src\main\java\com\hi>jps -l
14016
4992 com.hi.DeadLockDemo
6512 sun.tools.jps.Jps
​
E:\workspacenew1\ShiXun\src\main\java\com\hi>
​
​

jstack 4992 找到死锁查看

Java stack information for the threads listed above:
===================================================
"BBB":
        at com.hi.HoldLockThread.run(DeadLockDemo.java:26)
        - waiting to lock <0x000000078309c230> (a java.lang.String)
        - locked <0x000000078309c268> (a java.lang.String)
        at java.lang.Thread.run(Thread.java:748)
"AAA":
        at com.hi.HoldLockThread.run(DeadLockDemo.java:26)
        - waiting to lock <0x000000078309c268> (a java.lang.String)
        - locked <0x000000078309c230> (a java.lang.String)
        at java.lang.Thread.run(Thread.java:748)
​
Found 1 deadlock.
​
​
E:\workspacenew1\ShiXun\src\main\java\com\hi>

最后发现死锁!

 

参考链接

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值