1、什么是JUC
java.util 工具包、包、分类
2、线程和进程
进程是操作系统中的应用程序,是资源分配的基本单位,线程是用来执行具体的任务和功能,是CPU调度和分派的最小单位。
一个进程往往可以包含多个线程,至少包含一个。
对于Java而言:Thread、Runable、Callable进行开启线程的。
Runnable 没有返回值,企业中使用Callable
java默认有几个线程?
- java默认有两个线程:main、GC
java真的可以开启线程吗? 开不了
public class Test0 {
public static void main(String[] args) {
//查看线程启动,点击start查看
new Thread().start();
}
}
//java源码:调用native方法(本地方法栈的C++方法),java运行在虚拟机之上,无法直接操作硬件,由C++开启多线程。
private native void start0();
并发、并行
并发编程:并发、并行
并发(多线程操作同一个资源)
- CPU一核,模拟出来多条线程,快速交替。
并行(多个人一起行走)
- CPU多核,多个线程可以同时执行;线程池。
并发编程的本质:充分利用CPU的资源
package main.java.com.syzd.cpu;
public class Test1 {
public static void main(String[] args) {
// 获取cpu的核数
// CPU 密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
线程有几个状态
package main.java.com.syzd.cpu;
public class Test1 {
public static void main(String[] args) {
//查看线程状态,恩住ctrl,点State
Thread.State
}
}
//线程的状态:6个
public enum State {
//就绪
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待(死死的等)
WAITING,
//超时等待(超过一定时间,不再等待)
TIMED_WAITING,
//终止
TERMINATED;
}
wait于sleep的区别
- 来自不同的类
- wait=>Object
- sleep=>Thread
- wait释放锁,sleep不释放锁(sleep抱着锁睡觉)
- wait必须在同步代码块中,sleep可以在任何地方(sleep可以在任何地方睡觉)
- wait不需要捕获异常,sleep需要捕获异常(可能发生超时等待)
3、Lock锁(重点)
3.1、传统 Synchronized
public class SaleTicket {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源类丢入线程
Ticket ticket = new Ticket();
// @FunctionalInterface 函数式接口,jdk1.8 lambda表达式 (参数)->{ 代码 }
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;
//买票的方式
//synchronized 本质:队列,锁
public synchronized void sale(){
if (number > 0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余:"+number);
}
}
}
3.2、Lock 接口
public class LockTicket {
public static void main(String[] args) {
//资源类
Ticket2 ticket2 = new Ticket2();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket2.sale();
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket2.sale();
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket2.sale();
}
},"C").start();
}
}
//lock三部曲
//1、 Lock lock=new ReentrantLock();
//2、 lock.lock() 加锁
//3、 finally=> 解锁:lock.unlock();
class Ticket2{
//属性,方法
private int number = 30;
//创建锁
Lock lock = new ReentrantLock();
//买票的方式
public void sale(){
//开启锁
lock.lock();
try {
if (number > 0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余:"+number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
}
3.3、Synchronized和Lock区别
4、生产者和消费者的关系(面试:单例模式、排序算法、生产者和消费者、死锁。)
4.1、Synchronized版本
package main.java.com.syzd.pc;
/**
* 线程间的通信问题:生产者和消费者的问题! 等待唤醒 通知唤醒
* 线程交替执行 A B同时操作一个变量
* A B num+1
* C D num-1
*/
public class ConsumeAndProduct {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for (int i = 0; i < 10; i ++ ) {
try {
data.increment();
} 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.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
//等待 业务 通知
class Data {
private int num = 0;
// 生产者 +1
public synchronized void increment() throws InterruptedException {
//判断等待
if (num != 0) this.wait();
num ++;
System.out.println(Thread.currentThread().getName() + "->" + num);
//通知其他线程 我执行完毕了
this.notifyAll();
}
//消费者 -1
public synchronized void decrement() throws InterruptedException {
if (num == 0) this.wait();
num -- ;
System.out.println(Thread.currentThread().getName() + "->" + num);
//通知其他线程 我执行完毕了
this.notifyAll();
}
}
结果:
E:\develop\Java\jdk1.8.0_281\bin\java.exe "-javaagent:E:\JavaWeb\IntelliJ IDEA 2021.2\lib\idea_rt.jar=54399:E:\JavaWeb\IntelliJ IDEA 2021.2\bin" -Dfile.encoding=UTF-8 -classpath E:\develop\Java\jdk1.8.0_281\jre\lib\charsets.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\deploy.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\ext\access-bridge-64.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\ext\cldrdata.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\ext\dnsns.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\ext\jaccess.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\ext\jfxrt.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\ext\localedata.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\ext\nashorn.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\ext\sunec.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\ext\sunjce_provider.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\ext\sunmscapi.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\ext\sunpkcs11.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\ext\zipfs.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\javaws.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\jce.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\jfr.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\jfxswt.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\jsse.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\management-agent.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\plugin.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\resources.jar;E:\develop\Java\jdk1.8.0_281\jre\lib\rt.jar;E:\acwing\JUC\out\production\juc main.java.com.syzd.pc.ConsumeAndProduct
A->1
C->0
B->1
A->2
B->3
C->2
C->1
C->0
B->1
A->2
B->3
C->2
C->1
C->0
B->1
A->2
B->3
C->2
C->1
C->0
B->1
A->2
B->3
D->2
D->1
D->0
B->1
A->2
B->3
D->2
D->1
D->0
A->1
D->0
A->1
D->0
A->1
D->0
A->1
D->0
Process finished with exit code 0
存在问题(虚假唤醒)
4.2、虚假唤醒
while,如下述代码:
// 生产者 +1
public synchronized void increment() throws InterruptedException {
//判断等待
while (num != 0) this.wait();
num ++;
System.out.println(Thread.currentThread().getName() + "->" + num);
//通知其他线程 我执行完毕了
this.notifyAll();
}
4.3、Lock版
Condition
实现生产者和消费者代码
package main.java.com.syzd.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockCAP {
public static void main(String[] args) {
Data2 data2 = new Data2();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data2.increment();
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data2.increment();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data2.decrement();
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data2.decrement();
}
}, "D").start();
}
}
class Data2{
private int num = 0;
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() + "=>" + num);
// 通知其他线程 +1 执行完毕
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void decrement(){
lock.lock();
try {
while (num == 0){
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName() + "=>" + num);
// 通知其他线程 +1 执行完毕
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
4.4、Condition的优势
精确的通知和唤醒线程
如果我们要指定通知的下一个进行顺序怎么办呢? 我们可以使用Condition来指定通知进程~
package main.java.com.syzd.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionDemo {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data3.printA();
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data3.printB();
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data3.printC();
}
},"C").start();
}
}
class Data3{
private ReentrantLock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int num = 1;// 1A 2B 3C
public void printA(){
lock.lock();
try {
while (num != 1){
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "==> AAAA" );
num = 2;
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while (num != 2) {
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "==> BBBB" );
num = 3;
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while(num != 3){
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "==> CCCC" );
num = 1;
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
结果:
A==> AAAA
B==> BBBB
C==> CCCC
A==> AAAA
B==> BBBB
C==> CCCC
A==> AAAA
B==> BBBB
C==> CCCC
A==> AAAA
B==> BBBB
C==> CCCC
A==> AAAA
B==> BBBB
C==> CCCC
A==> AAAA
B==> BBBB
C==> CCCC
A==> AAAA
B==> BBBB
C==> CCCC
A==> AAAA
B==> BBBB
C==> CCCC
A==> AAAA
B==> BBBB
C==> CCCC
A==> AAAA
B==> BBBB
C==> CCCC
Process finished with exit code 0
5、8锁现象
6、集合不安全
面试知识点:工作中遇到哪些异常,并发修改异常,OOM内存溢出异常
6.1、List不安全
package main.java.com.syzd.collections;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class ListTest {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
for (int i = 1; i <= 30 ; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
会报错ConcurrentModificationException并发修改异常!ArrayList在并发情况下是不安全的!
解决方案:
1. Vector<String> list = new Vector<>();
2. List<String> list = Collections.synchronizedList(new ArrayList<>());
3. List<String> list = new CopyOnWriteArrayList<>();
CopyOnWriteArrayList:写入时复制! COW 计算机程序设计领域的一种优化策略
多个线程调用的时候,list是唯一的,读取的时候list是固定的,写入的时候给list复制一份给调用者,调用者写入副本,副本再添加到唯一的list中。避免在写入的时候被覆盖,造成数据问题!
核心思想:如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。
读的时候不需要加锁,如果读的时候有多个线程正向CopyOnWriteArrayList添加数据,读还是会读到旧数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。
CopyOnWriteArrayList比Vector厉害在哪里?
-
Vector底层是使用synchronized 关键字来实现的:效率特别低下。
-
CopyOnWriteArrayList 使用的是Lock锁,效率会更加高效!
6.2、set不安全
Set和List同理可得:多线程情况下,普通的Set集合是线程不安全的;
解决方案有两种:
-
使用Collections工具类的synchronized 包装的Set类;
-
使用CopyOnWriteArraySet 写入复制的 JUC解决方案;
package main.java.com.syzd.collections;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
public class SetTest {
public static void main(String[] args) {
/**
* 解决方案
* 1、Set<String> set = Collections.synchronizedSet(new HashSet<>());
* 2、Set<String> set = new CopyOnWriteArraySet<>();
*/
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
}).start();
}
}
}
HashSet底层是什么?
- hashSet底层就是一个HashMap;
6.3、Map不安全
//map 是这样用的吗? 不是,工作中不使用这个
//默认等价什么? new HashMap<>(16,0.75);
Map<String, String> map = new HashMap<>();
//加载因子、初始化容量
默认 加载因子是0.75 默认的 初始容量是16
同样的HashMap基础类也存在并发异常!
package main.java.com.syzd.collections;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class MapTest {
public static void main(String[] args) {
//map 是这样用的吗? 不是,工作中不使用这个
//默认等价什么? new HashMap<>(16,0.75);
/**
* Map<String, String> map = new HashMap<>();
* 解决方案
* 1、Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
* 2、Map<String, String> map = new ConcurrentHashMap<>();
*/
Map<String, String> map = new ConcurrentHashMap<>();
//加载因子、初始化容量
for (int i = 1; i < 100; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
System.out.println(map);
}).start();
}
}
}