1、什么是JUC?
在Java中,线程部分是一个重点,本篇文章说的JUC也是关于线程的。JUC就是java.util .concurrent工具包的简称。这是一个处理线程的工具包, JDK1.5开始出现的。
1.1、线程的状态
1.1.1、线程状态枚举类
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
//新建
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
//准备就绪
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
//阻塞
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
//不见不散
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
//过时不候
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
//终结
TERMINATED;
}
- NEW:新建
- RUNNABLE:准备就绪
- BLOCKED:阻塞
- WAITING:不见不散
- TIMED_WAITING:过时不候
- TERMINATED:终结
操作系统中进程的五种状态:

1.2、wait/sleep区别
- sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用;
- sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized 中)wait有一个特点:在哪里睡,就在哪里醒!;
- 它们都可以被 interrupted 方法中断。
1.3、管程
管程(monitor)是保证了同一时刻只有一个进程在管程内活动,即管程内定义的操作在同一时刻只被一个进程调用(由编译器实现)。但是这样并不能保证进程以设计的顺序执行。
JVM中同步是基于进入和退出管程(monitor)对象实现的,每个对象都会有一个管程(monitor)对象,管程(monitor)会随着java对象一同创建和销毁。
执行线程首先要持有管程对象,然后才能执行方法,当方法完成之后会释放管程,方法在执行时候会持有管程,其他线程无法再获取同一个管程。
1.4、用户线程和守护线程
- 用户线程:平时用到的普通线程,自定义线程
- 守护线程:运行在后台,是一种特殊的线程,比如垃圾回收
public class Main4
{
public static void main(String[] args)
{
Thread aa = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "::" + Thread.currentThread().isDaemon());
while (true) {
}
}, "aa");
aa.start();
System.out.println(Thread.currentThread().getName() + " over");
}
}

- 当主线程结束后,用户线程还在运行,JVM存活
调用setDaemon()方法把用户线程变为守护线程:
public class Main4
{
public static void main(String[] args)
{
Thread aa = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "::" + Thread.currentThread().isDaemon());
while (true) {
}
}, "aa");
//设置守护线程
aa.setDaemon(true);
aa.start();
System.out.println(Thread.currentThread().getName() + " over");
}
}

- 如果没有用户线程,都是守护线程,JVM结束
2、Lock接口
2.1、synchronized关键字
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此, synchronized关键字不能被继承。 - 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。
2.1.1、售票案例

要求:3个售票员卖出30张票。
第一步:创建资源类,在资源类中创建属性和操作方法
//1、创建一个资源类,定义属性和方法
class Ticket{
//票数
private int number = 30;
//操作方法:卖票
public synchronized void sale(){
//判断:是否有票
if (number > 0){
System.out.println(Thread.currentThread().getName() + " : 卖出: " + (number--) + " 剩下: " + number);
}
}
}
第二步:创建多个线程,调用资源类的操作方法
public class SaleTicket
{
//2、创建多个线程,调用
public static void main(String[] args)
{
//创建Ticket对象
Ticket ticket = new Ticket();
//创建三个线程
new Thread(new Runnable()
{
@Override
public void run()
{
//调用卖票的方法
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}
},"AA").start();
new Thread(new Runnable()
{
@Override
public void run()
{
//调用卖票的方法
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}
},"BB").start();
new Thread(new Runnable()
{
@Override
public void run()
{
//调用卖票的方法
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}
},"CC").start();
}
}
如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
- 获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
- 线程执行发生异常,此时 JVM 会让线程自动释放锁。
那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。
因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。
2.2、Lock接口
Lock锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。 Lock提供了比synchronized更多的功能。
Lock与的Synchronized区别:
- Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。 Lock是一个类,通过这个类可以实现同步访问;
- Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
- Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
- 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到;
- Lock可以提高多个线程进行读操作的效率;
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的;而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。
使用Lock接口实现上述案例:
//1、创建一个资源类,定义属性和方法
class LTicket{
//票数
private int number = 30;
//创建可重入锁
private final ReentrantLock lock = new ReentrantLock();
//操作方法:卖票
public void sale(){
//上锁
lock.lock();
try {
//判断:是否有票
if (number > 0){
System.out.println(Thread.currentThread().getName() + " : 卖出: " + (number--) + " 剩下: " + number);
}
}
finally {
//解锁
lock.unlock();
}
}
}
public class LSaleTicket
{
//2、创建多个线程,调用资源类的操作方法
public static void main(String[] args)
{
LTicket lTicket = new LTicket();
//创建三个线程
new Thread(()->{
for (int i = 0; i < 40; i++) {
lTicket.sale();
}
},"AA").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
lTicket.sale();
}
},"BB").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
lTicket.sale();
}
},"CC").start();
}
}
这里有一个问题:
调用start()方法时,线程是否已经创建?
查看start()源码,发现调用了native的方法,也就是说调用start()方法时,不一定会创建线程。
3、线程间的通信
操作系统中线程间的通信有哪几种方式?
- 管道,是一种半双工方式,数据单方向流动,而且只能在有亲缘关系的线程之间使用。他是基于内核的,可以把它当作是内核缓冲区,在内核和用户空间交换数据需要进行四次的数据拷贝,因为他是把用户空间的数据拷贝到内核,然后从内核拷贝到内存,然后从内存再拷贝到内核,最后拷贝到用户空间,至于为什么要拷贝到内核,是因为数据最重都是在内存中执行的。接收的时候要按照次序,按照先进先出的原则,在管道中的数据只能被读取1次,读出之后缓冲区就不存在了。它只能承载没有格式的字节流 。
- 命名管道,和管道差不多,但是它支持没有亲缘关系的线程之间通信。其他的类似。命名管道的名字对应磁盘索引截点,所有进程都可以对他进行访问。
- 消息队列,是消息的链表,是一系列保存在内核中的消息列表,同样交换数据也是需要4次拷贝的,它的优势是可以对每个消息指定特定的消息类型,接收的时候不需要按照队列的次序接收。
- 共享存储,就是映射一段可以被其他进程所访问的内存,由一个进程创建,多个进程都是可以访问的。用它传递数据只需要拷贝三次,用户空间->内存->用户空间。
- 信号量,用于控制多个进程对于共享资源的访问,可以用之线程同步。
- 套接字Socket,Socket套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
- 信号,是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生了。
&width=558.2000122070312 “多线程的编程步骤(中部)”)
案例:两个线程对number进行+1和-1
3.1、synchronized实现案例
//1、创建一个资源类,定义属性和方法
class Share{
//初始值
private int number = 0;
//+1的方法
public synchronized void incr() throws InterruptedException
{
//2、判断 干活 通知
if (number != 0){ //判断:如果number的值是否是0,如果不是0,等待
this.wait();
}
//干活:如果number的值是0,就+1
number++;
System.out.println(Thread.currentThread().getName() + "::" + number);
//通知:其他线程
this.notify();
}
//-1的方法
public synchronized void decr() throws InterruptedException
{
//2、判断 干活 通知
if (number != 1){ //判断:如果number的值是否是1,如果不是1,等待
this.wait();
}
//干活:如果number的值是1,就-1
number--;
System.out.println(Thread.currentThread().getName() + "::" + number);
//通知:其他线程
this.notify();
}
}
public class ThreadDemo1
{
//3、创建多个线程,调用资源类的操作方法
public static void main(String[] args)
{
Share share = new Share();
//创建线程
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.incr();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.decr();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
}
}
3.1.2、虚假唤醒问题
我们将线程的个数增加到4个,AA和CC线程执行+1操作;BB和CC线程执行-1操作。
public class ThreadDemo1
{
//3、创建多个线程,调用资源类的操作方法
public static void main(String[] args)
{
Share share = new Share();
//创建线程
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.incr();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.decr();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.incr();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.decr();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
},"DD").start();
}
}
执行结果:

出现了问题:我们希望的是只有0和1的值,但是这里边不符合预期。那么问题出现在哪里?在wait()方法:

具体分析:
wait有一个特点:在哪里睡,就在哪里醒!这就是导致虚假唤醒的原因!!!
解决方法:判断放在while语句块中
class Share{
//初始值
private int number = 0;
//+1的方法
public synchronized void incr() throws InterruptedException
{
//2、判断 干活 通知
while (number != 0){ //判断:如果number的值是否是0,如果不是0,等待
this.wait();
}
//干活:如果number的值是0,就+1
number++;
System.out.println(Thread.currentThread().getName() + "::" + number);
//通知:其他线程
this.notify();
}
//-1的方法
public synchronized void decr() throws InterruptedException
{
//2、判断 干活 通知
while (number != 1){ //判断:如果number的值是否是1,如果不是1,等待
this.wait();
}
//干活:如果number的值是1,就-1
number--;
System.out.println(Thread.currentThread().getName() + "::" + number);
//通知:其他线程
this.notify();
}
}
最终的多线程编程步骤:

3.2、Lock接口实现案例
//1、创建一个资源类,定义属性和方法
class Share{
//初始值
private int number = 0;
//创建Lock
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//+1的方法
public void incr() throws InterruptedException
{
//上锁
lock.lock();
try {
//判断
while (number != 0){
condition.await();
}
//干活
number++;
System.out.println(Thread.currentThread().getName() + "::" + number);
//通知
condition.signalAll();
}
finally {
//解锁
lock.unlock();
}
}
//-1的方法
public synchronized void decr() throws InterruptedException
{
//上锁
lock.lock();
try {
//判断
while (number != 1){
condition.await();
}
//干活
number--;
System.out.println(Thread.currentThread().getName() + "::" + number);
//通知
condition.signalAll();
}
finally {
//解锁
lock.unlock();
}
}
}
public class ThreadDemo2
{
public static void main(String[] args)
{
Share share = new Share();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.incr();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.decr();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.incr();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.decr();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
},"DD").start();
}
}
3.3、线程间的定制化通信
要求:
- AA打印5次,BB打印10次,CC打印15次
- AA打印15次,BB打印10次,CC打印15次
- 进行10轮
分析:

//1、创建资源类,定义属性和操作方法
class ShareResource{
//定义标志位
private int flag = 1; //1AA 2BB 3CC
//创建Lock锁
private Lock lock = new ReentrantLock();
//创建三个condition
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
//打印5次,参数第几轮
public void print5(int loop) throws InterruptedException
{
//上锁
lock.lock();
try {
//判断
while (flag != 1){
c1.await();
}
//干活
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName()+"::"+i+" :轮数:"+loop);
}
//通知
flag = 2; //修改标志位为2
c2.signal(); //通知BB线程
}
finally {
//解锁
lock.unlock();
}
}
//打印10次,参数第几轮
public void print10(int loop) throws InterruptedException
{
//上锁
lock.lock();
try {
//判断
while (flag != 2){
c2.await();
}
//干活
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName()+"::"+i+" :轮数:"+loop);
}
//通知
flag = 3; //修改标志位为3
c3.signal(); //通知CC线程
}
finally {
//解锁
lock.unlock();
}
}
//打印15次,参数第几轮
public void print15(int loop) throws InterruptedException
{
//上锁
lock.lock();
try {
//判断
while (flag != 3){
c3.await();
}
//干活
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName()+"::"+i+" :轮数:"+loop);
}
//通知
flag = 1; //修改标志位为1
c1.signal(); //通知AA线程
}
finally {
//解锁
lock.unlock();
}
}
}
public class ThreadDemo3
{
public static void main(String[] args)
{
ShareResource shareResource = new ShareResource();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
shareResource.print5(i);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
shareResource.print10(i);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
shareResource.print15(i);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
}
}
4、集合的线程安全
4.1、ArrayList集合线程不安全演示
查看ArrayList中add()方法的源码:
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
可以看到add()方法并没有加上synchronized关键字,所以这里边便存在着线程不安全的问题!
现在演示一种情况:使用多个线程,向list集合中添加元素,并从集合中获取元素:
//List线程不安全问题
public class ThreadDemo4
{
public static void main(String[] args)
{
//创建ArrayList集合
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
//向集合中添加元素
list.add(UUID.randomUUID().toString().substring(0,8));
//从集合中获取元素
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
输出的结果:
Exception in thread "2" Exception in thread "0" Exception in thread "4" Exception in thread "1" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at Lock.ThreadDemo4.lambda$main$0(ThreadDemo4.java:25)
at java.lang.Thread.run(Thread.java:748)
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at Lock.ThreadDemo4.lambda$main$0(ThreadDemo4.java:25)
at java.lang.Thread.run(Thread.java:748)
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at Lock.ThreadDemo4.lambda$main$0(ThreadDemo4.java:25)
at java.lang.Thread.run(Thread.java:748)
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at Lock.ThreadDemo4.lambda$main$0(ThreadDemo4.java:25)
at java.lang.Thread.run(Thread.java:748)
[null, null, 3b81634e, 278eb15f, 0687f3ba]
[null, null, 3b81634e, 278eb15f, 0687f3ba, d10c7698, 884dfd9e, 8e76f7e3, 3ee8aab6]
[null, null, 3b81634e, 278eb15f, 0687f3ba, d10c7698, 884dfd9e, 8e76f7e3]
[null, null, 3b81634e, 278eb15f, 0687f3ba, d10c7698, 884dfd9e]
[null, null, 3b81634e, 278eb15f, 0687f3ba, d10c7698]
[null, null, 3b81634e, 278eb15f, 0687f3ba, d10c7698, 884dfd9e, 8e76f7e3, 3ee8aab6, cfc4fe66]
出现了一个ConcurrentModificationException异常,称为并发修改异常。
4.1.1、解决方案:Vector
public class ThreadDemo4
{
public static void main(String[] args)
{
//解决方法:Vector
Vector<String> list = new Vector<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
//向集合中添加元素
list.add(UUID.randomUUID().toString().substring(0,8));
//从集合中获取元素
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
查看Vector的源码:
/**
* Appends the specified element to the end of this Vector.
*
* @param e element to be appended to this Vector
* @return {@code true} (as specified by {@link Collection#add})
* @since 1.2
*/
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
发现添加了synchronized关键字。
4.1.2、解决方案:Collections
public class ThreadDemo4
{
public static void main(String[] args)
{
//解决方法:Collections
List<Object> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 10; i++) {
new Thread(()->{
//向集合中添加元素
list.add(UUID.randomUUID().toString().substring(0,8));
//从集合中获取元素
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
4.1.3、解决方案:CopyOnWriteArrayList
public class ThreadDemo4
{
public static void main(String[] args)
{
//解决方法:CopyOnWriteArrayList
List<Object> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
//向集合中添加元素
list.add(UUID.randomUUID().toString().substring(0,8));
//从集合中获取元素
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
写时复制技术:每次写的时候先复制,然后往里写,再合并!

查看源码:
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
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();
}
}
4.2、HashSet和HashMap线程不安全问题及解决方案
public class ThreadDemo4
{
public static void main(String[] args)
{
Set<String> set = new HashSet<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
//向结合中添加元素
set.add(UUID.randomUUID().toString().substring(0,8));
//从集合中获取内容
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
同样出现了并发修改问题。
使用CopyOnWriteArraySet解决:
public class ThreadDemo4
{
public static void main(String[] args)
{
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
//向结合中添加元素
set.add(UUID.randomUUID().toString().substring(0,8));
//从集合中获取内容
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
public class ThreadDemo4
{
public static void main(String[] args)
{
HashMap<String, String> map = new HashMap<>();
for (int i = 0; i < 30; i++) {
String key = String.valueOf(i);
new Thread(()->{
//向结合中添加元素
map.put(key,UUID.randomUUID().toString().substring(0,8));
//从集合中获取内容
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
解决方法:ConcurrentHashMap
public class ThreadDemo4
{
public static void main(String[] args)
{
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 30; i++) {
String key = String.valueOf(i);
new Thread(()->{
//向结合中添加元素
map.put(key,UUID.randomUUID().toString().substring(0,8));
//从集合中获取内容
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
5、多线程锁
5.1、锁的八个问题演示
定义一个手机Phone类:
public class Phone
{
public synchronized void sendSMS() throws InterruptedException
{
//停留4秒
//TimeUnit.SECONDS.sleep(4);
System.out.println("------------sendSMS");
}
public synchronized void sendEmail(){
System.out.println("------------sendEmail");
}
public void getHello(){
System.out.println("------------getHello");
}
}
在main()方法中依次访问SMS和Email:
class Main{
public static void main(String[] args) throws InterruptedException
{
Phone phone = new Phone();
new Thread(()->{
try{
phone.sendSMS();
}catch (Exception e){
e.printStackTrace();
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try{
//phone.sendEmail();
//phone.getHello();
phone1.sendEmail();
}catch (Exception e){
e.printStackTrace();
}
},"BB").start();
}
}
- 标准访问,先打印短信还是邮件?
------------sendSMS
------------sendEmail - 在短信方法中停4秒,先打印短信还是邮件?
------------sendSMS
------------sendEmail - 新增普通方法hello,先打印短信还是hello?
------------getHello
------------sendSMS - 现有两部手机,先打印短信还是邮件?
------------sendEmail
------------sendSMS - 两个静态同步方法,1部手机,先打印短信还是邮件
------------sendSMS
------------sendEmail - 两个静态同步方法,2部手机,先打印短信还是邮件
------------sendSMS
------------sendEmail - 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
------------sendEmail
------------sendSMS - 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
------------sendEmail
------------sendSMS
结论:
- 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法,其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法。锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法加个普通方法后发现和同步锁无关。
- 换成两个对象后,不是同一把锁了,情况立刻变化。
synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
具体表现为以下 3 种形式:
- 对于普通同步方法,锁是当前实例对象;
- 对于静态同步方法,锁是当前类的Class对象;
- 对于同步方法块,锁是Synchonized括号里配置的对象。
5.2、公平锁和不公平锁
在上面的例子中new一个ReentrantLock锁时,我们使用的是无参构造,查看源码:
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
无参构造直接就是非公平锁,可能出现线程“饿死”的情况。还可以使用有参构造,传入一个参数。
5.3、可重入锁
也叫“递归锁”,在得到最外层的锁后,可以无障碍地进入所有内层锁。

演示可重入锁的特点:
package SynchronizedDemo;
public class SyncLockDemo
{
public synchronized void add(){
add();
}
public static void main(String[] args)
{
SyncLockDemo syncLockDemo = new SyncLockDemo();
//此处会抛出栈溢出的异常
syncLockDemo.add();
Object o = new Object();
new Thread(()->{
synchronized (o){
System.out.println(Thread.currentThread().getName() + " 外层");
synchronized (o){
System.out.println(Thread.currentThread().getName() + " 中层");
synchronized (o){
System.out.println(Thread.currentThread().getName() + " 内层");
}
}
}
},"t1").start();
}
}
如果不是“可重入”,那么就不可能访问到“中层”和“内层”;同理,也不可能可以递归调用add()方法。
package SynchronizedDemo;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SyncLockDemo
{
public static void main(String[] args)
{
//lock演示可重入锁
Lock lock = new ReentrantLock();
//创建线程
new Thread(()->{
try{
//上锁
lock.lock();
System.out.println(Thread.currentThread().getName()+"外层");
try{
//上锁
lock.lock();
System.out.println(Thread.currentThread().getName()+"内层");
}finally {
lock.unlock();
}
}finally {
lock.unlock();
}
},"t1").start();
}
}
程序可以正常执行。
演示另一种情况,如果我把内层不解锁呢?
package SynchronizedDemo;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SyncLockDemo
{
public synchronized void add(){
add();
}
public static void main(String[] args)
{
//lock演示可重入锁
Lock lock = new ReentrantLock();
//创建线程
new Thread(()->{
try{
//上锁
lock.lock();
System.out.println(Thread.currentThread().getName()+"外层");
try{
//上锁
lock.lock();
System.out.println(Thread.currentThread().getName()+"内层");
}finally {
//lock.unlock();
}
}finally {
lock.unlock();
}
},"t1").start();
new Thread(()->{
lock.lock();
System.out.println("aaaaa");
lock.unlock();
},"aa").start();
}
}

如果没有释放锁,对自己没有影响。但是别的线程则无法获得锁,别的线程无法继续执行下去!
5.4、死锁

package SynchronizedDemo;
import java.util.concurrent.TimeUnit;
public class DeadLock
{
//创建两个对象
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args)
{
new Thread(()->{
synchronized (a){
System.out.println(Thread.currentThread().getName()+" 持有锁a,试图获取锁b");
try {
TimeUnit.SECONDS.sleep(1);
}
catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b){
System.out.println(Thread.currentThread().getName()+" 获取锁b");
}
}
},"A").start();
new Thread(()->{
synchronized (b){
System.out.println(Thread.currentThread().getName()+" 持有锁b,试图获取锁a");
try {
TimeUnit.SECONDS.sleep(1);
}
catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a){
System.out.println(Thread.currentThread().getName()+" 获取锁a");
}
}
},"B").start();
}
}
6、Callable接口

创建线程的多种方式:
- 继承Thread类;
- 实现Runnable接口;
- 实现Callable接口;
- 线程池。
Runnable接口和Callable接口的区别:
- 是否有返回值;Callable接口有返回值,Runnable接口没有返回值;
- 是否抛出异常;Callable可以抛出异常,Runnable没有异常;
- 实现方法名称不同,一个run()方法,一个是call()方法。
6.1、Callable接口创建线程的方式
那么如何使用Callable接口创建一个线程呢?
//Callable接口创建一个线程,报错
//new Thread(new MyThred2(),"BB").start();

当call()方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。为此,可以使用Future对象。
将Future视为保存结果的对象–它可能暂时不保存结果,但将来会保存(一旦Callable 返回)。 Future基本上是主线程可以跟踪进度以及其他线程的结果的一种方式。要实现此接口,必须重写5种方法,这里列出了重要的方法,如下:
- public boolean cancel(boolean mayInterrupt): 用于停止任务;
- public Object get()抛出 InterruptedException, ExecutionException:用于获取任务的结果;
- public boolean isDone(): 如果任务完成,则返回 true,否则返回 false。
Java库具有具体的FutureTask类型,该类型实现Runnable和Future,并方便地将两种功能组合在一起。可以通过为其构造函数提供Callable来创建FutureTask。然后,将FutureTask对象提供给Thread的构造函数以创建Thread对象。因此,间接地使用Callable创建线程。
核心重点:
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成:
• 当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态;
• 一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果;
• 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞get方法;
• 一旦计算完成,就不能再重新开始或取消计算;
• get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常;
• get只计算一次,因此get方法放到最后。
package callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//比较两个接口
//实现Runnable接口
class MyThred1 implements Runnable{
@Override
public void run()
{
}
}
//实现Callable接口
class MyThread2 implements Callable{
@Override
public Integer call() throws Exception
{
System.out.println(Thread.currentThread().getName() +" come in callable");
return 200;
}
}
public class Demo1
{
public static void main(String[] args) throws ExecutionException, InterruptedException
{
//Runnable接口创建一个线程
new Thread(new MyThred1(),"AA").start();
//Callable接口创建一个线程,报错
//new Thread(new MyThred2(),"BB").start();
//FutureTask
FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());
FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() +" come in callable");
return 1024;
});
//创建线程
new Thread(futureTask2,"lucy").start();
new Thread(futureTask1,"mary").start();
while (!futureTask2.isDone()){
System.out.println("wait.....");
}
//调用FutureTask的get方法
System.out.println(futureTask2.get());
System.out.println(futureTask2.get());
System.out.println(Thread.currentThread().getName() +" over");
}
}
7、JUC辅助类
JUC中提供了三种常用的辅助类,通过这些辅助类可以很好的解决线程数量过多时Lock锁的频繁操作。这三种辅助类为:
- CountDownLatch:减少计数
- CyclicBarrier:循环栅栏
- Semaphore:信号灯
7.1、减少计数 CountDownLatch
CountDownLatch类可以设置一个计数器,然后通过countDown方法来进行减1的操作,使用await()方法等待计数器不大于0,然后继续执行await方法之后的语句。
- CountDownLatch主要有两个方法,当一个或多个线程调用await()方法时,这些线程会阻塞
- 其它线程调用countDown方法会将计数器减1(调用countDown()方法的线程不会阻塞)
- 当计数器的值变为0时,因await()方法阻塞的线程会被唤醒,继续执行
public class CountDownLatchDemo
{
//6个同学陆续离开教室之后,班长才可以锁门
public static void main(String[] args)
{
//6个同学陆续离开教室
for (int i = 1; i <= 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + " 号同学离开了教室");
},String.valueOf(i)).start();
}
System.out.println(Thread.currentThread().getName() + " 班长锁门走人了");
}
}
main 班长锁门走人了
4 号同学离开了教室
3 号同学离开了教室
1 号同学离开了教室
2 号同学离开了教室
6 号同学离开了教室
5 号同学离开了教室
使用CountDownLatch方法:
public class CountDownLatchDemo
{
//6个同学陆续离开教室之后,班长才可以锁门
public static void main(String[] args) throws InterruptedException
{
//1、创建CountDownLatch对象,设置初始值
CountDownLatch countDownLatch = new CountDownLatch(6);
//6个同学陆续离开教室
for (int i = 1; i <= 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + " 号同学离开了教室");
//2、计数 -1
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//3、等待(阻塞)
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + " 班长锁门走人了");
}
}
1 号同学离开了教室
6 号同学离开了教室
5 号同学离开了教室
3 号同学离开了教室
4 号同学离开了教室
2 号同学离开了教室
main 班长锁门走人了
7.2、循环栅栏 CyclicBarrier
CyclicBarrier看英文单词可以看出大概就是循环阻塞的意思,在使用中CyclicBarrier的构造方法第一个参数是目标障碍数,每次执行CyclicBarrier一次障碍数会加一,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后的语句。可以将CyclicBarrier理解为加1操作。
public class CyclicBarrierDemo
{
//创建固定值
private static final int NUMBER = 7;
public static void main(String[] args)
{
//创建CyclicBarrier
CyclicBarrier cyclicBarrier =
new CyclicBarrier(NUMBER,()->{
System.out.println("集齐7颗龙珠就可以召唤神龙");
});
//集齐7颗龙珠的过程
for (int i = 1; i <= 7; i++) {
new Thread(()->{
try{
System.out.println(Thread.currentThread().getName() + " 星龙珠收集到了");
//等待
cyclicBarrier.await();
}catch (Exception e){
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
1 星龙珠收集到了
5 星龙珠收集到了
6 星龙珠收集到了
3 星龙珠收集到了
2 星龙珠收集到了
7 星龙珠收集到了
4 星龙珠收集到了
集齐7颗龙珠就可以召唤神龙
7.3、信号灯Semaphore
Semaphore的构造方法中传入的第一个参数是最大信号量(可以看成最大线程池),每个信号量初始化为一个最多只能分发一个许可证。使用acquire方法获得许可证,release方法释放许可。
//抢车位, 6部汽车 3个停车位
public class SemaphoreDemo
{
public static void main(String[] args)
{
//创建Semaphore,设置许可证
Semaphore semaphore = new Semaphore(3);
//模拟6辆汽车
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
//抢占
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 抢到了车位");
//设置随机的停车时间
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println(Thread.currentThread().getName() + " ---------离开了车位");
}
catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
8、读写锁
悲观锁和乐观锁:

表锁和行锁以及读锁(共享锁)和写锁(独占锁):

现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。
针对这种场景,JAVA的并发包提供了读写锁ReentrantReadWriteLock,它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁。
- 线程进入读锁的前提条件:
没有其他线程的写锁
没有写请求,或者有写请求,但调用线程和持有锁的线程是同一个(可重入锁) - 线程进入写锁的前提条件:
没有其他线程的读锁
没有其他线程的写锁
而读写锁有以下三个重要的特性:
- 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平;
- 重进入:读锁和写锁都支持线程重进入;
- 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
class MyCache{
//创建map集合
private Map<String,Object> map = new HashMap<>();
//向map中放数据
public void put(String key, Object value){
System.out.println(Thread.currentThread().getName() + " 正在写操作" + key);
//暂停一会
try {
TimeUnit.MILLISECONDS.sleep(300);
}
catch (InterruptedException e) {
e.printStackTrace();
}
//放数据
map.put(key,value);
System.out.println(Thread.currentThread().getName() + " 写完了" + key);
}
//从map中取数据
public Object get(String key){
System.out.println(Thread.currentThread().getName() + " 正在读取操作" + key);
//暂停一会
try {
TimeUnit.MILLISECONDS.sleep(300);
}
catch (InterruptedException e) {
e.printStackTrace();
}
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + " 取完了" + key);
return result;
}
}
public class ReadWriteLockDemo
{
public static void main(String[] args)
{
MyCache myCache = new MyCache();
//创建线程放数据
for (int i = 1; i <= 5; i++) {
final int num = i;
new Thread(() -> {
myCache.put(num+"",num+"");
},String.valueOf(i)).start();
}
//创建线程取数据
for (int i = 1; i <= 5; i++) {
final int num = i;
new Thread(() -> {
myCache.get(num+"");
},String.valueOf(i)).start();
}
}
}
1 正在写操作1
5 正在写操作5
4 正在写操作4
3 正在写操作3
2 正在写操作2
1 正在读取操作1
2 正在读取操作2
3 正在读取操作3
4 正在读取操作4
5 正在读取操作5
3 写完了3
1 写完了1
3 取完了3
5 写完了5
1 取完了1
5 取完了5
4 写完了4
2 写完了2
4 取完了4
2 取完了2
正常情况下,写完了才可以读。
package readwrite;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
//资源类
class MyCache{
//创建map集合
private Map<String,Object> map = new HashMap<>();
//创建读写锁的对象
private ReadWriteLock rwlock = new ReentrantReadWriteLock();
//向map中放数据
public void put(String key, Object value){
//添加写锁
rwlock.writeLock().lock();
//暂停一会
try {
System.out.println(Thread.currentThread().getName() + " 正在写操作" + key);
TimeUnit.MILLISECONDS.sleep(300);
//放数据
map.put(key,value);
System.out.println(Thread.currentThread().getName() + " 写完了" + key);
}
catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放写锁
rwlock.writeLock().unlock();
}
}
//从map中取数据
public Object get(String key){
Object result = null;
//添加读锁
rwlock.readLock().lock();
//暂停一会
try {
System.out.println(Thread.currentThread().getName() + " 正在读取操作" + key);
TimeUnit.MILLISECONDS.sleep(300);
result = map.get(key);
System.out.println(Thread.currentThread().getName() + " 取完了" + key);
}
catch (InterruptedException e) {
e.printStackTrace();
}finally {
rwlock.readLock().unlock();
}
return result;
}
}
public class ReadWriteLockDemo
{
public static void main(String[] args)
{
MyCache myCache = new MyCache();
//创建线程放数据
for (int i = 1; i <= 5; i++) {
final int num = i;
new Thread(() -> {
myCache.put(num+"",num+"");
},String.valueOf(i)).start();
}
//创建线程取数据
for (int i = 1; i <= 5; i++) {
final int num = i;
new Thread(() -> {
myCache.get(num+"");
},String.valueOf(i)).start();
}
}
}
1 正在写操作1
1 写完了1
2 正在写操作2
2 写完了2
3 正在写操作3
3 写完了3
4 正在写操作4
4 写完了4
4 写完了4
5 正在写操作5
5 写完了5
1 正在读取操作1
2 正在读取操作2
3 正在读取操作3
5 正在读取操作5
4 正在读取操作4
4 取完了4
3 取完了3
1 取完了1
2 取完了2
5 取完了5
8.1、读写锁的演变

8.2、读写锁的降级

public class Demo1
{
public static void main(String[] args)
{
//可重入读写锁的对象
ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = rwlock.readLock(); //读锁
ReentrantReadWriteLock.WriteLock writeLock = rwlock.writeLock(); //写锁
//锁降级
//1 获取到写锁
writeLock.lock();
System.out.println("atguigu");
//2 获取到读锁
readLock.lock();
System.out.println("----read");
//3 释放写锁
writeLock.unlock();
//4 释放读锁
readLock.unlock();
}
}
9、阻塞队列
9.1、BlockingQueue简介
阻塞队列,顾名思义,首先它是一个队列,通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出;

在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起。
为什么需要BlockingQueue?
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了。
在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。
多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者” 和“消费者” 模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他们之间的数据共享问题。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然。
- 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列
- 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒
9.2、常见的BlockingQueue
- ArrayBlockingQueue(常用)
由数组结构组成的有界阻塞队列 - LinkedBlockingQueue(常用)
由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列 - DelayQueue
使用优先级队列实现的延迟无界阻塞队列 - PriorityBlockingQueue
支持优先级排序的无界阻塞队列 - SynchronousQueue
不存储元素的阻塞队列,也即单个元素的队列 - LinkedTransferQueue
由链表组成的无界阻塞队列 - LinkedBlockingDeque
由链表组成的双向阻塞队列
9.3、BlockingQueue核心方法

public class BlockingQueueDemo
{
public static void main(String[] args) throws InterruptedException
{
//创建阻塞队列
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue(3);
//第一组
System.out.println(blockingQueue.add("a")); //true
System.out.println(blockingQueue.add("b")); //true
System.out.println(blockingQueue.add("c")); //true
System.out.println(blockingQueue.element()); //a
/*
Exception in thread "main" java.lang.IllegalStateException: Queue full
*/
//System.out.println(blockingQueue.add("w"));
System.out.println(blockingQueue.remove()); //a
System.out.println(blockingQueue.remove()); //b
System.out.println(blockingQueue.remove()); //c
/*
Exception in thread "main" java.util.NoSuchElementException
*/
//System.out.println(blockingQueue.remove());
//第二组
System.out.println(blockingQueue.offer("a")); //true
System.out.println(blockingQueue.offer("b")); //true
System.out.println(blockingQueue.offer("c")); //true
System.out.println(blockingQueue.offer("ww")); //false
System.out.println(blockingQueue.poll()); //a
System.out.println(blockingQueue.poll()); //b
System.out.println(blockingQueue.poll()); //c
System.out.println(blockingQueue.poll()); //null
//第三组
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//队列没有空间会一直阻塞
//blockingQueue.put("w");
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"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
//会阻塞,超过3秒,会结束
System.out.println(blockingQueue.offer("w", 3L, TimeUnit.SECONDS));
}
}
10、线程池
线程池的优势: 线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
线程池的特点:
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的销耗;
- 提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行;
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控;
- Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类。

10.1、线程池的种类与创建
10.1.1、newCachedThreadPool(常用,线程池根据需求创建线程,可扩容)
作用:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
特点:
- 线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
- 线程池中的线程可进行缓存重复利用和回收(回收默认时间为1分钟)
- 当线程池中,没有可用线程,会重新创建一个线程
场景:适用于创建一个可无限扩大的线程池,服务器负载压力较轻,执行时间较短,任务多的场景。
10.1.2、newFixedThreadPool(常用,一池N线程)
作用:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。
特点:
- 线程池中的线程处于一定的量,可以很好的控制线程的并发量
- 线程可以重复被使用,在显示关闭之前,都将一直存在
- 超出一定量的线程被提交时候需在队列中等待
场景:适用于可以预测线程数量的业务中,或者服务器负载较重,对线程数有严格限制的场景。
10.1.3、newSingleThreadExecutor(常用,一个任务一个任务执行,一池一线程)
作用:创建一个使用单个worker线程的Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的newFixedThreadPool不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。
特点:
- 线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
场景:适用于需要保证顺序执行各个任务,并且在任意时间点,不会同时有多个线程的场景
public class ThreadPoolDemo1
{
public static void main(String[] args)
{
//演示三种常见的线程池分类
//1 一池N线程
//ExecutorService threadPool1 = Executors.newFixedThreadPool(5); //5个窗口
//2 一池一线程
//ExecutorService threadPool1 = Executors.newSingleThreadExecutor(); //1个窗口
//3 一池可扩容线程
ExecutorService threadPool1 = Executors.newCachedThreadPool(); //可扩容
//10个顾客请求
try{
for (int i = 1; i <= 10; i++) {
//执行
threadPool1.execute(()->{
System.out.println(Thread.currentThread().getName() + " 办理业务");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool1.shutdown();
}
}
}
打开源码查看这三个方法:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以发现都是使用的new ThreadPoolExecutor()方法。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
一共有7个参数。
10.2、ThreadPoolExecutor构造方法的七个参数

10.3、工作流程和拒绝策略

- 在创建了线程池后,线程池中的线程数为零
- 当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
2.1 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
2.2 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入阻塞队列;
2.3 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
2.4 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。 - 当一个线程完成任务时,它会从队列中取下一个任务来执行
- 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
4.1 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
4.2 所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。

- CallerRunsPolicy:当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大
- AbortPolicy:丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行
- DiscardPolicy:直接丢弃,其他啥都没有
- DiscardOldestPolicy:当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列workQueue中最老的一个任务,并将新任务加入
10.4、自定义线程池
public class ThreadPoolDemo2
{
public static void main(String[] args)
{
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2,
5,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
//10个顾客请求
try {
for (int i = 1; i <= 10; i++) {
//执行
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 办理业务");
});
}
}
catch (
Exception e) {
e.printStackTrace();
}
finally {
threadPool.shutdown();
}
}
}
11、Fork/Join分支合并框架
Fork/Join它可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出。Fork/Join框架要完成两件事情:
- Fork:把一个复杂任务进行分拆,大事化小
- Join:把分拆任务的结果进行合并

在Java的Fork/Join框架中,使用两个类完成上述操作:
- ForkJoinTask:我们要使用Fork/Join框架,首先需要创建一个ForkJoin任务。该类提供了在任务中执行fork和join的机制。通常情况下我们不需要直接集成 ForkJoinTask类,只需要继承它的子类,Fork/Join框架提供了两个子类:
1、RecursiveAction:用于没有返回结果的任务
2、RecursiveTask:用于有返回结果的任务 - ForkJoinPool:ForkJoinTask需要通过ForkJoinPool来执行
- RecursiveTask:继承后可以实现递归(自己调自己)调用的任务
class MyTask extends RecursiveTask<Integer>
{
//拆分差值不能超过10
private static final Integer VALUE = 10;
private int begin; //拆分开始值
private int end; //拆分结束值
private int result; //返回结果值
//创建有参数构造
public MyTask(int begin, int end){
this.begin = begin;
this.end = end;
}
//拆分和合并的过程
@Override
protected Integer compute()
{
//判断相加的两个值是否大于10
if (end-begin <= 10){
//相加操作
for (int i = begin; i <= end; i++) {
result += i;
}
} else {
//进一步拆分
//获取数据的中间值
int middle = (begin+end)/2;
//拆分左边部分
MyTask task01 = new MyTask(begin, middle);
//拆分右边部分
MyTask task02 = new MyTask(middle+1, end);
task01.fork();
task02.fork();
//合并结果
result = task01.join() + task02.join();
}
return result;
}
}
public class ForkJoinDemo1
{
public static void main(String[] args) throws ExecutionException, InterruptedException
{
//创建MyTask对象
MyTask myTask = new MyTask(0, 100);
//创建分支合并池对象
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(myTask);
//获取最终合并之后结果
Integer result = forkJoinTask.get();
System.out.println(result);
//关闭池对象
forkJoinPool.shutdown();
}
}
11.1、Fork/Join 框架的实现原理
ForkJoinPool由ForkJoinTask数组和ForkJoinWorkerThread数组组成,ForkJoinTask数组负责将存放以及将程序提交给ForkJoinPool,而ForkJoinWorkerThread负责执行这些任务。


12、异步回调
public class CompletableFutureDemo
{
public static void main(String[] args) throws ExecutionException, InterruptedException
{
//异步调用没有返回值的
CompletableFuture<Void> completableFuture1 = CompletableFuture.runAsync(()->{
System.out.println(Thread.currentThread().getName() + " completableFuture1");
});
completableFuture1.get();
//异步调用有返回值的
CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + " completableFuture2");
///模拟异常
int i = 10/0;
return 1024;
});
completableFuture2.whenComplete((t,u)->{
System.out.println("----t="+t); //----t=1024 方法返回值
//----u=java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero 异常
System.out.println("----u="+u);
}).get();
}
}