本篇内容是学习记录的一些笔记,在学习过程中有许多疑惑,通过多写Demo测试验证自己的想法,该过程比较杂乱无章,所以本篇内容更侧重于记录结论和自己的一些总结以及一些辅助自己容易回忆起的简单Demo等等。但也就如此而已,如果我真想不起来,还是更愿意去找网上的文章看看,毕竟人家的文章足够的好。
即使每个知识点网上都有,但我觉得通过自己整理过的东西会更容易理解,同时也能加深自己的记忆,而且在整理过程中,自己脑袋偶尔会产生一些想法,“如果我这么做,那他得到的结果会不会还是一样呢”,我不会放过它,这说不定能让我更深一步领悟该知识点。
interrupt()
方法不会让线程中断,只会给线程设置一个中断的标志(设置中断的flag为true),具体的中断仍需我们自己写程序来控制其他方法:
Thread.currentThread().isInterrupted()
:获取当前线程的中断标记,调用此方法不会改变中断的状态,也就是说这个只是简单的get操作Thread.interrupted()
:不同于上面的方法,这是一个静态方法, 也是获取当前线程的中断标记,但是调用此方法会改变中断的状态,清除了线程的中断标记。也就是说get操作后会将是否处于中断的标记设置为false。public boolean isInterrupted() {
return isInterrupted(false);
}
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
//demo
public class InterruptsTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i <= 99999; i++) {
if (Thread.interrupted()) {}
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + i + "线程处于中断状态");
System.out.println(Thread.currentThread().isInterrupted());
break;
}
System.out.println(Thread.currentThread().isInterrupted());
}
});
thread.start();
// 先睡一会觉
Thread.sleep(20);
// 再中断,
thread.interrupt();
System.out.println("main程序");
}
}
如果该线程在调用 Object 类的 wait()方法或 join()时被阻塞, sleep(long), 这个类的方法,那么它的中断状态会被清除并且会收到一个InterruptedException(中断异常)
如果遇到了中断异常,我们不必恐惧它,它的出现 说明我们在调用中断的时候被中断的线程中执行着sleep()、wait()、join()方法,可以利用这个异常来进行一些数据补偿之类的操作。
锁竞争问题,在高并发、线程数量高时会引起CPU占用居高不下,或者直接宕机。
银行存钱和取钱的例子
理解:自己拿到了资源自己可以反复的利用
一个线程在获得锁之后执行操作,发生错误抛出异常,则自动释放锁
Synchronized代码块:
不要在线程中修改对象锁的引用,引用被改变会导致锁失效
Person person = new Person();
synchronized(person){
//1
person.setName("周三");
//2
person = new Person();
}
使用关键字volatile
定义的变量也可以实现同样的效果
作用在类的变量上
volatile只能解决可见性,不能保证原子性
static变量的修改方式和普通变量一样都是先拷贝再修改后写回主内存,只有一份(唯一性)
volatile直接在主内存中修改(可见性、一致性),每个实例都有单独的变量,只是一个普通变量
原理是使用CAS
CAS-ABA问题:解决->(增加版本号、使用时间戳)
使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本
ThreadLocal
创建的变量,在子线程中获取不到InheritableThreadLocal
,且子线程之间不会互相影响使用InheritableThreadLocal
的
InheritableThreadLocal
,如果是的话就将父线程的值赋值给当前线程,否则赋值为null,所以他的get方法会返回父线程的值小结:
AtomicXXX类的底层就是依赖Unsafe类里的相关方法(本地方法)来完成原子性操作
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
Unsafe -突破安全限制
通过反射模式可以突破Unsafe类的安全限制
putInt、getInt、getAndSetInt、getAndAddInt、CAS、实例demo:
public class UnsafeDemo01 {
private int age;
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
UnsafeDemo01 demo01 = new UnsafeDemo01();
// 通过反射获取Unsafe的静态变量成员
Field field = Unsafe.class.getDeclaredField("theUnsafe");
// 设置无障碍
field.setAccessible(true);
// 强转获取Unsafe对象
Unsafe unsafe = (Unsafe) field.get(null);
// 获取age属性的内存偏移地址
long ageOffset = unsafe.objectFieldOffset(UnsafeDemo01.class.getDeclaredField("age"));
// 设置age值为11
unsafe.putInt(demo01, ageOffset, 11);
System.out.println("unsafe的put方法设置age :" + demo01.age);
// 获取age
int age = unsafe.getInt(demo01, ageOffset);
System.out.println("unsafe的get方法获取age : " + age);
// 验证CAS方法,如果它是10,设置为88,cas不一定保证成功,有默认的自旋次数,失败
boolean flag = unsafe.compareAndSwapInt(demo01, ageOffset, 10, 88);
System.out.println("CAS test :" + flag + " value is " + demo01.age);
// 成功
flag = unsafe.compareAndSwapInt(demo01, ageOffset, 11, 666);
System.out.println("CAS test :" + flag + " value is " + demo01.age);
// 获取并设置,返回的是旧的值
int setInt = unsafe.getAndSetInt(demo01, ageOffset, 333);
System.out.println("old value :" + setInt + "new value :" + demo01.age);
// 获取并增加
int addInt = unsafe.getAndAddInt(demo01, ageOffset, 7);
System.out.println("old value :" + setInt + "new value :" + demo01.age);
}
}
private static int age;
...
unsafe.staticFieldOffset(UnsafeDemo01.class.getDeclaredField("age"));
int age = unsafe.getInt(UnsafeDemo01.class, ageOffset);
如果是针对volatile变量操作,则几乎和普通变量没区别,只是put、get方法调用改成有volatile后缀的方法
如果是数组,则传入数组类模板来获取地址偏移量,设置数组元素值 使用偏移量+类型大小*下标
Unsafe unsafe = (Unsafe) field.get(null);
long arrOffset = unsafe.arrayBaseOffset(long[].class);
// 设置数组下标为1的元素的值为9
unsafe.putLong(demo01.getArray(),arrOffset + arrSize * 1)
**注意:**如果设置的大小超过了数组的长度,不会报错,但也不会扩容
public native long allocateMemory(long bytes);//分配内存
public native long reallocateMemory(long address, long bytes);//重新分配内存
public native void setMemory(Object o, long offset, long bytes, byte value);//初始化内存
public void setMemory(long address, long bytes, byte value)//初始化内存
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, longbytes);//复制内存
public void copyMemory(long srcAddress, long destAddress, long bytes);//复制内存
public native void freeMemory(long address);//释放内存
有C++基础理解起来不难
public class UnsafeDemo02 {
public static void main(String[] args) throws Exception {
UnsafeDemo01 demo01 = new UnsafeDemo01();
// 通过反射获取Unsafe的静态变量成员
Field field = Unsafe.class.getDeclaredField("theUnsafe");
// 设置无障碍
field.setAccessible(true);
// 强转获取Unsafe对象
Unsafe unsafe = (Unsafe) field.get(null);
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "--start");
// 线程挂起2000 * 100000 纳秒 = 2s
unsafe.park(false, 2000 * 1000000);
if (Thread.currentThread().isInterrupted()) {
System.out.println("Thread status is interrupted");
}
// 挂起当前时间往后推2s
//unsafe.park(true, System.currentTimeMillis() + 2000);
System.out.println(Thread.currentThread().getName() + "--end");
});
thread.start();
// 唤醒线程
unsafe.unpark(thread);
// 设置中断flag
//thread.interrupt();
System.out.println("main run over");
}
}
Concurrent
并发类容器:
COWlterator的弱一致性
并发-无阻塞队列
**ConcurrentLinkedQueue**
:无阻塞、无锁、高性能、无界、线程安全,性能优于BlockingQueue(并发阻塞)、不允许null值并发-阻塞队列
**ArrayBlockingQueue**
:基于数组实现的阻塞有界队列、创建时可指定长度,内部实现维护了一个定长数组用于缓存数据,内部没有采用读写分离,写入和读取数据不能同时进行,不允许null值使用的时候要注意,该队列的offer、add、put方法有很大区别
**LinkedBlockingQueue**
:基于链表的阻塞队列,内部维护一个链表存储缓存数据,支持写入和读取的并发操作,创建时可指定长度也可以不指定,不指定时代表无界队列,不允许null值**SynchronousQueue **
:没有任何容量,必须先有线程先从队列中take,才能向queue中add数据,否则会抛出队列已满的异常。不能使用peek方法取数据,此方法底层没有实现,会直接返回null**PriorityBlockingQueue**
:一个无界阻塞队列,默认初始化长度11,也可以手动指定,但是队列会自动扩容。资源被耗尽时导致OutOfMemoryError。不允许使用null元素。不允许插入不可比较的对象(导致抛出 ClassCastException),加入的对象实现comparable接口(维护了take取出元素的优先级)**DelayQueue**
:https://www.cnblogs.com/myseries/p/10944211.html**CountDownLatch**
是一个辅助工具类(可以理解为一个同步计数器),它允许一个或多个线程等待系列指定操作的完成。CountDownLatch 以一个给定的数量初始化。countDown()每被调用一次,这一数量就减一。通过调用await()方法,线程可以阻塞等待这一数量到达零(当计数器数值减为0时,所有受其影响而等待的线程将会被激活)。
它的作用就是会让所有线程都等待完成后才会继续下一步行动
CyclicBarrier 使用场景
可以用于多线程计算数据,最后合并计算结果的场景。(CountDownLatch也可以)
CyclicBarrier 与 CountDownLatch 区别
public class CyclicBarrierDemo {
public static void main(String[] args) {
test1();
}
private static void test1() {
int size = 4;
ExecutorService executorService = Executors.newFixedThreadPool(size);
CyclicBarrier cyclicBarrier = new CyclicBarrier(size, () -> {
System.out.println("最后一名是:" + Thread.currentThread().getName());
});
for (int i = 0; i < size; i++) {
Runnable r = () -> {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " :达到栅栏点A");
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + " :从栅栏点A出发");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " :达到栅栏点B");
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + " :从栅栏点B出发");
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
};
executorService.execute(r);
}
executorService.shutdown();
}
}
private static void test2() {
// 分为2个线程去查询,需要加1,主线程也必须算上.
int size = 3;
ExecutorService executorService = Executors.newFixedThreadPool(size);
CyclicBarrier cyclicBarrier = new CyclicBarrier(size, () -> {
System.out.println("查询结束");
});
AtomicInteger sum = new AtomicInteger();
Runnable r1 = ()->{
sum.getAndAdd(QueryUtil.querySaas());
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
};
Runnable r2 = ()->{
sum.getAndAdd(QueryUtil.queryDatabases());
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
};
executorService.execute(r1);
executorService.execute(r2);
try {
// 等待结果,主线程也必须参与等待
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("sum = " + sum);
executorService.shutdown();
}
class QueryUtil {
/**
* 模拟查询中台
*
* @return
*/
public static int querySaas() {
// 模拟业务耗时
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " :查询中台……");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return 10;
}
/**
* 模拟查询数据库
*
* @return
*/
public static int queryDatabases() {
// 模拟业务耗时
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " :查询数据库……");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return 5;
}
}
https://cloud.tencent.com/developer/article/1350849
Phaser(移相器,一种电子元件)是JDK7中引入的新的并发工具辅助类,oralce官网文档描述Phaser是一个可重复使用的同步栅栏,功能上与 CountDownLatch 和 CyclicBarrier类似但支持的场景更加灵活,这个类可能是目前并发包里面实现最复杂的一个了。
Phaser的灵活性主要体现在在构造函数时不需要强制指定目前有多少参与协作的线程,可以在运行时动态改变
package study.demo;
import java.util.Random;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;
/**
* @author yhchen
* @date 2022/9/13 19:40
*/
public class PhaserDemo5 {
public static void main(String[] args) throws InterruptedException {
Phaser phaser = new Phaser() {
@Override
protected boolean onAdvance(int phase, int registeredParties) {
System.out.println("=================step-" + phase + "===================" + registeredParties);
return super.onAdvance(phase, registeredParties);
}
};
Bus bus1 = new Bus(phaser, "小张");
Bus bus2 = new Bus(phaser, "小李");
Bus bus3 = new Bus(phaser, "小王");
bus1.start();
bus2.start();
bus3.start();
System.out.println(phaser.getRegisteredParties());
}
static public class Bus extends Thread {
private Phaser phaser;
private Random random;
public Bus(Phaser phaser, String name) {
this.phaser = phaser;
setName(name);
random = new Random();
phaser.register();
}
private void trip(int sleepRange, String cityName) {
System.out.println(this.getName() + " 准备去" + cityName + "....");
int sleep = random.nextInt(sleepRange);
try {
TimeUnit.SECONDS.sleep(sleep);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName() + " 达到" + cityName + "...... ");
if (this.getName().equals("小王1")) { // 测试掉队的情况
try {
TimeUnit.SECONDS.sleep(7);
} catch (InterruptedException e) {
e.printStackTrace();
}
phaser.arriveAndDeregister();
} else {
phaser.arriveAndAwaitAdvance();
}
}
@Override
public void run() {
try {
int s = random.nextInt(3);
TimeUnit.SECONDS.sleep(s);
System.out.println(this.getName() + " 准备好了,旅行路线=北京=>上海=>杭州 ");
phaser.arriveAndAwaitAdvance();// 等待所有的汽车准备好
} catch (InterruptedException e) {
e.printStackTrace();
}
trip(5, "北京");
trip(5, "上海");
trip(3, "杭州");
}
}
}
**Semaphore**
一个计数信号量。信号量维护了一个许可证集合,可以用来做流量分流。
Semaphore
内部维护了一组虚拟的许可,许可的数量可以通过构造函数的参数指定。公平性:没有办法保证线程能够公平地可从信号量中获得许可。也就是说,无法拉保掉第一个调用acquire()的线程会是第一个获得一个许可的线程。如果第一个线程在等待一个许可时发生阻塞,而第二个线程前来索要一个许可的时候刚好有一个许可被释放出来,那么它就可能会在第一个线程之前获得许可证。如果你想要强制公平,Semaphore类具有一个布尔类型的参数的构造子,通过这个参数以告知Semaphore是否要强制公平。强制公平会影响到并发性能,所以除非你确实需要它否则不要启用它。(默认不公平)
public class DemoSemaphore {
public static void main(String[] args) {
test1();
}
/**
* 模拟10个顾客点餐,只有2个服务员
*/
private static void test1() {
int size = 10;
Semaphore semaphore = new Semaphore(2);
ExecutorService executorService = Executors.newFixedThreadPool(size);
for (int i = 0; i < size; i++) {
Runnable r = () -> {
try {
System.out.println(Thread.currentThread().getName() + " :顾客呼叫服务员");
// 线程拿到许可证,可以继续执行
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "正在点餐……");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "点餐结束");
//执行完毕释放许可证,其他线程才能获取,若不释放,程序回一直阻塞
//semaphore.release();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
};
executorService.execute(r);
}
executorService.shutdown();
}
}
Exchanger 原理
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据, 如果第一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。因此使用Exchanger的重点是成对的线程使用exchange()方法,当有一对线程达到了同步点,就会进行交换数据。因此该工具类的线程对象是成对的
package com.concurrent.demo;
import java.util.concurrent.Exchanger;
/**
* @author cyh
* @date 2022/9/17 16:25
*/
public class DemoExChange {
public static void main(String[] args) {
test1();
}
public static void test1() {
Exchanger<String> exchanger = new Exchanger<>();
TaskExchange task1 = new TaskExchange(exchanger, "月饼");
TaskExchange task2 = new TaskExchange(exchanger, "蛋糕");
new Thread(task1, "小王").start();
new Thread(task2, "大明").start();
//new Thread(task2, "张三").start();
//new Thread(task2, "李四").start();
}
static class TaskExchange implements Runnable {
Exchanger<String> exchanger;
String goods;
public TaskExchange(Exchanger<String> exchanger, String goods) {
this.exchanger = exchanger;
this.goods = goods;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + " 拥有: " + goods);
try {
System.out.println(name + "交换出" + goods);
Thread.sleep(1000);
exchanger.exchange(goods);
System.out.println(name + " 交换到了: " + goods);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
ReentrantLock
可以用来替代synchronized
,在需要同步的代码块加上锁,最后一定要释放锁,否则其他线程永远进不来。
ReentrantLock使用方法
ReentrantLock.newCondition()
创建的每一个Condition对象,实质上都是AQS.ConditionObject
对象,而这个对象也是一个FIFO的队列
解决线程安全问题使用ReentrantLock就可以了,但是ReentrantLock是独占锁,某一时刻只有一个线程可以获取该锁,而实际中会有写少读多的场景,显然ReentrantLock满足不了这个需求,所以ReentrantReadWriteLock应运而生。ReentrantReadWriteLock采用读写分离的策略,允许多个线程可以同时获取读锁
ReentrantReadWriteLock使用方法
package com.concurrent.demo;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author cyh
* @date 2022/9/17 20:19
*/
public class DemoReentrantLock {
public static void main(String[] args) {
//test01();// 重入
test02();// 读写锁
}
private static void test02() {
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
Thread read1 = new Thread(() -> {
readLock.lock();
System.out.println(Thread.currentThread().getName() + "获取读锁……");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
readLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放读锁");
});
Thread read2 = new Thread(() -> {
readLock.lock();
System.out.println(Thread.currentThread().getName() + "获取读锁……");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
readLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放读锁");
});
Thread write1 = new Thread(() -> {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "获取写锁……");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
writeLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放写锁");
});
Thread write2 = new Thread(() -> {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "获取写锁……");
System.out.println(Thread.currentThread().getName() + "持有写锁次数:" + writeLock.getHoldCount());
bbb(writeLock);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
writeLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放写锁");
System.out.println(Thread.currentThread().getName() + "持有写锁次数:" + writeLock.getHoldCount());
});
// 读读不互斥
//read1.start();
//read2.start();
//读写互斥
//read1.start();
//write1.start();
//写写互斥
//write1.start();
write2.start(); //测试写锁重入
}
private static void bbb(ReentrantReadWriteLock.WriteLock writeLock) {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "持有写锁次数:" + writeLock.getHoldCount());
writeLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放写锁");
}
private static void test01() {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获得锁,当前锁数量:" + lock.getHoldCount());
Thread.sleep(1000);
aaa(lock);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁");
}
});
t1.start();
}
private static void aaa(ReentrantLock lock) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获得锁,当前锁数量:" + lock.getHoldCount());
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}
}
StampedLock
特点:**写写互斥、读写互斥、读读共享 **
StampedLock
与ReentrantReadWriteLock
功能类似
StampedLock.writeLock
,类似于ReetrantReadWriteLock.writeLock
,区别是StampedLock.writeLock
是不可重入锁。writeLock
与unlockWrite
必须成对儿使用,解锁时必须需要传入相对应的stamp才可以释放锁。每次获得锁之后都会得到一个新stamp值。StampedLock悲观读锁
悲观读锁并算不上绝对的悲观,排他锁才是真正的悲观锁,由于读锁具有读读共享的特性,所以对于读多写少的场景十分适用,可以大大提高并发性能。
StampedLock乐观读锁
https://www.jianshu.com/p/c9447688dc62
LockSupport
的底层采用Unsafe
类来实现,他是其他同步类的阻塞与唤醒的基础。ReentrantLock
的实现来说,state可以用来表示当前线程获取锁的可重入次数;ReentrantReadWriteLock
来说,state的高16位表示读状态,也就是获取该读锁的次数,低16位表示获取到写锁的线程的可重入次数;semaphore
来说,state用来表示当前可用信号的个数;CountDownlatch
来说,state用来表示计数器当前的值。在入FIFO同步等待队列前是否尝试获取锁,如果有,那就是非公平;如果已进入同步等待队列,那么则按先进先出的顺序被唤醒
ReentrantLock
就是以独占方式实现的,属于悲观锁ReadWriteLock
读写锁是以共享锁方式实现的,属于乐观锁StampedLock
的写锁,属于悲观锁。StampedLock
的乐观读锁、悲观读锁,属于乐观锁。次获取,如果尝试了指定次数之后仍然没有获得锁,再阻塞线程。
Random
存在性能缺陷,主要原因是要不断的计算新的种子更新原种子,使用CAS方法。高并发的情况下会造成大量的线程自旋,而只有一个线程会更新成功。ThreadLocalRandom
采用ThreadLocal
的机制,每一个线程都是用自己的种子去进行计算下—个种子,规避CAS在并发下的问题。SecureRandom坑记录——阻塞程序
SecureRandom.getInstanceStrong()
; 是jdk1.8里新增的加强版随机数实现
如果服务器在Linux操作系统上,这里的罪魁祸首是SecureRandom generateSeed()。它使用/dev/random生成种子。但是/dev/random是一个阻塞数字生成器,如果它没有足够的随机数据提供,它就一直等,这迫使JVM等待。键盘和鼠标输入以及磁盘活动可以产生所需的随机性或熵。但在一个服务器缺乏这样的活动,可能会出现问题
背景:AtomicLong
存在性能瓶颈,由于使用CAS方法。高并发的情况下会造成大量的线程自旋,而只有一个线程会更新成功,浪费CPU资源。
LongAdder的思想是将单一的原子变量拆分为多个变量,从而降低高并发下的资源争抢。
理解和总结:
AtomicLong
操作的使用CAS操作一个volatile修饰的变量,多个线程操作一个共享变量;LongAdder
类似分治归并,几个线程为一个单元操作一个变量,最后多个合并为一个没有incrementAndGet、decrementAndGet这种累加后获取值的原子性方法,只有单独的increment、longValue这种方法,如果组合使用则需要自己做同步控制,否则无法保证原子性。
LongAdder
是LongAccumulator
的特例,DoubleAdder
是DoubleAccumulator
的特列
Accumulator
牛b的地方在于可以设置初始值、自定义累加算法
***Accumulator 和 ***Adder 非常相似,实际上 ***Accumulator 就是一个更通用版本的 ***Adder,比如 LongAccumulator 是 LongAdder 的功能增强版,因为 LongAdder 的 API 只有对数值的加减,而 LongAccumulator 提供了自定义的函数操作。
使用方法
package study.demo;
import utils.LookTime;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.function.LongBinaryOperator;
/**
* @author yhchen
* @date 2022/9/20 15:37
*/
public class DemoLongAccumulator {
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
int size = 1000;
// 自定义累加算法
AtomicLong atomicLong = new AtomicLong();
LongBinaryOperator binaryOperator = (left, right) -> left + right;
//累加器 参数(累加算法,初始值)
LongAccumulator accumulator = new LongAccumulator(binaryOperator, 3);
System.out.println("初始值=" + accumulator.longValue());
// System.out.println("初始值=" + atomicLong.longValue());
ArrayList<Thread> list = new ArrayList<>();
LookTime.setStartTime();
// 创建1000个线程
for (int i = 0; i < size; i++) {
Thread thread = new Thread(() -> {
// 每个线程要对累加器加多少值
accumulator.accumulate(1);
// atomicLong.getAndAdd(1);
});
list.add(thread);
}
// 线程全部启动
for (Thread thread : list) {
thread.start();
}
// 等待线程全部执行完
for (Thread thread : list) {
thread.join();
}
LookTime.lookSpendMill();
System.out.println("最终值=" + accumulator.longValue());
// System.out.println("最终值=" + atomicLong.longValue());
}
}
](https://blog.csdn.net/inthat/article/details/108469200)四种线程池
**CachedThreadPool**
具有缓存性质的线程池,线程最大空闲时间60s,线程可重复利用(缓存特性),没有最大线程数限制。任务耗时端,数量大。**FixedThreadPool**
具有固定数量的线程池,核心线程数等于最大线程数,线程最大空闲时间为0,执行完毕即销毁,超出最大线程数进行等待。高并发下控制性能(控制适合机子的线程数量,让服务不会被打死)。**ScheduledThreadPool**
具有时间调度特性的线程池,必须初始化核心线程数,底层使**DelayedWorkQueue**
实现延迟特性。**SingleThreadExecutor**
核心线程数与最大线程数均为1,用于不需要并发顺序执行。总结:可以发现,这四个线程池都是通过new一个ThreadPoolExecutor来实现的,我们也可以通过这种方式来实现我们自己的线程池
jdk提供了四种拒绝策略
AbortPolicy
(默认):抛出异常,不影响其他线程运行CallerRunsPolicy
:调用当前任务(线程池没空闲线程了,使用调用的线程来执行,有点特别)DiscardOldestPolicy
丢弃最旧的任务DiscardPolicy
:直接丢弃,什么也不做可以发现,四个策略都是实现了RejectedExecutionHandler接口,我们也可以做实现改接口来自定义拒绝策略
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 6, 30L,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(10), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("做操作:打日志、发邮件、数据库操作等……");
}
});
}
}
我们通常使用线程池的submit方法将任务提交到线程池内执行。
如果此时线程池内有空闲的线程,则会立即执行该任务,如果没有则需要根据线程池的类型选择等待,或者新建线程。
所以线程池内的线程并不是线程池对象初始化( new )的时候就创建好的。而是当有任务被提交进来之后才创建的,而创建线程的过程是无法干预的。
如果我们想在每个线程创建时记录一些日志,或者推送一些消息那怎么做?
使用ThreadFactory
第一步: 编写ThreadFactory接口的实现类
第二步: 创建线程池时传入ThreadFactory对象
package study.thread01;
import java.util.concurrent.ThreadFactory;
/**
* @author yhchen
* @date 2022/9/27 21:15
*/
public class ThreadFactoryDemo implements ThreadFactory {
/**
* 执行标志
*/
private boolean flag;
/**
* 工厂名称
*/
private String factoryName;
public ThreadFactoryDemo(boolean flag, String factoryName) {
this.flag = flag;
this.factoryName = factoryName;
}
@Override
public Thread newThread(Runnable r) {
if (flag) {
System.out.println("线程池--创建线程前--吃了一个水果");
}
Thread thread = new Thread(r);
// 设置线程名字
thread.setName("改了个名字:血汗"+factoryName);
//执行各种 初始化等等
if (flag) {
System.out.println("线程创建完成后又吃了一个西瓜");
}
return thread;
}
}
package study.thread01;
import java.util.concurrent.*;
/**
* @author yhchen
* @date 2022/9/27 21:24
*/
public class ThreadFactoryClient {
public static void main(String[] args) {
Runnable runnable = () -> {
System.out.println("hhhhhhhhhh");
};
//创建工厂
ThreadFactoryDemo factoryDemo = new ThreadFactoryDemo(true, "工厂1");
//创建自定义线程池
ExecutorService executorService = Executors.newCachedThreadPool(factoryDemo);
for (int i = 0; i < 3; i++) {
executorService.execute(runnable);
}
executorService.shutdown();
}
}
只有使用Executors创建的线程池可以放这个线程创建工厂
execute()方法和submit()的区别
- 接收的参数不同
- 返回值不同
虽然submit()方法可以提交Runnable类型的参数,但执行Future方法的get()时,线程执行完会返回null,不会有实际的返回值,这是因为Runable本来就没有返回值
当用submit()提交线程时,run()orcall()方法尽量显式的catch异常,这样才不至于任务提交线程池后丢失异常信息
shutdown
让线程池内的任务继续执行完毕,但是不允许新的任务提交shutdown
方法不阻塞,等所有线程执行完毕后,销毁线程shutdown
之后提交的任务会抛出RejectedExecutionException
异常,代表拒绝接收shutdownNow
之后提交的任务会抛出RejectedExecutionException
异常,代表拒绝接收,也不会执行线程池内的任务shutdownNow
之后会引发sleep、join、wait方法的InterruptedException
异常,如果任务中没有触发InterruptedException
的条件,则任务会继续运行直到结束如果是使用Executors.newCachedThreadPool()创建的线程池,等线程执行完后程序不会马上停止,而是等待60s后(默认),线程池销毁,jvm才退出,如果是其他线程池有可能会无限等待,不会销毁,如果需要马上退出 在最后执行shutdown方法
扩展:
shutdownNow
试图终止线程的方法是通过调用Thread.interrupt()
方法来实现的。这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出allowsCoreThreadTimeOut
就用来做这个事应用场景:
- 核心线程开的比较多,需要销毁
prestartCoreThread
:预启动,每调用一次,线程池中初始化一个核心线程prestartAllCoreThreads
:全启动获取线程池的各种动态和静态数据,用于程序控制
ThreadPoolExecutor
,并覆写beforeExecute、afterExecute、terminated方法这样使用自己的实现的线程池来执行线程,切面功能会在每个线程生效
Future模式是多线程开发中常见的一种设计模式。它的核心思想是异步调用。当我们需要调用一个函数方法时。如果这个函数执行很慢,那么我们就要进行等待。但有时候,我们可能并不急着要结果。因此,我们可以让被调用者立即返回,让他在后台慢慢处理这个请求。对于调用者来说,则可以先处理一些其他任务,在真正需要数据的场合再去尝试获取需要的数据。
普通方式和Future模式的差别
普通模式是串行的,在遇到耗时操作的时候只能等待。
Future模式,只是发起了耗时操作,函数立马就返回了,并不会阻塞客户端线程。所以在工作线程执行耗时操作的时候客户端无需等待,可以继续做其他事情,等到需要的时候再向工作线程获取结果
public static void main(String[] args) {
LookTimeUtils.setStartTime();
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(QueryUtils::querySaas); //此方法耗时1s
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(QueryUtils::querySaas); //此方法耗时1s
int dataBase = QueryUtils.queryDataBase();//此方法耗时1s
// 可以将 future1-2塞到一个List里面
CompletableFuture.allOf(future1,future2).join();
LookTimeUtils.lookSpendMill(); //总共耗时1064ms
System.out.println(dataBase + future1.join()+future2.join());
}
class QueryUtils {
public static int querySaas() {
System.out.println(Thread.currentThread().getName() + "-查询中台");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "查询中台结束");
return 2;
}
public static int queryDataBase() {
System.out.println(Thread.currentThread().getName() + "-查询数据库");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "查询数据库结束");
return 1;
}
}
将耗时任务拆分,分到多个future去做,可提高效率
使用CompletionService类可以完成Master-Worker模式的开发
如图说明了CompletionService
的使用模式:
Executor
处理处理后的结果都会自动放入BlockedQueue
,另外一个线程不断的从队列里取得处理结果**CompletionService**
本质是线程池+阻塞队列
使用
注意
CompletionService
都很像,与大数据MapReduce也像CompletionService
的无序性