JUC并发学习
JUC 并发学习
1、什么是JUC
源码+官方文档 面试高频问

java.util 工具包、包、分类
业务:普通的线程代码 Thread
Runnable 没有返回值
2、线程与进程
线程、进程,没有不能使用一句话说出来的技术,不扎实!
进程:一个程序,QQ.exe 程序的集合
一个进程往往可以包含多个线程,至少包含一个!
Java默认有几个线程? 2个 main、GC(垃圾回收)
线程:开了一个进程Typora,写字,自动保存(线程负责的)
对于Java而言:Thread、Runnable、Callable
Java真的可以开启线程吗? 开不了
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
// 有native修饰的方法是本地方法,底层C++,java无法直接操控硬件,因为java是运行在虚拟机上的
private native void start0();
并行,并发
并发编程:并发、并行
并发(多线程操作同一个资源)
- CPU一核,模拟出来多条线程,天下武功,为快不破,快速交替
并行(多个人一起行走)
- CUP多核,多个线程可以同时执行;线程池
public static void main(String[] args) {
// 获取cpu的核数
// CPU 密集型, IO 密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
并发编程的本质:充分利用CPU的资源
所有的公司都很看重!
线程有几个状态
public enum State {
// 新生
NEW,
// 运行
RUNNABLE,
// 堵塞
BLOCKED,
// 等待,死死的等
WAITING,
// 超时等待
TIMED_WAITING,
// 终止
TERMINATED;
}
wait/sleep 区别
1、来自不同的类
wait=>Object
sleep=>Thread
2、关于锁的释放
wait 会释放锁,sleep 不会释放锁!
3、使用的范围是不同的
wait:wait必须在同步代码块中
sleep 可以在任何地方睡
4、是否需要捕获异常
wait不需要捕获异常
3、Lock锁(重点)
传统Synchronized
package com.company.project.test;
/**
* @Author: gcl
* @create: 2020/11/30 20:50
*/
// 基本的买票例子
/**
* 真正的多线程开发,公司中的开发,降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作哦
* 1、属性、方法
*/
public class ThreadTest extends Thread {
// 并发,多线程操作同一个资源类,把资源类丢进线程
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale();}, "A").start();
new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale();}, "B").start();
new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale();}, "C").start();
}
}
// 资源类 OOP
class Ticket {
private int number = 30;
public synchronized void sale() {
if (number>0)
System.out.println(Thread.currentThread().getName() + "剩余" + number-- + "票。");
}
}
执行结果
// 可以看到从头到尾都是B线程在跑
...
B剩余9票。
B剩余8票。
B剩余7票。
B剩余6票。
B剩余5票。
B剩余4票。
B剩余3票。
B剩余2票。
B剩余1票。
Disconnected from the target VM, address: '127.0.0.1:14265', transport: 'socket'
Lock接口


公平锁:十分公平,可以先来后到
非公平锁:十分不公平,可以插队(默认)
为什么Lock与Synchronized默认都是非公平锁?
假如一个线程3h完成,一个线程3s完成,3h的线程在3s的前面,这时候如果是公平锁的话,3s的要等待3h之后才能执行!
public ReentrantLock() {
sync = new NonfairSync(); // 默认非公平锁
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) { // 有参构造可以转变成公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
package com.company.project.test;
/**
* @Author: gcl
* @create: 2020/11/30 20:50
*/
// 基本的买票例子
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 真正的多线程开发,公司中的开发,降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作哦
* 1、属性、方法
*/
public class ThreadTest extends Thread {
// 并发,多线程操作同一个资源类,把资源类丢进线程
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale();}, "A").start();
new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale();}, "B").start();
new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale();}, "C").start();
}
}
/**
* lock三部曲
* 1、new ReentrantLock();
* 2、lock.lock(); // 加锁
* 3、finally=> lock.unlock(); // 解锁
*/
class Ticket {
// 属性、方法
private int number = 30;
Lock lock = new ReentrantLock();
public void sale() {
lock.lock(); // 加锁
try {
if (number>0)
System.out.println(Thread.currentThread().getName() + "剩余" + number-- + "票。");
}catch (Exception e){
e.printStackTrace();
}finally {
// 解锁
lock.unlock();
}
}
}
执行效果
...
A剩余11票。
A剩余10票。
A剩余9票。
A剩余8票。
A剩余7票。
A剩余6票。
A剩余5票。
A剩余4票。
A剩余3票。
A剩余2票。
A剩余1票。
Disconnected from the target VM, address: '127.0.0.1:14389', transport: 'socket'
Synchronized 和 Lock 区别
1、Synchronized 内置的Java关键字,Lock是一个Java类
2、Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取了锁
3、Synchronized 会释放锁,lock必须要手动释放锁!如果不释放,死锁
4、Synchronized 线程1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去;
5、Synchronized 可重入锁,不可以中断的,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置);
6、Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码!
锁是什么,如何判断锁的是谁?
4、生产者、消费者问题
示例代码:
package com.company.project.practice;
/**
* @Author: gcl
* @create: 2020/12/13 19:12
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
// 判断等待,业务,通知
class Data { // 数字 资源类
private int number = 0;
// +1
public synchronized void increment() throws InterruptedException {
if (number != 0) {
// 等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,我+1完毕了
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
if (number == 0) {
// 等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,我-1完毕了
this.notifyAll();
}
}
运行结果:
B=>1
A=>0
B=>1
A=>0
B=>1
A=>0
B=>1
A=>0
B=>1
A=>0
B=>1
A=>0
B=>1
A=>0
B=>1
A=>0
B=>1
A=>0
B=>1
A=>0
问题存在:如果是四个线程那还安全吗?
新增两个线程
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();

看到程序不仅数值异常,并且出现了死锁的情况
原因如下:

所以在Data中将if换成while即可
while (number == 0) {
// 等待
this.wait();
}
使用Lock锁实现生产、消费
package com.company.project.practice;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: gcl
* @create: 2020/12/13 19:12
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
// 判断等待,业务,通知
class Data { // 数字 资源类
private int number = 0;
Lock lock = new ReentrantLock(); // 1
Condition condition = lock.newCondition(); // 2
// condition.await(); 等待
// condition.signalAll(); 喚醒全部
// +1
public void increment() throws InterruptedException {
lock.lock(); // 3
try {
while (number != 0) {
// 等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,我+1完毕了
condition.signalAll(); // 4
}catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); // 5
}
}
// -1
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number == 0) {
// 等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,我+1完毕了
condition.signalAll();
}catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
任何一个新的技术,绝不仅仅只是覆盖了原来的技,优势和补充!
Condition 精准的通知和唤醒线程

解决代码:
package com.company.project.practice;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: gcl
* @create: 2020/12/13 19:12
*/
public class C {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printC();
}
},"C").start();
}
}
// 判断等待,业务,通知
class Data3 { // 数字 资源类
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1; // 1A 2B 3C
public void printA() {
lock.lock();
try {
// 业务,判断-> 执行 -> 通知
while (number != 1) {
condition1.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>AAAAAA");
condition2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
// 业务,判断-> 执行 -> 通知
while (number != 2) {
condition2.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>BBBBBB");
condition3.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
// 业务,判断-> 执行 -> 通知
while (number != 3) {
condition3.await();
}
number = 1;
System.out.println(Thread.currentThread().getName() + "=>CCCCCCCC");
condition1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}

5、线程八锁问题
如何判断锁的是谁!永远的知道是什么是锁,锁到底锁的是谁!
对象,Class
synchronized锁的对象是调用
package com.company.project;
/*
* 题目:判断打印的 "one" or "two" ?
*
* 1. 两个普通同步方法,两个线程,标准打印, 打印? //one two
* 2. 新增 Thread.sleep() 给 getOne() ,打印? //one two
* 3. 新增普通方法 getThree() , 打印? //three one two
* 4. 两个普通同步方法,两个 Number 对象,打印? //two one
* 5. 修改 getOne() 为静态同步方法,打印? //two one
* 6. 修改两个方法均为静态同步方法,一个 Number 对象? //one two
* 7. 一个静态同步方法,一个非静态同步方法,两个 Number 对象? //two one
* 8. 两个静态同步方法,两个 Number 对象? //one two
*
* 线程八锁的关键:
* ①非静态方法的锁默认为 this, 静态方法的锁为 对应的 Class 实例
* ②某一个时刻内,只能有一个线程持有锁,无论几个方法。
*/
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number2 = new Number();
new Thread(() -> {
Number.getOne();
}).start();
new Thread(() -> {
number2.getTwo();
}).start();
/*new Thread(new Runnable() {
@Override
public void run() {
number.getThree();
}
}).start();*/
}
}
class Number{
public static synchronized void getOne(){//Number.class
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println("one");
}
public synchronized void getTwo(){//this
System.out.println("two");
}
public void getThree(){
System.out.println("three");
}
}
6、集合类不安全
List
List不安全
package com.company.project.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author gaocl4
* @date 2020/12/15 12:00
*/
// Exception in thread "0" java.util.ConcurrentModificationException
public class ListTest {
public static void main(String[] args) {
// 并发下 ArrayList 不安全的吗,Synchronized
/**
* 解决方法:
* 1、List<String> list = new Vector<>(); //可以进入add方法中看到
* 这里的add是由synchronized修饰的
* public synchronized boolean add(E e) {
* modCount++;
* ensureCapacityHelper(elementCount + 1);
* elementData[elementCount++] = e;
* return true;
* }
*
* 2、List<String> list = Collections.synchronizedList(new ArrayList<>());
*
* 3、List<String> list = new CopyOnWriteArrayList<>();
* 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();
* }
* }
*
*/
List<String> list = new ArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list.toString());
}, String.valueOf(i)).start();
}
}
}
下面是普通的ArrayList()

可以看到会报 并发修改异常
Exception in thread "22" java.util.ConcurrentModificationException
Set
Set集合不安全
package com.company.project.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.stream.Collectors;
/**
* @author gaocl4
* @date 2020/12/15 12:00
*/
// Exception in thread "0" java.util.ConcurrentModificationException
public class SetTest {
public static void main(String[] args) {
// Set<String> list = new HashSet<>();
// Set<String> list = Collections.synchronizedSet(new HashSet<>());
Set<String> list = new CopyOnWriteArraySet<>();
for (int i = 0; i < 50; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list.toString());
}, String.valueOf(i)).start();
}
}
}
hashSet 底层是什么?
进入底层查看
public HashSet() { map = new HashMap<>(); }可以看到,底层是new了一个hashMap
public boolean add(E e) { return map.put(e, PRESENT)==null; }add方法是将Map的key存进集合中
Map
Map 集合不安全
HashMap:

查看Java Api

public class MapTest {
public static void main(String[] args) {
Map<String,String> list = new ConcurrentHashMap<>();
for (int i = 0; i < 50; i++) {
new Thread(() -> {
list.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(list.toString());
}, String.valueOf(i)).start();
}
}
}
7、Callable(简单)

以前的类实现线程
public class CallableTest {
public static void main(String[] args) {
new Thread(new Thread()).start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
}
}
JUC中是使用Callable接口实现,为什么?
查看API,可以看到Runablle中有一个FutureTask的实现类

在FutrueTask的构造方法中可以看到

// 实例:不指定返回类型
class MyThread implements Callable{
@Override
public Object call() throws Exception {
return null;
}
}
// 实例:指定返回类型,在实现Callable接口时,可以通过Callable<Type>,在Type中指定返回的类型
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("call me");
return 1024;
}
}
实测:
public class CallableTest {
public static void main(String[] args) {
// new Thread(new Thread()).start();
new Thread(new FutureTask<>(new MyThread())).start();
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("call me");
return 1024;
}
}
结果:
call me
亦可写成
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// new Thread(new Thread()).start();
MyThread thread = new MyThread();
// 适配类
FutureTask futureTask = new FutureTask(thread);
new Thread(futureTask, "testThread").start();
// 获取返回值, 这个get方法可能会产生阻塞!
Integer o = (Integer) futureTask.get();
System.out.println(o);
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("call me");
// 耗时的操作
return 1024;
}
}
结果
call me
1024
细节:
- 有缓存
- 结果可能需要等待,会阻塞!
8、常用的辅助类
8.1、CountDownLatch

实例:
package com.company.project.utils;
import java.util.concurrent.CountDownLatch;
/**
* @author gaocl4
* @date 2020/12/15 15:44
*/
// 计数器
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 总数是六,必须要执行任务的时候,在使用!
CountDownLatch countDownLatch = new CountDownLatch(7);
for (int i = 0; i < 7; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "Go out");
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
// countDownLatch.await(); // 等待计数器归零,然后再向下执行
System.out.println("Close Door");
}
}
Close Door
0Go out
1Go out
2Go out
3Go out
4Go out
5Go out
6Go out
可以看到,如果注掉countDownLatch.await();线程全部自由执行
如果打开countDownLatch.await();
1Go out
0Go out
3Go out
4Go out
2Go out
5Go out
6Go out
Close Door
原理:
countDownLatch.countDown(); // 数量减1
countDownLatch.await(); // 等待计数器归零,然后再向下执行
每次有线程调用 countDown() 数量-1, 假设计数器变为零,countDownLatch.await() 就会被唤醒,继续执行!
8.2、CyclicBarrier

加法计数器
/**
* @author gaocl4
* @date 2020/12/15 16:08
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召喚神竜!");
});
for (int i = 1; i <= 7; i++) {
final int temp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "收集到了" + temp + "星球!");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Thread-0收集到了1星球!
Thread-1收集到了2星球!
Thread-2收集到了3星球!
Thread-3收集到了4星球!
Thread-4收集到了5星球!
Thread-5收集到了6星球!
Thread-6收集到了7星球!
召喚神竜!
如果是CyclicBarrier在构造方法传的时候是8的话,就得等8个线程才释放

这样程序就会一直等待下一个线程。
8.3、Semaphore

实例代码:
// 停车位场景,限流!
public class SemaphoredDemo {
public static void main(String[] args) {
// 线程数量,停车位!限流!
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
// acquire() 得到
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到车位!");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "离开车位!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // release() 释放
}
}, String.valueOf(i)).start();
}
}
}
0抢到车位!
1抢到车位!
2抢到车位!
0离开车位!
3抢到车位!
1离开车位!
4抢到车位!
2离开车位!
5抢到车位!
3离开车位!
5离开车位!
4离开车位!
原理:
semaphore.acquire(); 获得,假设如果已经满了,等待,等到被释放为止!
semaphore.release(); 释放,会将当前的信号量释放+1,然后唤醒等待的线程!
作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!

浙公网安备 33010602011771号