Java 并发编程JUC
并发与并行
Rob Pike 对并发与并行的描述
-
并发(concurrent)是同一时间应付(dealing with)多件事情的能力
-
并行(parallel)是同一时间做(doing)多件事情的你能力
对于单核CPU来说,多个线程是并发执行的,由操作系统的任务调度器调度线程间的轮流、交替执行
对于多核CPU来说,多个线程可以是并行执行的,有几核则可以同时运行几个线程
同步与异步
从方法调用的角度:
-
如果方法需要等待结果返回才能继续往下执行,则该方法是同步方法
-
如果方法不需要等待结果返回就能够继续往下执行,则该方法是异步方法
Java中可以通过采用多线程的方式让方法变为异步执行的
创建线程
-
方法一:
Thread t1 = new Thread() { @Override public void run() { System.out.println("线程1..."); } }; t1.setName("线程1") t1.start();
-
方式二:
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("线程2...");
}
};
Thread t2 = new Thread(runnable,"线程2");
t2.start();
//对于只有一个函数的接口称为函数式接口,可以使用lambda表达式简化
new Thread( () -> {
System.out.println("线程2...");
},"线程2...");
- 方式三
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("线程3...");
return 100;
}
});
new Thread(futureTask,"线程3").start();
System.out.println(futureTask.get());
//lambda表达式
new Thread(new FutureTask<Integer>(() -> {
System.out.println("线程3...")
return 100;
}),"线程3").start();
FutureTask<>是泛型接口,泛型类型为返回结果类型,继承了Runnable接口,接受一个Callable对象,Callable的call()方法能返回结果和抛出异常
栈与栈帧
Java中的栈是线程私有的,即一个线程有自己独立的栈空间
栈由栈帧组成,每调用一个方法就会生成一个栈帧,方法结束该栈帧空间就会被释放
public class StackDemo {
public static void main(String[] args) {
method1(100);
}
private static void method1(int x) {
int y = x * 2;
Integer integer = method2(y);
System.out.println(integer);
}
private static Integer method2(int y) {
return y * 2;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DCWsh6Bd-1626688014350)(images/StackDemo.png)]
线程的方法
- start()和run()
- start()方法表示启动一个新的线程来执行run里面的程序
- run()方法里面存放线程要执行的程序逻辑
如果单独调用run()方法则不会启动一个新的线程,必须调用start()方法来启动一个线程,start()方法内部会自动调用run()方法
- sleep()和yield()
- sleep()
- 调用sleep()方法会让当前线程由Running状态转换为Timed Waiting状态
- 其他线程能够使用interrupt()方法打断正在睡眠的线程,此时sleep()方法会抛出InterruptException异常
- yield()
- 调用yield()方法的线程会从Running状态转换为Runnable状态,从而让出CPU资源给其他线程
- sleep()
public class SleepAndYield {
public static void main(String[] args) {
Thread t1 = new Thread("t1"){
@Override
public void run() {
try {
Thread.sleep(1000);
// 使用TimeUnit类不仅能够使线程睡眠,并且具有更高的可读性
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println("InterruptException...");
}
}
};
System.out.println(t1.getState());
t1.start();
System.out.println(t1.getState());
// 主线程睡眠2秒
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t1.getState());
t1.interrupt();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QuzAWgfR-1626688014352)(images/SleepAndYield.png)]
-
Join()
-
在某线程内执行其他线程的join()方法,则该线程会等待调用join()方法的线程执行结束后才会往下执行
join(long time) : 表示最长等待时间
-
中断线程
- interrupt():中断线程
- isInterrupted():判断线程是否被中断,不会清除中断标记
- interrupted():判断线程是否被中断,会清除中断标记
当一个线程处于阻塞(sleep,wait)状态时被中断会抛出中断异常,此时的中断标记会被清除,需重新设置中断标记,即再次调用interrupt()方法
线程的状态
NEW
RUNNABLE
WAITING
TIMEED WAITING
BLOCKED
TERMINATED
对象锁Synchronized
synchronized实际上是使用了对象锁保证了临界区代码的原子性,使临界区内的代码对外不可分割,不会被线程切换影响
public void increment(){
for (int i = 0; i < 1000; i++) {
/* counter++操作被编译为字节码是会被转换为四个操作
1.获取counter变量
2.初始化counter变量
3.counter变量自增
4.把自增后的counter变量再赋值给counter
*/
synchronized(this){
// 对于同一个临界资源,必须使用同一个对象锁才能够保证同步
counter++;
}
}
}
public class SynchronizedDemo2 {
public static void main(String[] args) throws InterruptedException {
Account a = new Account(1000);
Account b = new Account(1000);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
a.tranfer(50,b);
}
},"a");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
b.tranfer(10,a);
}
},"b");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(a.getMoney() + b.getMoney());
}
}
class Account{
private int money;
public Account(int money){
this.money = money;
}
public void setMoney(int money){
this.money = money;
}
public int getMoney(){
return money;
}
public void tranfer(int count, Account target) {
synchronized (Account.class) {
if (this.money >= count) {
this.setMoney(this.money - count);
target.setMoney(target.money + count);
}
}
}
}
一个类是否具有线程安全问题,主要是取决于该类的状态是否会被改变,即一个类的属性是否能被改变
由于Java的虚拟机栈是线程私有的,则每一个线程在调用方法时,方法内的局部变量是私有的,故不会有线程安全问题,但如果该方法传入的是一个引用对象,是存放在堆中的会被多个线程共享,则可能是线程不安全的
Monitor概念
Monitor被称为监视器或管程
每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象加锁(重量级锁),则该对象的MarkWord的值会被设置为指向Monitor对象的地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Yj3HMvC-1626688014353)(images/Monitor结构.png)]
- 对象头
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G0EBaemY-1626688014355)(images/普通对象头.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZRvLPnx2-1626688014356)(images/数组对象头.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZsIosslI-1626688014357)(images/MarkWord64结构.png)]
- synchronized字节码原理
synchronized(obj){
//同步代码块
}
/* 对obj对象加锁时会如下步骤
1.获取obj对象的引用
2.将obj对象的引用存起来
3.将obj对象的对象头中的MarkWord设置为Monitor对象的指针(Monitorenter)
4.执行同步代码块
5.将步骤2中的obj对象引用加载出来
6.将obj对象的对象头的MarkWord重置,唤醒Monitor对象中的EntryList中的等待线程队列(Monitorexit)
*/
轻量级锁
多个线程执行临界区的时间是错开的,则应该使用轻量级锁来优化程序
每个栈帧都有包含有一个锁记录的结构,该结构能存储锁定对象的MarkWord
当给一个对象加锁时,会将锁记录的Object Reference指向对象的MarkWord,并把MarkWord设置加锁状态
锁膨胀
当一个线程想为一个对象加锁时,发现该对象已经被其他线程持有,这时就需要进行锁膨胀,将轻量级锁转为重量级锁,即为该对象关联到一个Monitor对象,并将该线程加入到Monitor对象的EntryList队列中等待
自旋优化
当一个线程申请一个重量级锁时,该锁却已被其他线程持有,则该线程将被阻塞,可以将该线程进行自旋来循环申请锁而避免阻塞(有可能在自旋期间锁被释放了,就可以获得锁而继续运行),当然,自旋的次数是有限制的
偏向锁
轻量级锁在没有竞争时,每次重入都需要进行CAS操作,而偏向锁可以解决这个问题
偏向锁会在线程第一次申请锁的时候将自己的线程Id设置到该对象锁的MarkWord中,以后该线程重入时发现对象锁中的线程id是自己就不用进行CAS操作了
当另一个线程使用到偏向锁时,偏向锁会升级为轻量级锁
如果一个线程撤销一个偏向锁次数达到20次,则该偏向锁会重新偏向于新的线程,如果撤销达到40次,则该对象锁会成为一个轻量级锁
锁消除
如果一个synchronized锁住的是一个局部变量,则当每个线程调用该方法时都会产生自己独立的栈帧,即对对象锁是不同的,故加锁反而会影响性能,故JVM在这种情况下会进行锁消除,即把sychronized语句忽略
wait() 和 notify()
wait()、notify()和notifyAll()是Object类的静态方法,这三个方法都需要在持有monitor锁的临界区内才能够被调用
- obj.wait():让持有锁的线程进入Monitor的waitSet集合中等待
- obj.notify():随机唤醒一个waitSet集合中的线程
- obj.notifyAll():唤醒全部在waitSet集合中的线程
wait()和notify()原理
当一个线程调用wait()方法时,则该线程会进入Monitor对象的WaitSet集合中等待,线程状态也由Running转换为Waiting,而当被notify()或notifyAll()唤醒时,则在WaitSet集合中等待的线程会进入EntryList中重新竞争锁
wait()和sleep()的区别
wait()是Object类的方法,sleep()是Thread类中的方法
wait()必须在同步代码块中执行,sleep可以在任何地方执行
wait()会释放锁,sleep()不会释放锁
boolean flag = false;
new Thread(() -> {
synchronized(lock){
while(flag){
lock.wait();
}
}
}).start();
new Thread(() -> {
synchronized(lock){
flag = ture;
lock.notifyAll();
}
}).start();
生存者与消费者
import java.util.LinkedList;
public class ProviderAndConsumer {
public static void main(String[] args) {
MessageQueue messageQueue = new MessageQueue(4);
for (int i = 0; i < 5; i++) {
int finalI = i;
new Thread(() -> {
messageQueue.provide(new Message("消息" + finalI));
},"provider").start();
}
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message consume = messageQueue.consume();
System.out.println("消费了消息:" + consume);
},"consumer").start();
}
}
class Message{
private Object val;
public Message(Object val) {
this.val = val;
}
}
class MessageQueue{
private int capaticy;
LinkedList<Message> messagesList = new LinkedList<>();
public MessageQueue(int capaticy) {
this.capaticy = capaticy;
}
public Message consume(){
synchronized (messagesList){
while (messagesList.isEmpty()){
try {
System.out.println("消息队列为空,请等待...");
messagesList.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Message message = messagesList.removeFirst();
System.out.println("消费了一条消息");
messagesList.notifyAll();
return message;
}
}
public void provide(Message message){
synchronized (messagesList){
while (messagesList.size() == capaticy){
System.out.println("消息队列已满,清等待...");
try {
messagesList.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
messagesList.addLast(message);
System.out.println("添加消息...");
messagesList.notifyAll();
}
}
}
park()和unpark()
park()和unpark()是LockSupport类中的方法,能够使指定线程暂停运行和继续运行
// 暂停当前线程
LockSupport.park();
//恢复某个线程的运行
LockSupport.unpark(线程对象);
每个线程都有自己的Parker对象,由_counter,_cond和_muter三个属性组成
park()和unpark()方法在暂停或恢复某个线程时,会先判断当前线程的Parker对象中的counter值,如果counter值为0则暂停线程,为1则继续运行线程,在某线程在运行时调用unpark()方法会设置该线程的Parker对象的counter值为1,当进行park()时并不会暂停线程
死锁
多线程环境下,如果一个线程需要多把不同的锁,则该线程容易发生死锁
哲学家进餐问题
public class PhilosopherEatDemo {
public static void main(String[] args) {
ChopSticks chopSticks = new ChopSticks("1");
ChopSticks chopSticks1 = new ChopSticks("2");
ChopSticks chopSticks2 = new ChopSticks("3");
ChopSticks chopSticks3 = new ChopSticks("4");
ChopSticks chopSticks4 = new ChopSticks("5");
new Philosopher("哲学家A",chopSticks,chopSticks1).start();
new Philosopher("哲学家B",chopSticks1,chopSticks2).start();
new Philosopher("哲学家C",chopSticks2,chopSticks3).start();
new Philosopher("哲学家D",chopSticks3,chopSticks4).start();
new Philosopher("哲学家E",chopSticks4,chopSticks1).start();
}
}
class Philosopher extends Thread{
private String name;
private ChopSticks left;
private ChopSticks right;
public Philosopher(String name, ChopSticks left, ChopSticks right) {
this.name = name;
this.left = left;
this.right = right;
}
@Override
public void run() {
while(true){
synchronized(left){
System.out.println(name + ":获得左边的筷子");
synchronized (right){
System.out.println(name + ":获得右边的筷子");
eat(name);
}
}
}
}
public void eat(String name){
System.out.println(name + "吃...");
}
}
class ChopSticks{
private String name;
public ChopSticks(String name) {
this.name = name;
}
@Override
public String toString() {
return "ChopSticks{" +
"name='" + name + '\'' +
'}';
}
}
定位死锁
- jstack
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nWRhYxfm-1626688014359)(images/jstack.png)]
- jconsole
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zfnMD7Au-1626688014360)(images/jconsole.png)]
活锁
活锁出现在线程之间相互改变对方的结束条件,使线程无法结束
public class AliveLock {
static int counter = 10;
public static void main(String[] args) {
new Thread(() -> {
while (counter > 0){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( "减操作:" + counter);
counter--;
}
}).start();
new Thread(() -> {
while (counter < 20){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("加操作:" + counter);
counter++;
}
}).start();
}
}
饥饿
如果一个线程在很长的一段时间都得不到CPU调度执行,一直处于就绪状态,这种现象称为线程饥饿
ReentrantLock
- 特点
- 可中断:lock.lockInterruptibly()方法可被interrupt()方法中断
- 可设置超时时间:lock.tryLock(long , timeUtil)在一定时间范围内尝试获取锁,获取不到则放弃
- 支持多个条件变量:lock.newCondition()可为一把锁设置多个条件变量
- 支持公平锁:new ReentrantLock(true),boolean参数表示是否开启公平锁,默认为false
// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
Java内存模型JMM
JMM(Java Menory Model)定义了主存与线程工作内存的抽象,底层对应着CPU寄存器、缓存、内存,CPU指令优化等
JMM主要有三个方面
- 原子性:保证指令的执行不会受到线程上下文切换的影响
- 可见性:保证指令不会受到CPU缓存的影响,即某一线程对变量的修改对其他线程是可见的
- 有序性:保证指令的执行顺序不会受到CPU指令优化的影响
可见性
public class AtomicDemo {
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (run){
System.out.println("执行...");
}
}).start();
new Thread(() -> {
try {
Thread.sleep(1000);
run = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KYc7keAK-1626688014361)(images/volatile.png)]
volatile原理
volatile的底层原理是内存屏障(Memory Barrier)
- 对volatile变量的写指令后会加入写屏障
- 对volatile变量的度指令前会加入读屏障
写屏障与读屏障
- 写屏障保证在该屏障之前,对共享变量的改动,都写回到主存中
- 读屏障保证在该屏障之后,对共享变量的读取都是读取主存中的最新变量
- 读写屏障保证在屏障前后的指令都是有序执行的,不会发生指令优化重排序
Balking模式
public class BalkingDemo {
public static void main(String[] args) {
TestBalking testBalking = new TestBalking();
new Thread(() -> {
testBalking.init();
}).start();
new Thread(() -> {
testBalking.init();
}).start();
new Thread(() -> {
testBalking.init();
}).start();
}
}
class TestBalking{
static volatile boolean flag = false;
public void init(){
synchronized (TestBalking.class){
if (flag){
System.out.println("只能初始化一次...");
return;
}
doInit();
flag = true;
}
}
private void doInit(){
System.out.println("初始化...");
}
}
DCL双重检测锁
public class DCLDemo {
private static SingletonTest instance1;
private static SingletonTest instance2;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
instance1 = SingletonTest.getInstance();
}).start();
new Thread(() -> {
instance2 = SingletonTest.getInstance();
}).start();
Thread.sleep(2000);
System.out.println(instance1 == instance2);
}
}
class SingletonTest{
private static volatile SingletonTest singleton = null;
private SingletonTest(){
}
public static SingletonTest getInstance(){
if(singleton == null){
synchronized (SingletonTest.class){
if (singleton == null){
singleton = new SingletonTest();
}
}
}
return singleton;
}
}
synchronized关键字能够保证原子性,可见性,有序性,是对在synchronized代码块内的程序可保证,而在上例中,由于singleton变量再synchronized代码块外使用了,故不能保证
CAS(比较并交换)
import javax.swing.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
public static void main(String[] args) throws InterruptedException {
CASTest casTest = new CASTest(1000);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
casTest.withDrap(100);
}).start();
}
Thread.sleep(1000);;
System.out.println(casTest.getBalance());
// new Thread(() -> {
// casTest.withDrap(100);
// }).start();
}
}
class CASTest{
private AtomicInteger balance;
public CASTest(int balance) {
this.balance =new AtomicInteger(balance);
}
public int getBalance(){
return balance.get();
}
public void withDrap(int account){
while (true){
int current = balance.get();
int next = current - account;
// compareAndSet会比较当前current的值与主存中的current值,如果两个current值相等则会将主存中的current值设置成next,如果不相等则不设置,重新循环直至设置成功
if (balance.compareAndSet(current,next)){
break;
}
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-arOXw6Yy-1626688014362)(images/CAS.png)]
CAS(compareAndSet)底层是lock cmxchg指令,在单核和多核CPU架构中都能保证原子性
CAS必须借助volatile关键字来保证线程读取的数据是主存中的最新数据,在原子类中的value就是使用了volatile关键字修饰的
CAS的特点
结合CAS和volatile能够实现乐观锁(无锁)机制,适合与线程数少,多核CPU的情况
乐观锁
乐观锁认为主存中的数据并不会频繁被其他线程修改,可采用无锁来访问共享变量
悲观锁
悲观锁认为主存中的数据会频繁被修改,故线程需要采取加锁的形式来保证自己读取到的数据时真实的
原子类
JUC包下提供的原子类(AtomicInteger、AtomicBoolean、AtomicLong)都是线程安全的,它们的实现依赖于CAS和volatile
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class AtomicTest {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(1);
AtomicBoolean atomicBoolean = new AtomicBoolean(false);
AtomicLong atomicLong = new AtomicLong(1l);
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.addAndGet(2));
int i = atomicInteger.updateAndGet(getvalue -> {
return getvalue * 10;
});
System.out.println(i);
System.out.println(atomicBoolean.get());
System.out.println(atomicBoolean.compareAndSet(false, true));
System.out.println(atomicBoolean);
System.out.println(atomicLong.getAndIncrement());
System.out.println(atomicLong.get());
}
}
原子引用类
- AtomicReference:只能比较当前值与主存中的值是否一致
- AtomicMarkableReference:可以为主存中的值进行标记(是否被修改过)
- AtomicStampedReference:可以为主存中的值设置版本号(修改一次设置一个版本号)
ABA问题
import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABAQuestion {
public static void main(String[] args) throws InterruptedException {
// AtomicReference原子引用类并不能辨别出A是否被其他线程修改过了,只是比较要修改的值是否一样
AtomicReference<String> A = new AtomicReference<>("A");
System.out.println(A.get());
new Thread(() -> {
if (A.compareAndSet("A","B")) {
System.out.println("修改成功:A->B");
}
}).start();
new Thread(() -> {
if (A.compareAndSet("B","A")) {
System.out.println("修改成功:B->A");
}
}).start();
Thread.sleep(1000);
if (A.compareAndSet("A","B")) {
System.out.println("主线程:A->B");
}
AtomicMarkableReference<String> mA = new AtomicMarkableReference<>("A", false);
System.out.println(mA.getReference().toString());
new Thread(() -> {
if (mA.compareAndSet("A","B",false,true)) {
System.out.println("修改成功:A->B");
}
}).start();
new Thread(() -> {
if (mA.compareAndSet("B","A",true,false)) {
System.out.println("修改成功:B->A");
}
}).start();
Thread.sleep(1000);
if (mA.compareAndSet("A","B",true,false)) {
System.out.println("主线程:A->B");
}
AtomicStampedReference<String> sA = new AtomicStampedReference<String>("A",1);
System.out.println(sA.getReference().toString());
new Thread(() -> {
if (sA.compareAndSet("A","B",1,2)) {
System.out.println("修改成功:" + sA.getReference().toString() + "---" + sA.getStamp());
}
}).start();
new Thread(() -> {
if (sA.compareAndSet("B","A",2,3)) {
System.out.println("修改成功:" + sA.getReference().toString() + "---" + sA.getStamp());
}
}).start();
Thread.sleep(1000);
if (sA.compareAndSet("A","B",1,2)) {
System.out.println("主线程-----修改成功");
}else {
System.out.println("修改失败");
System.out.println("当前:" + sA.get(new int[]{1,2,3})+ + sA.getStamp());
}
}
}
原子数组
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
字段更新器
使用字段更新器,可以针对对象中的 某个属性进行原子操作,但这个属性必须被volatile修饰,否则会出现异常
- AtomicReferenceFieldUpdater
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
原子累加器
- AtomicLong.getAndIncrement()
- LongAdder.increment():设置多个累加单元,效率更高
缓存行与伪共享
CPU的缓存是以缓存行为单位的,每一个缓存行对应着一块内存,在多核CPU的情况下,主存中的数据会被多个CPU共同读取而存储到各自的缓存中,而当有一个CPU对该数据进行修改时就要刷会主存中,而其他CPU对该数据的读取还是在自己的缓存中,这样就不能感知主存中被其他CPU修改后的实时数据,而造成了伪共享
Unsafe
Unsafe对象提供了操作线程,内存的接口,Unsafe对象不能够直接构造,可以通过反射来获取
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class AtomicDataDemo {
private volatile Object data;
static Unsafe unsafe = null;
static long dataOffset = 0;
public static void main(String[] args) {
AtomicDataDemo atomicDataDemo = new AtomicDataDemo();
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
Field data1 = AtomicDataDemo.class.getDeclaredField("data");
long offset = unsafe.objectFieldOffset(data1);
unsafe.compareAndSwapObject(atomicDataDemo,offset,null,"张三");
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
System.out.println(atomicDataDemo.data);
}
}
连接池实现案例
import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicIntegerArray;
public class PoolDemo {
public static void main(String[] args) {
PoolTest poolTest = new PoolTest(2);
for (int i = 1; i <= 5; i++) {
int finalI = i;
new Thread(() -> {
Connection connection = null;
try {
connection = poolTest.getConnection();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
poolTest.free(connection);
}
},"t" + i).start();
}
}
}
class PoolTest{
// 连接线程池的大小
private int poolSize;
// 保存连接线程的数组
private Connection[] connections;
// 保存数组中每一个连接的状态,0:空闲,1:繁忙
private AtomicIntegerArray state;
public PoolTest(int poolSize) {
this.poolSize = poolSize;
this.connections = new Connection[poolSize];
this.state = new AtomicIntegerArray(new int[poolSize]);
for (int i = 0; i < poolSize; i++) {
connections[i] = new ConnectionTest("conn" + i);
}
}
// 获取连接
public Connection getConnection(){
while(true){
for (int i = 0; i < poolSize; i++) {
if (state.get(i) == 0) {
if (state.compareAndSet(i,0,1)) {
System.out.println( Thread.currentThread().getName() + "获取到一条连接..." + connections[i]);
return connections[i];
}
}
}
synchronized (this){
try {
System.out.println( Thread.currentThread().getName() + ":目前没有空闲连接,等待...");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 归还连接
public void free(Connection conn){
for (int i = 0; i < poolSize; i++) {
if (conn == connections[i]) {
state.set(i,0);
System.out.println(Thread.currentThread().getName() + "归还了一条连接..." + conn);
synchronized (this){
this.notifyAll();
}
break;
}
}
}
}
class ConnectionTest implements Connection {
private String name;
public ConnectionTest(String name) {
this.name = name;
}
@Override
public String toString() {
return "ConnectionTest{" +
"name='" + name + '\'' +
'}';
}
// Connection的方法实现略...
}
final原理
对于使用了final关键字修饰的变量,对变量进行赋值的时候会在赋值后加入写屏障
ThreadPoolExecutor
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OltZgVkp-1626688014363)(images/ExecutorService.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xLBp7Dye-1626688014364)(images/ThreadPoolExecutor.png)]
ThreadPoolExecutor
ThreadPoolExecutor使用AtomicInteger的高3位来表示线程状态,剩余的29位表示线程数量
// 将线程池的状态和数量用一个变量来表示,这样的好处是对这两个属性的操作在一次CAS操作中就可以完成
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
// SHUTDOWN表示优雅的停止线程服务,即线程池中的线程不会再接受新任务,但会继续执行完任务队列中的任务
private static final int SHUTDOWN = 0 << COUNT_BITS;
// STOP会中止正在执行的任务,也不会执行任务队列中的任务,强制停止线程服务
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
- ThreadPoolExecutor构造方法
- corePoolSize:核心线程数大小
- maximumPoolSize:最大线程数大小
- keepAliveTime:救急线程的生存时间
- unit:生存时间的时间单位
- workQueue:任务队列
- handler:拒绝策略
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
-
线程池的工作方式
线程池中一开始并没有线程,当一个任务提交给线程池时,线程池才会创建线程来执行新任务
当线程池中的线程数已经达到核心线程数大小时,再给线程池提交任务时会把该任务加入线程池的任务阻塞队列中,如果该队列是一个有界的阻塞队列,则会创建救急线程来执行该任务(救急线程数量等外最大线程数减去核心线程数)
如果线程达到最大线程数,新提交的任务线程池没法再创建线程来执行,则会执行相应的拒绝策略
-
拒绝策略
- AbortPolicy:抛出RejectedExecutionException异常,默认拒绝策略
- CallerRunsPolicy:让调用者自己执行任务
- DiscardPolicy:放弃本次任务
- DiscardOldesPolicy:放弃任务队列中最老的任务,并将新任务进入队列中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YNC8E5ty-1626688014365)(images/RejectPolicy.png)]
newFixedThreadPool
- 构造函数
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory){
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
- 案例
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
int finalI = i;
fixedThreadPool.execute(() -> {
System.out.println("第" + finalI + "任务");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
newCachedThreadPool
- 构造
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
- 案例
// cachedThreadPool没有核心线程数,即所有线程都是救急线程,救急线程的生存时间是60秒
// cachedThreadPool采用SynchronousQueue来实现任务队列,该队列的特点是没有容量,当线程池中有线程来该队列区任务时,才能够将任务放进去给线程执行
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
int finalI = i;
cachedThreadPool.execute(() -> {
System.out.println("第" + finalI + "任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
newSingleThreadExecutor
- 构造
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1, threadFactory));
}
- 案例
// newSingleThreadExecutor()构造的线程池中的线程数固定为1,当任务多余1时会将任务放入无界的任务队列中
// 当前任务执行完后会循序从任务队列中取出任务执行,当任务全部执行完毕后,该唯一线程也不会释放
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
int finalI = i;
singleThreadExecutor.execute(() -> {
System.out.println("第" + finalI + "任务");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
- 特点
- 与自己创建一个线程来执行任务相比,如果任务执行异常,该唯一线程不会释放,而会从任务队列中取新任务或等待新任务并且执行
- Executor.newSingleThreadExecutor()创建的线程数固定为1,不能被修改,使用了FinalizableDelegatedExecutorService进行装饰,对外则暴露了ExecutorService接口的方法
- Executor.newFixedThreadPool(1)虽然初始了线程池大小为1,但以后可以使用setCorePoolSize()方法进行更改
提交任务
//执行Runnable任务
void execute(Runnable command);
//执行Callable<T>任务,能够返回结果和抛出异常,返回结果类型类T
//方法类型上的泛型会覆盖类上的泛型类型,即该方法可以传入任意类型,不会受到类上的泛型的影响
<T> Future<T> submit(Callable<T> task)
//提交多个任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
//提交多个任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout,TimeUnit,unit) throws InterrupedException
//提交多个任务,但只要任务列表中有一个执行完毕返回结果,则其他任务放弃执行
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException,ExecutionException
//提交多个任务,但只要任务列表中有一个执行完毕返回结果,则其他任务放弃执行,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException
关闭线程池
/*
线程池状态变为 SHUTDOWN
- 不会接收新任务
- 但已提交任务会执行完
- 此方法不会阻塞调用线程的执行
*/
void shutdown();
/*
线程池状态变为 STOP
- 不会接收新任务
- 会将队列中的任务返回
- 并用 interrupt 的方式中断正在执行的任务
*/
List<Runnable> shutdownNow()
// 不在 RUNNING 状态的线程池,此方法就返回 true
boolean isShutdown();
// 线程池状态是否是 TERMINATED
boolean isTerminated();
// 调用 shutdown 后,由于调用线程并不会等待所有任务运行结束,因此如果它想在线程池 TERMINATED 后做些事
情,可以利用此方法等待
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
任务调度线程池
可以使用 java.util.Timer 来实现定时功能,Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务
- Executors.newScheduledThreadPool()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolDemo {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
scheduledExecutorService.schedule(() -> {
System.out.println("running1...");
},2, TimeUnit.SECONDS);
System.out.println("main....");
// 如果间隔时间period小于任务的执行时间,则任务的间隔时间是任务的执行时间
scheduledExecutorService.scheduleAtFixedRate(() -> {
System.out.println("running...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},2,2,TimeUnit.SECONDS);
// scheduledWithFixedDelay()方法是会在任务执行完毕后延时delay后再开设下一个任务,故每个任务的间隔时间为:任务的执行时间 + delay
scheduledExecutorService.scheduleWithFixedDelay(() -> {
System.out.println("run");
},2,2,TimeUnit.SECONDS);
}
}
线程池的数量多少合适
-
过小会导致程序不能充分地利用系统资源、容易导致饥饿
-
过大会导致更多的线程上下文切换,占用更多内存
CPU密集型程序
通常采用 cpu 核数 + 1 能够实现最优的 CPU 利用率,+1 是保证当线程由于页缺失故障(操作系统)或其它原因导致暂停时,额外的这个线程就能顶上去,保证 CPU 时钟周期不被浪费
I/O密集型程序
CPU 不总是处于繁忙状态,例如,当你执行业务计算时,这时候会使用 CPU 资源,但当你执行 I/O 操作时、远程RPC 调用时,包括进行数据库操作时,这时候 CPU 就闲下来了,你可以利用多线程提高它的利用率。经验公式如下:
线程数 = 核数 * 期望 CPU 利用率 * 总时间(CPU计算时间+等待时间) / CPU 计算时间
Fork/Join
Fork/Join是JDK1.7新增的线程池实现,采用了分治思想,将一个任务拆分为多个小任务给多个线程同时执行以提高效率,适合于CPU密集型的程序
Fork/Join默认会创建于CPU核心数相同的线程池
-
使用
提交给Fork/Join的任务必须是实现了RecursiveTask(有返回值)或RecursiveAction(没返回值)的类
ForkJoinPool pool = new ForkJoinPool(); class AddTask extends RecursiveTask<T>{ private T data; @Override protected T compute(){ //任务逻辑,并将这个任务在compute方法中进行拆分 //小任务.fork():执行小任务 //小任务.join():获取小任务的运行结果 } }
AQS
AQS(AbstractQueuedSchronizer)抽象队列同步器,定义了一套多线程访问共享资源的同步器框架
采用了先进先出(FIFO)的阻塞队列,是一种单向无锁的队列,不支持优先级队列
-
特点
- 用state来表示资源的状态(独占模式或共享模式):getState(),setState(),compareAndSetState()
-
原理
-
加锁
-
while(资源不可用){ if(不可重入){ //当前线程加入队列 } } 当前线程处队
-
解锁
-
if(资源可用){ //恢复阻塞线程 }
-
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class AQSDemo {
public static void main(String[] args) throws InterruptedException {
MyLock myLock = new MyLock();
new Thread(() -> {
myLock.lock();
try {
System.out.println("mylock...");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
myLock.unlock();
}
}).start();
Thread.sleep(1000);
myLock.lock();
try {
System.out.println("main...");
}finally {
myLock.unlock();
}
}
}
//自定义同步器
class MySync extends AbstractQueuedSynchronizer{
@Override
protected boolean tryAcquire(int arg) {
if(arg == 1){
if (compareAndSetState(0,1)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
if(arg == 1){
if(getState() == 0){
throw new IllegalMonitorStateException();
}
setState(0);
setExclusiveOwnerThread(null);
return true;
}
return false;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
protected Condition newConditon(){
return new ConditionObject();
}
}
class MyLock implements Lock{
static MySync mySync = new MySync();
@Override
public void lock() {
mySync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
mySync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return mySync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return mySync.tryAcquireNanos(1,unit.toNanos(time));
}
@Override
public void unlock() {
mySync.release(1);
}
@Override
public Condition newCondition() {
return mySync.newConditon();
}
}
ReentrantLock
-
非公平锁
-
// new ReentrantLock()默认是非公平锁,可传入true构造公平锁 public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
-
Sync是ReentrantLock的内部类,继承了AbstractQueuedSynchronizer,有NonfairSync和FairSync两个子类,分别实现了非公平锁和公平锁机制
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7g11BDgx-1626688014365)(images/Sync.png)]
-
AQS内部结构
-
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { //exclusiceOwnerThread是AbstractOwnableSynchronizer类中的属性 //代表了资源持有者的线程 private transient Thread exclusiveOwnerThread; private transient volatile Node head; private transient volatile Node tail; private volatile int state; //静态内部类Node抽象了线程的队列 static final class Node { //共享模式 static final Node SHARED = new Node(); //独占模式 static final Node EXCLUSIVE = null; //waitStatus表示Node中的每个线程的状态(CANCELLED、SIGNAL、CONDITION、PROPAGATE,0)每一个状态代表了一个职务 volatile int waitStatus; //CANCELLED表示线程节点被取消了(timeout、interrupt) static final int CANCELLED = 1; //SIGNAL表示该线程有义务区唤醒(unpark)它的下一个节点 static final int SIGNAL = -1; //CONDITION表示线程在等待条件,即该线程在条件队列中 static final int CONDITION = -2; static final int PROPAGATE = -3; //0:表示当前节点在sync队列中,等待着获取锁 //前驱节点 volatile Node prev; //后继节点 volatile Node next; //节点所对应的线程 volatile Thread thread; //下一个等待的节点 Node nextWaiter; } //内部类ConditionObject抽象了线程的条件队列 public class ConditionObject implements Condition, java.io.Serializable { /** First node of condition queue. 条件队列中的第一个线程*/ private transient Node firstWaiter; /** Last node of condition queue. */ private transient Node lastWaiter; } }
-
结构图
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0cFA99SP-1626688014366)(images/AQSStructure.png)]
-
-
-
流程
- 加锁
- 如果资源持有者为null,即state为0,则将exclusiveOwnerThread设置为当前线程,并将资源状态state设置为1
- 如果资源持有者不为null,即state为1,则会循环尝试获取,如果失败则将当前线程加入Node队列中
- 解锁
- 当exclusiveOwnerThread设置为null,并将资源状态state设置为0
- 加锁
-
条件变量实现原理
- 每个条件变量其实对应着一个等待队列,其实现类是ConditionObject
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LlYT5Zth-1626688014367)(images/ConditionObject.png)]
- 流程
- await():持有锁的线程调用await()方法后,进入ConditionObject的addConditionWaiter流程,将该线程的waitstatus修改为-2(CONDITION),加入到等待队列尾部,然后释放锁
- signal():持有锁的线程调用signal()方法后,进入ConditionObject的doSignal()流程,取出等待队列中的第一个线程执行,并修改相关线程的waitStatus的状态
读写锁ReentrantReadWriteLock
通常一个程序的读操作往往比写操作频繁,但是读取数据通常不会发生同步问题,所以可以使用读写锁来提高程序的性能,即使用读锁使读操作能够并行执行,使用写锁来保证线程访问安全
Semaphore信号量
Semaphore可以用来限制同时访问资源的线程数量,采用了信号量机制
CountdownLatch
CountdownLatch可以设置一个正整数,调用await()方法的线程需要等待这个正整数为0时才能向下执行,否则就会阻塞,而这个正整数可以通过调用countDown()方法来进行减1操作
-
案例
-
import java.util.Arrays; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; public class CountdownLatchDemo { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(); ExecutorService executorService = Executors.newFixedThreadPool(10, (r) -> { return new Thread(r,"t" + atomicInteger.addAndGet(1)); }); CountDownLatch countDownLatch = new CountDownLatch(10); String all[] = new String[10]; Random random = new Random(); for (int i = 0; i < 10; i++) { int finalI = i; executorService.submit(() -> { for (int j = 0; j <= 100; j++) { try { Thread.sleep(random.nextInt(200)); } catch (InterruptedException e) { e.printStackTrace(); } all[finalI] = Thread.currentThread().getName() + "(" + j + "%" +")"; System.out.print("\r" + Arrays.toString(all)); } countDownLatch.countDown(); }); } try { countDownLatch.await(); System.out.println("\n 欢迎来到..."); } catch (InterruptedException e) { e.printStackTrace(); } executorService.shutdownNow(); } }
-
CyclicBarrier
循环栅栏,用来进行线程协作,等待线程满足某个计数。构造时设置『计数个数』,每个线程执
行到某个需要“同步”的时刻调用 await() 方法进行等待,当等待的线程数满足『计数个数』时,继续执行
线程安全集合类
- 遗留的线程安全集合
- Hashtable
- Vector
- 修饰的线程安全集合
- SynchronizedMap
- SynchronizedList
- JUC线程安全集合
- Blocking类
- CopyOnWrite类
- Concurrent类
弱一致性
Concurrent包下的线程安全集合类存在弱一致性的缺点,即当有线程遍历集合中的数据时,其他线程修改了集合中的数据,并不会抛出异常,则遍历线程读取到集合数据时过时的数据,即一致性得不到保障,而对于非线程安全的集合类在遍历时如果集合数据被修改了会抛出异常
防止线程占用CPU100%
new Thread(){
@Override
public void run(){
while(true){
//通过让线程睡眠而使线程让出CPU资源
Thread.sleep(1000);
}
}
}.start();