JUC学习-B站KUANG
线程和进程
如果不能用一句话说出来的技术,不扎实!
线程是操作系统能够进行运算调度的最小单位被包含在进程之中,是进程中的实际运作单位。一个进程中可以并发多个线程,每条线程并行执行不同的任务。
进程是一段程序的执行过程,系统进行资源分配和调度的基本单位
wait和sleep的区别
来自不同的类
wait -> Object
sleep->Thread
关于锁的释放
wait会释放锁,sleep不会
使用的范围不同
wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用
sleep在任何地方
是否需要捕获异常
sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常(中断异常)
Lock锁(重点)
传统synchronized
Lock
公平锁:十分公平,可以先来后到
非公平锁:十分不公平,可以插队(默认)
synchronized和Lock区别
- synchronized是内置的JAVA关键字,Lock是一个java类
- synchronized无法获取锁的状态,Lock可以判断是否获取到了锁
- synchronized会自动释放锁,lock必须手动释放锁!如果不,产生死锁
- synchronized线程1{获得锁,阻塞}、线程2(等待,傻傻的等);Lock不一定等待下去
- synchronized 可重入锁,不可以中断,非公平;Lock可重入锁,可以判断锁,公平自己设置
- synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码
锁是什么,如何判断锁的是谁?
生产者和消费者问题
面试:单例模式,排序算法,生产者和消费者,死锁
Synchronized版
/* A 执行完 执行B 执行C 执行A ...*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.decrement();
} 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.notify();
}
//-1
public synchronized void decrement() throws InterruptedException {
if (number == 0){
//等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"->"+number);
//通知
this.notify();
}
}
如果存在多个线程,会造成虚假唤醒!
将if改为while
JUC版
package pc;
/**
* @author Pillar
* @version 1.0
* @date 2022/6/3 18:37
*/
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Description: 线程之间的通信问题:生产者和消费者问题
* 线程交替执行 A B 操作同一个变量 num = 0
* A num + 1
* B num - 1
*/
public class b {
public static void main(String[] args) {
Data1 data = new Data1();
new Thread(()->{
for (int i = 0; i < 20; i++) {
data.increment();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
data.increment();
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//等待、业务、通知
class Data1 {
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//+1
public void increment() {
lock.lock();
try {
while (number > 0){
//等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"->"+number);
//通知其他线程,+1完毕
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//-1
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number == 0){
//等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"->"+number);
//通知
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
condition 精准的通知和唤醒线程
public class C {
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 Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int num = 1;
public void printA(){
lock.lock();
try {
//业务:判断、执行、通知
while (num!=1){
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"-A");
num = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
//业务:判断、执行、通知
while (num!=2){
//等待
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"-BB");
num = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
//业务:判断、执行、通知
while (num!=3){
//等待
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"-CCC");
num = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
8锁现象
Test1
package com.pillar.lock8;
import java.util.concurrent.TimeUnit;
/**
* @author Pillar
* @version 1.0
* @date 2022/6/6 16:38
* 8锁问题 :关于锁的8个问题
* 1. 标准情况下,两个线程 打印顺序 首先是打电话还是发短信 ? 1 发短信 2 打电话
* 2. sendSms延时4s,两个线程 打印顺序 首先是打电话还是发短信 ? 1 发短信 2 打电话
*/
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone {
// synchronized 锁的对象是方法的调用者!
// 在main中用的是同一个phone对象,所以两个方法用的是同一个锁,谁先拿到谁执行
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
Test2
package com.pillar.lock8;
import java.util.concurrent.TimeUnit;
/**
* @author Pillar
* @version 1.0
* @date 2022/6/6 16:38
* 8锁问题 :关于锁的8个问题
* 1. 标准情况下,两个线程 打印顺序 首先是打电话还是发短信 ? 1 发短信 2 打电话
* 2. sendSms延时4s,两个线程 打印顺序 首先是打电话还是发短信 ? 1 发短信 2 打电话
* 3. 增加了一个普通方法后,先执行发短信还是hello? 普通方法
* 4. 两个对象,两个同步方法,首先是打电话还是发短信 ? 打电话
*/
public class Test2 {
public static void main(String[] args) {
//两个对象,两个调用者,两把锁
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(()->{
phone1.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone2 {
// synchronized 锁的对象是方法的调用者!
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
//这里没有锁!不是同步方法,不受锁的影响
public void hello(){
System.out.println("hello");
}
}
Test3
package com.pillar.lock8;
import java.util.concurrent.TimeUnit;
/**
* @author Pillar
* @version 1.0
* @date 2022/6/6 17:12
* 5. 增加两个静态同步方法,只有一个对象, 首先发短信还是打电话? 发短信
* 6. 增加两个静态同步方法,有两个对象, 首先发短信还是打电话? 发短信
*/
public class Test3 {
public static void main(String[] args) {
//两个对象,两个调用者,两把锁
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{
phone1.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
//Phone3 只有唯一的一个class对象
class Phone3 {
// synchronized 锁的对象是方法的调用者!
//static是静态方法
//类一加载就有了, 锁的是class,全局唯一
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
Test4
package com.pillar.lock8;
import java.util.concurrent.TimeUnit;
/**
* @author Pillar
* @version 1.0
* @date 2022/6/6 18:30
*/
public class Test4 {
public static void main(String[] args) {
//两个对象,两个调用者,两把锁
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(()->{
phone1.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
//Phone3 只有唯一的一个class对象
class Phone4 {
//静态的同步方法 ,锁的是Class类模板
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//普通的同步方法, 锁的是调用者
public synchronized void call(){
System.out.println("打电话");
}
}
小结
-
synchronized 锁的对象是方法的调用者!也就是new出来的对象
同一个对象中,带有synchronized 的方法,谁先拿到谁先执行(同步)
-
static synchronized 锁的是class,类本身,全局唯一
只要是这个类的对象,调用static synchronized的方法,谁先拿到谁先执行(同步)
集合类不安全
List不安全
public class ListTest {
public static void main(String[] args) {
// 并发下ArrayList不安全
/**
* 解决方案:
* 1. List<String> list = new Vector<>();
* 2. List<String> list = Collections.synchronizedList(new ArrayList<>());
* 3. List<String> list = new CopyOnWriteArrayList<>();
*/
//CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略;https://blog.csdn.net/u014203449/article/details/83867921
//解决了并发编程中,多线程写入时覆盖问题。
// add加锁了,复制一个容器,在这个复制的容器中添加元素,添加完之后,再将引用指向这个新容器。
//get没加锁,不需要加锁,因为当前容器不会添加任何元素
// 读写分离
// Vector 、synchronizedList 、CopyOnWriteArrayList三者区别 :https://blog.csdn.net/dimu9293/article/details/107729756
/*vector 所有操作都要加锁,性能不佳 即使在高并发环境下都能保证数据的一致性
synchronizedList 同上 同上
CopyOnWriteArrayList写操作会有一定的延迟 只有写加锁,读不加锁*/
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 1; i <= 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
Vector 、synchronizedList 、CopyOnWriteArrayList三者区别
- 与vector以及Collections.synchronizedList()实现原理上的区别
1、vector是通过在每个方法加synchronized实现,包括最简单的size()方法
public synchronized int size() {
return elementCount;
}
public synchronized boolean isEmpty() {
return elementCount == 0;
}
123456
2、synchronizedList是通过在每个方法内部加 synchronized实现,包括最简单的size()方法
public int size() {
synchronized (mutex) {return m.size();}
}
public boolean isEmpty() {
synchronized (mutex) {return m.isEmpty();}
}
public boolean containsKey(Object key) {
synchronized (mutex) {return m.containsKey(key);}
}
public boolean containsValue(Object value) {
synchronized (mutex) {return m.containsValue(value);}
}
public V get(Object key) {
synchronized (mutex) {return m.get(key);}
}
123456789101112131415
3、CopyOnWriteArraylist是通过复制一份副本,然后通过修改副本之后再赋值回去,lock是new()出来的,用于在set,add等写操作的时候进行锁操作
final transient Object lock = new Object();
public E set(int index, E element) {
synchronized (lock) {
Object[] es = getArray();
E oldValue = elementAt(es, index);
if (oldValue != element) {
es = es.clone();
es[index] = element;
setArray(es);
}
return oldValue;
}
}
//获取迭代器的时候是复制了一个新的数组
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
123456789101112131415161718192021
2. 三者各有什么区别
废话不多说,直接上表
类型 | 缺点 | 优点 |
---|---|---|
vector | 所有操作都要加锁,性能不佳 | 即使在高并发环境下都能保证数据的一致性 |
synchronizedList | 同上 | 同上 |
CopyOnWriteArrayList | 写操作会有一定的延迟 | 只有写加锁,读不加锁 |
解析
1、由于vector与synchronizedList都是通过加synchronized实现,两者性能上差距并不大。但是由于每次调用方法都要加锁,导致在大数据量的情况下性能不理想。
2、CopyOnWriteArrayList 由于写锁读不锁使得性能更好,但是由于写操作是在副本上进行的,在数据量大的情况下数组的复制比较耗时,所以写完后不能立马体现出来,有一定的延迟。
3. CopyOnWriteArrayList 读写不一致的场景示例
1.循环中删除元素不报错
例子
List<String> list = new CopyOnWriteArrayList(); //CopyOnWriteArrayList换成ArrayList则直接报错
list.add("A1");
list.add("A2");
list.add("A3");
for (String str : list) {
if ("A1".equals(str)) {
list.remove(str);
}
}
System.out.println(list);
//运行结果
[A2, A3]
1234567891011121314
2.迭代过程中往list添加或减少元素不会立马体现
由于迭代是复制了一份新的数组,然后对新数组进行迭代。所以修改数组并不会立马反馈到迭代器中。
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList();
list.add("A1");
list.add("A2");
list.add("A3");
Iterator<String> it = list.iterator();
list.add("A4");
while (it.hasNext()){
System.out.println(it.next());
}
//运行结果
A1
A2
A3
123456789101112131415
3.不支持remove操作
为了保证数据不错乱,CopyOnWriteArrayList不支持在迭代器中进行remove操作。
在CopyOnWriteArrayList中
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
123
在 COWIterator 中
public void remove() {
throw new UnsupportedOperationException();
}
123
测试
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList();
list.add("A1");
list.add("A2");
list.add("A3");
Iterator<String> it = list.iterator();
list.add("A4");
while (it.hasNext()){
it.remove();
System.out.println(it.next());
}
结果:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.concurrent.CopyOnWriteArrayList$COWIterator.remove(CopyOnWriteArrayList.java:1120)
at STU_CopyOnWriteArraylist.read(STU_CopyOnWriteArraylist.java:20)
at STU_CopyOnWriteArraylist.main(STU_CopyOnWriteArraylist.java:7)
set不安全
/**
* java.util.ConcurrentModificationException并发修改异常
* 解决方案:
* 1. Set<String> set = Collections.synchronizedSet(new HashSet<>());
* 2. Set<String> set = Collections.synchronizedSet(new HashSet<>());
*/
public class SetTest {
public static void main(String[] args) {
// Set<String> set = new HashSet<>();
// Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
hashSet?
public HashSet() {
map = new HashMap<>();
}
//add 本质就是 map 的 key是无法重复的!
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// PRESENT为不变的值
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
Map不安全
Callable(简单)
- 可以有返回值
- 可以抛出异常
- 方法不用,call()
测试
public class CallTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//提问
// 线程怎样使用callable? 利用FutureTask作为适配类,来接受实现了callable的线程对象,然后再new Thread(futureTask).start就能启动了
//为什么用FutureTask? 因为线程类的参数中可以接受Runnable接口的实现类对象,参数中不能接受Callable接口的实现类对象,
// 但是FutureTask,实现了RunnableFuture接口,并且实现了RunnableFuture接口继承了Runnable接口。这样线程就能实现callable接口了。
// 功能:成功后,可以访问其结果
//new Thread(new Runnable()).start();
//new Thread(new FutureTask<V>()).start();
//new Thread(new FutureTask<V>(Callable)).start();
// new Thread().start(); //怎么启动callable
MyThread myThread = new MyThread();
//适配类
FutureTask futureTask = new FutureTask(myThread);
new Thread(futureTask,"A").start();
//获取callAble.返回结果
//get()
//等待计算完成,然后检索其结果。
// 方法可能会产生阻塞!把它放到最后或者使用异步通信来处理!
new Thread(futureTask,"B").start();
/* 这个并不是缓存,是由于JVM第二次再调用FutrueTask对象所持有的线程
此时FutrueTask的state此时已非NEW状态(各个状态,这边不做详细解释)
则此时会直接结束对应线程,就会导致任务也不执行
只是在第一次调用时返回结果保存了,可能这就是老师所说的缓存*/
String o = (String) futureTask.get();
System.out.println(o);
}
}
class MyThread implements Callable<String> {
@Override
public String call() {
System.out.println("call方法");
return "123";
}
}
细节:
1.
//get()
//等待计算完成,然后检索其结果。
// 方法可能会产生阻塞!把它放到最后或者使用异步通信来处理!
2. new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();
只打印一遍结果
/* 这个并不是缓存,是由于JVM第二次再调用FutrueTask对象所持有的线程
此时FutrueTask的state此时已非NEW状态(各个状态,这边不做详细解释)
则此时会直接结束对应线程,就会导致任务也不执行
只是在第一次调用时返回结果保存了,可能这就是老师所说的缓存*/
常用的辅助类
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");
}
}
原理:
countDownLatch.countDown();//数量减1
countDownLatch.await();//等待计数器归零,然后再向下执行
每次调用countDown(),假设计数器变为0,await()就会被唤醒,继续执行!
CyclicBarrier
https://blog.csdn.net/Shockang/article/details/118559080
CyclicBarrier 是另外一种多线程并发控制工具。
和 CountDownLatch 非常类似,它也可以实现线程间的计数等待,但它的功能比 CountDownLatch 更加复杂且强大。
CyclicBarrier 可以理解为循环栅栏。
public class CyclicBarrierDemo2 {
public static class Soldier implements Runnable {
private String soldier;
private final CyclicBarrier cyclic;
Soldier(CyclicBarrier cyclic, String soldierName) {
this.cyclic = cyclic;
this.soldier = soldierName;
}
public void run() {
try {
//等待所有士兵到齐
cyclic.await();
doWork();
//等待所有士兵完成工作
cyclic.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
void doWork() {
try {
Thread.sleep(Math.abs(new Random().nextInt() % 10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(soldier + ":任务完成");
}
}
public static class BarrierRun implements Runnable {
boolean flag;
int N;
public BarrierRun(boolean flag, int N) {
this.flag = flag;
this.N = N;
}
public void run() {
if (flag) {
System.out.println("司令:[士兵" + N + "个,任务完成!]");
} else {
System.out.println("司令:[士兵" + N + "个,集合完毕!]");
flag = true;
}
}
}
public static void main(String args[]) throws InterruptedException {
final int N = 10;
Thread[] allSoldier = new Thread[N];
boolean flag = false;
CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));
//设置屏障点,主要是为了执行这个方法
System.out.println("集合队伍!");
for (int i = 0; i < N; ++i) {
System.out.println("士兵 " + i + " 报道!");
allSoldier[i] = new Thread(new Soldier(cyclic, "士兵 " + i));
allSoldier[i].start();
}
}
}
Semaphore
public class SemaphoreDemo {
public static void main(String[] args) {
//线程数量,停车位,限流
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(()->{
try {
semaphore.acquire();//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 释放
}
}).start();
}
}
}
原理:
semaphore.acquire();获得,如果已经满了,等待其他线程被释放
semaphore.release();释放,唤醒等待的线程
作用:共享资源互斥的使用!并发限流。控制最大的线程数!
读写锁ReadWriteLock
/**
* @author Pillar
* @version 1.0
* @date 2022/6/9 10:40
* 独占锁(写锁)一次只能被一个线程占有
* 共享锁(读锁)多个线程同时占有
* ReadWriteLock
* 读-读 可共存
* 读-写 不可
* 写-写 不可
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
// MyCache myCache = new MyCache();
MyCacheLock myCache = new MyCacheLock();
//写入操作
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(()->{
myCache.put(String.valueOf(temp),temp);
},String.valueOf(i)).start();
}
//读取操作
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(()->{
myCache.get(String.valueOf(temp));
},String.valueOf(i)).start();
}
}
}
/*
* 自定义缓存
*/
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);
System.out.println(Thread.currentThread().getName()+"读取OK");
}
}
class MyCacheLock {
private volatile Map<String,Object> map = new HashMap<>();
//读写锁:更加细粒度的控制
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
//存、写入的时候,只希望同时只有一个线程写
public void put(String key,Object value){
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
//取、读的时候,都可以读
public void get(String key){
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"开始读"+key);
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
读写锁和锁Lock的最大区别就是读写锁读写是互斥,而Lock读写是非互斥的。读的时候没加读写锁(写的时候就开始读了),可能会出现脏读。
阻塞队列
什么情况下会使用 阻塞队列?
多线程并发处理,线程池!
使用
增、删
四组API
- 抛出异常
- 不会抛出异常
- 阻塞等待
- 超时等待
public class Test {
public static void main(String[] args) throws InterruptedException {
test4();
}
/*
* 抛出异常
*/
public static void test1(){
//队列大小
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("aa"));
System.out.println(blockingQueue.add("aaa"));
// java.lang.IllegalStateException: Queue full
// System.out.println(blockingQueue.add("a"));
System.out.println("====================");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
// java.util.NoSuchElementException
// System.out.println(blockingQueue.remove());
}
/*
* 有返回值, 没有异常
*/
public static void test2(){
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("s"));
System.out.println(blockingQueue.offer("ss"));
System.out.println(blockingQueue.offer("sss"));
//false
//System.out.println(blockingQueue.offer("sss"));
System.out.println(blockingQueue.peek());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//null
//System.out.println(blockingQueue.poll());
}
/*
* 等待,阻塞(一直)
*/
public static void test3() throws InterruptedException {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
//一直阻塞
blockingQueue.put("a");
blockingQueue.put("aa");
blockingQueue.put("aaa");
//队列没有位置,一直阻塞
//blockingQueue.put("a");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
//一直阻塞
//System.out.println(blockingQueue.take());
}
/*
* 等待,阻塞(等待超时)
*/
public static void test4() throws InterruptedException {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("aa"));
System.out.println(blockingQueue.offer("aaa"));
//blockingQueue.offer("aaaa", 2,TimeUnit.SECONDS);
System.out.println("====================================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
}
}
SynchronousQueue同步队列
没有容量,进去一个元素,必须等待取出来,才能再向里面添加元素
线程池(重点)
三大方法、7大参数、4种拒绝策略
好处:
- 降低系统资源的消耗
- 提高响应的速度
- 方便管理
线程复用、可以控制最大并发数、管理线程
线程池:三大方法
//Executors工具类、3大方法
//使用线程池来创建线程
public class Demo {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
// ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定线程池的大小
ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩的,遇强则强、遇弱则弱
int max = 0;
try {
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
7大参数
源码分析
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,//21亿
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//本质 ThreadPoolExecutor()
public ThreadPoolExecutor(int corePoolSize, //核心线程大小
int maximumPoolSize, //最大线程大小
long keepAliveTime, //空闲线程存活时间
TimeUnit unit, //时间单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂
RejectedExecutionHandler handler) {//拒绝策略
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
银行办理业务
1号2号窗口开(core),3、4、5号窗口休息。
来两个人正常办理业务,多的人来候客区(blockingQueue)等待。
如果候客区也满了,根据多的人数来开启3、4、5号窗口(max)。
如果候客区满,窗口满,再来的人有四种拒绝策略(rejectedHandle)。
手动创建一个线程池
public class Demo {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
// ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定线程池的大小
// ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩的,遇强则强、遇弱则弱
//自定义线程池
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
1,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
// new ThreadPoolExecutor.AbortPolicy());
// new ThreadPoolExecutor.CallerRunsPolicy());
// new ThreadPoolExecutor.DiscardPolicy());
new ThreadPoolExecutor.DiscardOldestPolicy());
try {
for (int i = 1; i <= 9; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
4种拒绝策略
new ThreadPoolExecutor.AbortPolicy()
new ThreadPoolExecutor.CallerRunsPolicy()
new ThreadPoolExecutor.DiscardPolicy()
new ThreadPoolExecutor.DiscardOldestPolicy()
- 它直接在execute方法的调用线程中运行被拒绝的任务,除非执行程序已经关闭,在这种情况下,任务被丢弃
- 它会抛出RejectedExecutionException
- 该处理程序将默默地丢弃被拒绝的任务
- 它丢弃最老的未处理请求,然后重试执行,除非执行程序关闭,在这种情况下,任务被丢弃
小结与拓展
最大的线程池大小
了解:CPU密集型、IO密集型(调优)
- CPU密集型,几核就是几,可以保证CPU效率最高
- IO密集型, 判断程序中十分消耗IO的线程,大于这个,比如两倍
//获取CPU核数
System.out.println(Runtime.getRuntime().availableProcessors());
四大函数式接口(必须掌握)
新时代:lambda表达式、链式编程、函数式接口、Stream流式计算
函数式接口,简化编程模型
函数式接口:只有一个方法的接口,比如
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Function函数式接口
public class Demo01 {
public static void main(String[] args) {
/*Function function = new Function<String,String>() {
@Override
public String apply(String str) {
return str;
}
};*/
// Function function = (str)->{return str;};
Function function = s->s;
System.out.println(function.apply("123"));
}
}
断定性接口:有一个参数,返回值是 布尔值
public class Demo02 {
public static void main(String[] args) {
//判断字符串是否为空
/*Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String str) {
return str.isEmpty();
}
};*/
Predicate<String> predicate = str -> str.isEmpty();
System.out.println(predicate.test("123"));
System.out.println(predicate.test(""));
}
}
消费型接口:只有输入,没有返回值
public class Demo03 {
public static void main(String[] args) {
// Consumer consumer = new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// };
Consumer consumer = str-> System.out.println("123");
consumer.accept("123");
}
}
供给型接口:没有输入,有返回值
public class Demo04 {
public static void main(String[] args) {
/*Supplier<String> supplier = new Supplier<String>() {
@Override
public String get() {
System.out.println("supplierGet");
return "null";
}
};*/
Supplier<String> supplier = ()->{
System.out.println("123");
return "null";
};
System.out.println(supplier.get());
}
}
Stream流式计算
什么是Stream流式计算
Stream(流)是一个来自数据源的元素队列(可以使集合或者数组等),并且支持聚合操作;对于集合数据的一些处理。
可以参考:https://blog.csdn.net/java_Trainees/article/details/123236971
import java.util.Arrays;
import java.util.List;
/**
* 题目要求:一分钟内完成此题,只能用一行代码实现!
* 现在有5个用户!筛选:
* 1、ID 必须是偶数
* 2、年龄必须大于23岁
* 3、用户名转为大写字母
* 4、用户名字母倒着排序
* 5、只输出一个用户!
**/
/*
二.常用方法
1.filter(Predicate<? super T> predicate) 返回由与此给定谓词匹配的此流的元素组成的流。
2.count() 返回此流中的元素数。
3.forEach(Consumer<? super T> action) 对此流的每个元素执行操作。
4.sorted(Comparator<? super T> comparator) 返回由该流的元素组成的流,根据提供的 Comparator进行排序。
5.map(Function<? super T,? extends R> mapper)返回由给定函数应用于此流的元素的结果组成的流。
*/
public class Test {
public static void main(String[] args) {
User user1 = new User(1,"张三",21);
User user2 = new User(2,"李四",25);
User user3 = new User(3,"王五",22);
User user4 = new User(4,"赵六",23);
User user5 = new User(5,"孙七",24);
User user6 = new User(6,"王五",26);
User user7 = new User(7,"赵六",27);
User user8 = new User(8,"孙七",28);
List<User> users = Arrays.asList(user1, user2, user3, user4, user5,user6,user7,user8);
users.stream()
.filter(u->u.getId()%2==0)//1、ID 必须是偶数
.filter(u->u.getAge()>23)//2、年龄必须大于23岁
.map(u->u.getName().toUpperCase())//3、用户名转为大写字母
.sorted((u1,u2)->u2.compareTo(u1))//用户名字母倒着排序
.limit(1)//只输出一个用户!
.forEach(System.out::println);
}
}
ForkJoin
什么是ForkJoin
Fork/Join框架是Java 7提供的一个用于并行执行任务的框架, 核心思想就是把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果 - <分而治之> (在大数据量环境下)
特点:工作窃取
其他线程完成的快,不闲这,帮未完成的线程的任务
/ * 如何使用ForkJoin
* 1. 通过ForkJoinPool
* 2. 计算任务 forkjoinPool.execute(ForkJoinTask task)
* 3. 计算类要继承 ForkJoinTask
*/
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start;
private Long end;
//临界值
private Long temp = 10000L;
public ForkJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
/**
* 计算方法
* @return
*/
@Override
protected Long compute() {
if((end-start)<temp){
Long sum=0L;
for (Long i = start; i < end; i++) {
sum+=i;
}
return sum;
}else { // forkjoin 递归
long middle = (start + end) / 2; // 中间值
ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
task1.fork(); // 拆分任务,把任务压入线程队列
ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
task2.fork(); // 拆分任务,把任务压入线程队列
return task1.join() + task2.join();
}
}
}
测试
/**
* 同一个任务 别人比你高几十倍
*/
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1();//5247
// test2();//4210
// test3();//151
}
//普通程序员
public static void test1(){
Long sum=0L;
long start=System.currentTimeMillis();
for (Long i = 1L; i <= 10_0000_0000; i++) {
sum+=i;
}
long end=System.currentTimeMillis();
System.out.println("sum="+sum+"时间:"+(end-start));
}
//会使用forkjoin
public static void test2() throws ExecutionException, InterruptedException {
long start=System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);// 提交任务
Long sum = submit.get();
long end=System.currentTimeMillis();
System.out.println("sum="+sum+"时间:"+(end-start));
}
//会使用stream流
public static void test3(){
long start=System.currentTimeMillis();
Long sum= LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end=System.currentTimeMillis();
System.out.println("sum="+sum+"时间:"+(end-start));
}
}
异步回调
Future设计初衷:对将来的某个事件的结果进行建模
JMM
请你谈谈对Volatile的理解
Volatile 是java虚拟机提供的轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
什么是JMM
JMM:Java内存模型,不存在的东西,概念,约定!
关于JMM的一些同步的约定
- 线程解锁前,必须把共享变量立刻刷回主存
- 线程加锁前,必须读取主存中的最新值到工作内存中!
- 加锁和解锁是同一把锁
Volatile
- 保证可见性
public class JMMDemo {
//不加 volatile 程序就会死循环!
//加 volatile 可以保证可见性//这里的原理是MESI和嗅探机制
private volatile static int num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{//线程1 对主内存的变化不知道
while (num==0){
}
}).start();
TimeUnit.SECONDS.sleep(1);
num = 1;
System.out.println(num);
}
}
- 不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰,也不能被分割。要么同时成功,要么同时失败。
//不保证原子性
public class VDemo02 {
private volatile static int num = 0;
public static void add(){
num++;
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(num);
}
}
如果不加Lock和Synchronized怎么样保证原子性
- 指令重排
什么是指令重排?
计算机并不是按照用户写的去执行。
Volatile 是可以保证可见性。不保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
彻底玩单例模式(必须会)
深入理解CAS
什么是CAS
大厂必须要深入研究底层!有所突破!
本文来自博客园,作者:NeverLateThanBetter,转载请注明原文链接:https://www.cnblogs.com/do-it-520/p/16729306.html
韶华易逝,不能虚度年华。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?