1. JUC
java.util:工具包
2. 线程和进程
线程,进程,如果不能使用一句话说出来的技术,不扎实
进程:一个程序,QQ.exe Music.exe 程序的集合
一个进程往往可以包含多个线程,至少包含一个
Java默认有几个线程?2个 main线程和GC线程。
并发,并行
并发(多线程操作同一个资源)
- CPU一核,模拟出多条线程,快速交替。
并行
- CPU多核,多个线程可以同时执行;线程池。
// 获取cpu的核数
// CPU密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
wait不需要捕获异常???存疑
Lock锁(重点)
传统Synchronized
public class SaleTicketDemo01 {
public static void main(String[] args) {
// 并发:多个线程操作同一个资源类,把资源类丢入线程
Ticket ticket = new Ticket();
new Thread(() ->{
for (int i = 0; i < 20; i++) {
ticket.sale();
}
}, "A").start();
new Thread(() ->{
for (int i = 0; i < 20; i++) {
ticket.sale();
}
}, "B").start();
new Thread(() ->{
for (int i = 0; i < 20; i++) {
ticket.sale();
}
}, "C").start();
}
}
//资源类
class Ticket {
private int number = 10;
public synchronized void sale() {
if (number > 0) {
System.out.println(Thread.currentThread().getName() +
"卖出了第" + (number--) + "票,剩余:" + number);
}
}
}
Lock接口
公平锁:十分公平:可以先来后到
非公平锁:十分不公平:可以插队(默认)
锁是什么,如何判断锁的是谁?
有序执行A->B->C
public class B {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(()->{
for (int i = 0; i < 3; i++) {
data.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 3; i++) {
data.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 3; i++) {
data.printC();
}
},"C").start();
}
}
class Data2 {
private int munber = 0;
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1;
public void printA() {
lock.lock();
try {
while (number != 1){
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "-->AAAA");
number = 2;
//唤醒指定的人,B
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
while (number != 2){
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "-->BBBB");
number = 3;
//唤醒指定的人,B
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
//业务,判断-》执行-》通知
while (number != 3){
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "-->CCCC");
number = 1;
//唤醒指定的人,B
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
5 八锁问题
synchronized
:该关键字锁的对象是方法的调用者!
两个方法用的是同一把锁,谁先拿到谁先执行。
new this 具体的一个对象
static Class唯一的一个模板
两个普通同步方法
/**
* 1.标准情况下,两个线程先打印发短信还是打电话 answer:发短信
* 2.sendSms延迟4秒,两个线程先打印发短信还是打电话 answer:发短信
*/
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.semdSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {phone.call();}, "B").start();
}
}
class Phone {
// synchronized 锁的对象是方法的调用者
// 两个方法用的是同一个锁,谁先拿到谁执行
public synchronized void semdSms() {
//情况2
// try {
// TimeUnit.SECONDS.sleep(4);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
非同步方法和同步方法
/**
* 3.增加了一个普通方法后,先执行发短信还是hello? answer: hello
* 4.两个对象都执行同步方法 answer:打电话
*/
public class Test2 {
public static void main(String[] args) {
// 两个对象,两个调用者,两把锁
Phone2 phone = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(() -> {
phone.semdSms();
}, "A").start();
new Thread(() -> {
phone2.call();
}, "D").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {phone.call();}, "B").start();
new Thread(() -> {
phone.hello();
}, "C").start();
}
}
class Phone2 {
// synchronized 锁的对象是方法的调用者
// 两个方法用的是同一个锁,谁先拿到谁执行
public synchronized void semdSms() {
//情况2
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
//hello 没有锁,不是同步锁,不受锁的影响
public void hello() {
System.out.println("hello");
}
}
同步方法和静态同步方法
/**
* 5.增加两个静态的同步方法,只有一个对象,先打电话还是发短信 answer 发短信
* 6.两个对象,增加两个静态的同步方法。 answer 发短信
*/
public class Test3 {
public static void main(String[] args) {
// 两个对象,两个调用者,两把锁
Phone3 phone = new Phone3();
Phone3 phone3 = new Phone3();
new Thread(() -> {
phone.semdSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 问题5代码
// new Thread(() -> {phone.call();}, "B").start();
new Thread(() -> {phone3.call();}, "B").start();
}
}
// Phone3 只有唯一的一个Class对象
class Phone3 {
// synchronized 锁的对象是方法的调用者
// static 静态方法,类一加载就有了!Class 是模板
// 类一加载就有了! 锁的是Class
public static synchronized void semdSms() {
//情况2
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
}
Class对象和类对象
/**
* 7. 一个静态的同步方法,1个普通的同步方法,一个对象,先打印谁? 打电话
* 8. 一个静态的同步方法,一个普通的同步方法,两个对象,先打印谁? 打电话
* @author Evan
* @date 2021/3/18 15:06
*/
public class Test4 {
public static void main(String[] args) {
// 两个对象,两个调用者,两把锁
Phone4 phone = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(() -> {
phone.semdSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 问题7的代码
// new Thread(() -> {phone.call();}, "B").start();
new Thread(() -> {phone2.call();}, "B").start();
}
}
class Phone4 {
// synchronized 锁的对象是方法的调用者
// static 静态方法,类一加载就有了!Class 是模板
// 类一加载就有了! 锁的是Class
public static synchronized void semdSms() {
//情况2
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
6、集合类不安全
List不安全
使用ArrayList
public class ListTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(() ->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
输出:出现ConcurrentModificationException
说明:测试中出现过不报错的情况,循环次数增加到20次仍会出现完全不报错的情况。
[null, 1770e, b15b8, 38f61, 804a5]
[null, 1770e, b15b8, 38f61, 804a5, 620f7, 71db8]
[null, 1770e, b15b8, 38f61, 804a5, 620f7, 71db8, a8acf]
[null, 1770e, b15b8, 38f61, 804a5, 620f7, 71db8]
[null, 1770e, b15b8, 38f61, 804a5, 620f7]
[null, 1770e, b15b8, 38f61, 804a5]
[null, 1770e, b15b8, 38f61, 804a5]
[null, 1770e, b15b8, 38f61, 804a5]
[null, 1770e, b15b8, 38f61, 804a5]
Exception in thread "0" java.util.ConcurrentModificationException
public class ListTest {
public static void main(String[] args) {
/**
* ArrayList 并发下会出现ConcurrentModificationException
* 解决方案
* 1.List<String> list = new Vector<>();
* 2.List<String> list = Collections.synchronizedList(new ArrayList<>());
* 3.List<String> list = new CopyOnWriteArrayList<>();
*/
/**
* CopyOnWrite 写入时复制, 这是计算机程序设计领域的一种优化策略
* 多个线程调用的时候,list,读取的时候,固定的,写入覆盖
* 在写入的时候避免覆盖,造成数据问题
* 读写分离
*/
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 200; i++) {
new Thread(() ->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
set不安全
HashSet底层是什么
//hash底层就是hashmap,根据map的key不重复的特性建立集合
public HashSet() {
map = new HashMap<>();
}
// add方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// Dummy value to associate with an Object in the backing Map
// 与背景map中的对象关联的虚拟值
private static final Object PRESENT = new Object();
hash底层就是hashmap,key不重复
public class SetTest {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0,4));
System.out.println(set);
}, String.valueOf(i).toString()).start();
}
}
}
解决方案
public class SetTest {
public static void main(String[] args) {
/**
* Set<String> set = new HashSet<>(); 出现ConcurrentModificationException
* 解决方案
* 1.Set<String> set = Collections.synchronizedSet(new HashSet<>());
* 2.Set<String> set = new CopyOnWriteArraySet<>();
*/
Set<String> set = new HashSet<>();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0,4));
System.out.println(set);
}, String.valueOf(i).toString()).start();
}
}
}
Map不安全
public class MapSet {
public static void main(String[] args) {
/**
* map默认等价于什么? new HashMap<>(16, 0.74) 和hashmap的原理有关
* Map<String, String> map = new HashMap<>();
* Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
* Map<String, String> map = new ConcurrentHashMap<>();
*/
Map<String, String> map = new HashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
}).start();
}
}
}
7、Callable
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread thread = new MyThread();
FutureTask futureTask = new FutureTask(thread);
new Thread(futureTask, "A").start();
new Thread(futureTask, "B").start(); //结果会被缓存,效率高
// 此时,FutureTask的state已非new状态,则此时会直接结束对应的线程,就会导致任务也不执行
// 只是在第一次调用时返回结果保存了。(网友弹幕)
//这个get方法可能会产生阻塞!把他放在最后
Integer o = (Integer) futureTask.get();
// 主线程会在此处等待,futureTask.get()返回结果后继续执行
System.out.println(o);
System.out.println("快点快点");
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call()"); //会打印几个call,只打印了一个
TimeUnit.SECONDS.sleep(3);
// 耗时的操作
return 1024;
}
}
输出
call()
1024
快点快点
8、常用辅助类
CountDownLatch
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 总数是6, 必须要执行任务的时候,再使用
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "Go out");
countDownLatch.countDown(); //数量-1
}, String.valueOf(i)).start();
}
countDownLatch.await(); //等待计数器归零,然后再向下执行
System.out.println("Close Door");
}
}
输出
1Go out
5Go out
0Go out
4Go out
3Go out
2Go out
Close Door
每次有线程调用countDown()数量-1,假设计数器变为0,countDownLatch.await()
就会被唤醒,继续执行!
CyclicBarrier
public class CyclicBarrierDemo {
public static void main(String[] args) {
/**
* 集齐7颗龙珠召唤神龙
*/
// 召唤龙珠的线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙成功!");
});
for (int i = 0; i < 7; i++) {
final int temp = i;
//lambda表达式不能操作i,所以
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "收集" + temp + "个龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
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() + "抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}
原理:
semaphore.acquire();
获得,假设如果已经满了,等待,等待被释放为止!
semaphore.release();
释放,会将当前的信号量释放+1,然后唤醒等待的线程!
作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!
9、读写锁
ReadWriteLock
读可以被多个线程同时读,写的时候只能有一个线程去写
public class ReadWriteLockDemo {
public static void main(String[] args) {
//MyCache myCache = new MyCache();
MyCacheLock myCache = new MyCacheLock();
for (int i = 1; i <= 10; i++) {
final int temp = i;
new Thread(() -> {
myCache.put(temp + "", temp + "");
}, String.valueOf(i)).start();
}
//读取
for (int i = 1; i <= 10; i++) {
final int temp = i;
new Thread(() -> {
myCache.get(temp + "");
}, String.valueOf(i)).start();
}
}
}
//加读写锁
class MyCacheLock{
private final Map<String, Object> map = new HashMap<>();
// 读写锁:更加细粒度的控制
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock lock = new ReentrantLock();
// 存
public void put(String key, Object value) {
// 写锁
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入ok");
} finally {
readWriteLock.writeLock().unlock();
}
}
public void get(String key) {
//实测读写10个线程不加锁,出现写入过程中读取,因此要加锁
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取ok");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
//不加锁
class MyCache{
private volatile Map<String, Object> map = new HashMap<>();
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入ok");
}
public void get(String key) {
System.out.println(Thread.currentThread().getName() + "读取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取ok");
}
}
10、阻塞队列
阻塞队列的使用场景:多线程并发处理,线程池!
四组API
.put 队列没有位置了,会一直阻塞
11、线程池
池化技术
线程池的好处
1、降低资源的消耗
2、提高响应的速度
3、方便管理
线程复用、
12、四大函数式接口
新时代的程序员:
lambda表达式
链式编程
函数式接口
Stream流计算
16、 JMM
请你谈谈Volatile的理解
Volatile是Java虚拟机提供轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
什么是JMM
Java内存模型,不存在的东西,是概念和约定
关于JMM的一些同步的约定
1、线程解锁前,必须把共享变量立刻刷回主存。
2、线程加锁前,必须读取主存中的最新值到工作内存中
3、加锁和解锁是同一把锁
线程 工作内存,主内存
public class JMMDemo {
private static int num =0;
public static void main(String[] args) {
new Thread(() -> {
while (num == 0) {
}
}).start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
设想的是num在主线程中赋值为1,new线程也因为num != 0而停止运行。实际运行时,new线程未停止。这是因为主内存中的num==1,而线程的工作内存未做相应的修改,因此不会停止运行。
解决办法见下一节。
17、Volatile
1、保证可见性
public class JMMDemo {
private volatile static int num =0;
// 不加 volatile 程序就会死循环
// 加 volatile 可以保证可见性
public static void main(String[] args) {
new Thread(() -> { // 线程1对主内存的变化不知道
while (num == 0) {
}
}).start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
2、不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰的,也不能被分割。要么同时成功,要么同时失败。
public class VDemo2 {
private volatile static int num = 0;
public static void add() {
num++;
}
public static void main(String[] args) {
//理论上num结果应该为 2 万
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
while (Thread.activeCount() > 2) { // main gc 线程
Thread.yield();
}
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JdEcQga1-1616655476952)(…/image/image-20210322113602501.png)]
输出始终是20000,未出现视频中的小于20000的情况。但是idea会有警告。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LxYhg3wR-1616655476954)(…/image/image-20210322113722836.png)]
使用原子类解决原子性问题
public class VDemo3 {
// 原子类的 Integer
private volatile static AtomicInteger num = new AtomicInteger();
public static void add() {
num.getAndIncrement(); // AtomicInteger + 1方法, CAS
}
public static void main(String[] args) {
//理论上num结果应该为 2 万
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
while (Thread.activeCount() > 2) { // main gc 线程
Thread.yield();
}
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
这些类的底层都直接和操作系统挂钩! 在内存中修改值! Unsafe类是一个很特殊的存在。
Volatile是可以保持可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
单例模式
搞懂单例模式和枚举
)]
输出始终是20000,未出现视频中的小于20000的情况。但是idea会有警告。
[外链图片转存中…(img-LxYhg3wR-1616655476954)]
使用原子类解决原子性问题
public class VDemo3 {
// 原子类的 Integer
private volatile static AtomicInteger num = new AtomicInteger();
public static void add() {
num.getAndIncrement(); // AtomicInteger + 1方法, CAS
}
public static void main(String[] args) {
//理论上num结果应该为 2 万
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
while (Thread.activeCount() > 2) { // main gc 线程
Thread.yield();
}
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
这些类的底层都直接和操作系统挂钩! 在内存中修改值! Unsafe类是一个很特殊的存在。
Volatile是可以保持可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
单例模式
搞懂单例模式和枚举
构造器私有