JUC详解
JUC
1.什么是JUC
JUC实际上就是java.util下的一个工具包,多线程中的Lock类,Callable接口都在此工具包下
2.回顾进程与线程
-
程序:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
-
进程:进程则是执行程序的一次过程,它是一个动态的概念。是系统资源分配的单位。
-
线程:线程是CPU调度和执行的的单位。通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。
java默认有2个线程:main,GC
java可以开启线程吗?
java没有权限开启线程,只能调用底层的方法,java是运行在虚拟机上的,没有权限操控硬件。
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
//调用本地的方法启动线程
private native void start0();
并发与并行:
- 并发:同一个时间周期内,处理多件事情
- 并行:在同一时间,同时进行2件或多件事情
单核的cpu在快速交替时可以做到并发,但不能做到并行
public static void main(String[] args) {
//获取cpu的核数
//CPU密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
并发编程的本质:充分利用CPU的资源!
线程状态(源码层面):
public enum State {
//新生状态
NEW,
//运行状态
RUNNABLE,
//阻塞状态
BLOCKED,
//等待状态,会一直等待
WAITING,
//超时等待状态,等待超过一定时间后,就会退出等待状态
TIMED_WAITING,
//终止状态
TERMINATED;
}
sleep和wait方法的区别:
- sleep =>Thread wait=>Object
- sleep使用时不限制位置,wait必须在同步代码块中
- sleep不会释放锁,wait会释放锁
3.Synchronized与Lock锁
1.Lock锁
首先,Lock是一个接口,它有三个实现类,ReentrantLock(可重入锁),ReentrantReadWriteLock.ReadLock(可读写锁.读锁),ReentrantReadWriteLock.WriteLock(可读写锁.写锁),常用是ReentrantLock(可重入锁)
ReentrantLock(可重入锁)的方法:
- 加锁:lock()
- 尝试获取锁:tryLock()
- 解锁:unlock
公平锁与非公平锁:
- 公平锁:先来后到,一定要排队执行
- 非公平锁:可以插队
- 可重入锁默认使用非公平锁,因为公平,例:性能倒置问题(3h的线程排在3s的线程前时)
2.两者的比较
多线程卖票产生的并发问题,使用synchronized和lock锁的解决方式
package com.lzl;
//使用Synchronized关键字
public class demo1 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{ for (int i = 0; i < 60; i++) ticket.saleTicket(); },"A").start();
new Thread(()->{ for (int i = 0; i < 60; i++) ticket.saleTicket(); },"B").start();
new Thread(()->{ for (int i = 0; i < 60; i++) ticket.saleTicket(); },"C").start();
}
}
class Ticket{
int ticketNum = 30;
public synchronized void saleTicket(){
if(ticketNum>0){
System.out.println("卖出了第"+ticketNum--+"张票!");
}
}
}
package com.lzl;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//使用lock锁
public class demo2 {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(()->{ for (int i = 0; i < 60; i++) ticket.saleTicket(); },"A").start();
new Thread(()->{ for (int i = 0; i < 60; i++) ticket.saleTicket(); },"B").start();
new Thread(()->{ for (int i = 0; i < 60; i++) ticket.saleTicket(); },"C").start();
}
}
class Ticket2{
int ticketNum = 30;
//默认是非公平锁,传入true使用公平锁,false非公平锁
Lock lock = new ReentrantLock();
public void saleTicket(){
//加锁
lock.lock();
try {
if(ticketNum>0){
System.out.println("卖出了第"+ticketNum--+"张票!");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
lock.unlock();
}
}
}
我们通过Lock锁或者Synchronized关键字,都可以解决多线程的并发问题,下面说下它们的区别、优缺点
- Synchronized是java内置的一个关键字,Lock是工具包下java提供的一个接口
- Synchronized无法判断锁的状态,Lock可以判断是否获取到了锁
- Synchronized会自动释放锁,Lock必须要手动释放锁,否则会产生死锁
- Synchronized遇到线程阻塞时会一直等待,Lock锁不一定会等待下去
- Synchronized是已设置好的可重入锁、不可中断、非公平锁;Lock是可以手动设置的可重入锁、中断(是否),公平(是否)
- 正是由于Synchronized的全自动性,对于少量代码时,Synchronized更适用,Lock适用于大量代码内容
4.生产者消费者问题
1.传统解决方式
在加入同步锁的情况下,A线程负责生产,B线程负责消费时,使用if不使用whlie进行判断,程序也可以正常进行,但当生产者或消费者有多个对象时,if判断会产生虚假唤醒的问题,应当使用while判断。
package com.lzl;
/**
* 测试生产者、消费者问题
*/
public class Demo3 {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.increment();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.decrement();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.increment();
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.decrement();
}
},"D").start();
}
}
class Data{
private Integer num=0;
public synchronized void increment(){
while (num!=0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num++;
System.out.println(Thread.currentThread().getName()+"-->"+num);
//唤醒所有线程
notifyAll();
}
public synchronized void decrement(){
while(num == 0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println(Thread.currentThread().getName()+"-->"+num);
notifyAll();
}
}
2.JUC版的解决方式
传统方式:synchronized --> wait() --> notifyAll()
JUC: lock(),unlock(),lock.newCondition() --> condition.await() --> condition.signalAll()
package com.lzl;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 测试JUC解决生产者、消费者问题
*/
public class Demo4 {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.increment();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.decrement();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.increment();
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.decrement();
}
},"D").start();
}
}
class Data2{
private Integer num=0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void increment(){
lock.lock();
try {
while (num!=0){
//等待
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"-->"+num);
//唤醒全部线程
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement(){
lock.lock();
try {
while(num == 0){
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"-->"+num);
//唤醒全部线程
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
问题:上述两种方式,可以做到生产者、消费者交替执行,但是无法精准定位谁进行生产?谁进行消费?
package com.lzl;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 利用Condition监视器精准唤醒某一线程
*/
public class Demo5 {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(()->{ for (int i = 0; i < 10; i++) data.printA();}).start();
new Thread(()->{ for (int i = 0; i < 10; i++) data.printB();}).start();
new Thread(()->{ for (int i = 0; i < 10; i++) data.printC();}).start();
}
}
class Data3{
private int num=1;
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
private Condition conditionC = lock.newCondition();
public void printA(){
lock.lock();
try {
if(num != 1){
conditionA.await();
}
System.out.println("A线程开始工作。。。。。。。");
num=2;
//精准唤醒某一个线程
conditionB.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
if(num != 2){
conditionB.await();
}
System.out.println("B线程开始工作。。。。。。。");
num=3;
//精准唤醒某一个线程
conditionC.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
if(num != 3){
conditionC.await();
}
System.out.println("C线程开始工作。。。。。。。");
num=1;
//精准唤醒某一个线程
conditionA.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
5.8锁问题
结论:锁,锁的只有两个东西,一个是对象,一个是class模板
下述四组八个问题:
package com.lzl.lock8;
import java.util.concurrent.TimeUnit;
/**
* 问题1:在不加延迟的情况下,同一个对象调用两个方法 ---》发短信、打电话
* 问题2:在发短信方法加了延迟的情况下,同一个对象调用两个方法 ---》 发短信、打电话
* 结论:两个方法由同一个对象调用,使用同一把锁,谁先获得锁,谁先调用
*/
public class Demo1 {
public static void main(String[] args) {
Phone phone = new Phone();
//问题1、2
new Thread(()-> phone.sendSms() ).start();
new Thread(()-> phone.call() ).start();
}
}
class Phone{
public synchronized void sendSms(){
try {
//睡4s
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
package com.lzl.lock8;
import java.util.concurrent.TimeUnit;
/**
* 问题1:同一个对象调用加锁和不加锁两个的方法 --》hello、发短信
* 问题2:不同对象调用两个加锁的方法 --》打电话、发短信
* 结论:未加锁或两个对象使用的不是同一把锁,没有延迟的方法先执行
*/
public class Demo2 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
Phone2 phone2 = new Phone2();
//问题1
new Thread(()-> phone.sendSms() ).start();
new Thread(()-> phone.hello() ).start();
//问题2
new Thread(()-> phone.sendSms() ).start();
new Thread(()-> phone2.call() ).start();
}
}
class Phone2{
public synchronized void sendSms(){
try {
//睡4s
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");
}
}
package com.lzl.lock8;
import java.util.concurrent.TimeUnit;
/**
* 问题1:同一个对象的两个方法都加了static静态修饰时 --》发短信、打电话
* 问题2:两个对象的两个方法都加了static静态修饰时 --》发短信、打电话
* 结论:由于方法被静态修饰,在类一加载时就会产生,此时锁的是class模板,无论一个还是多个对象,都共用一把锁
*
*/
public class Demo3 {
public static void main(String[] args) {
Phone3 phone = new Phone3();
Phone3 phone2 = new Phone3();
//问题1
new Thread(()-> phone.sendSms() ).start();
new Thread(()-> phone.call() ).start();
//问题2
new Thread(()-> phone.sendSms() ).start();
new Thread(()-> phone2.call() ).start();
}
}
class Phone3{
public static synchronized void sendSms(){
try {
//睡4s
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
package com.lzl.lock8;
import java.util.concurrent.TimeUnit;
/**
* 问题1:同一个对象调用两个静态方法,一个加锁,一个不加锁 --》打电话、发短信
* 问题2:两个个对象调用两个静态方法,一个加锁,一个不加锁 --》打电话、发短信
* 结论:没有锁的情况下,没有延迟的先执行
*/
public class Demo4 {
public static void main(String[] args) {
Phone4 phone = new Phone4();
Phone4 phone2 = new Phone4();
//问题1
new Thread(()-> phone.sendSms() ).start();
new Thread(()-> phone.call() ).start();
//问题2
new Thread(()-> phone.sendSms() ).start();
new Thread(()-> phone2.call() ).start();
}
}
class Phone4{
public static synchronized void sendSms(){
try {
//睡4s
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static void call(){
System.out.println("打电话");
}
}
6.不安全集合类
1.CopyOnWriteArrayList
多个线程向List集合中插入数据时,java.util.ConcurrentModificationException 并发修改异常
解决方式:
1.使用Vector
2.使用Collections.synchronizedList
3.使用JUC包下的CopyOnWriteArrayList
CopyOnWrite 写入时复制,cow计算机程序设计领域的一种优化策略
相较于Vector的优势,CopyOnWriteArrayList使用lock锁的方式,效率更高
package com.lzl.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 测试多线程环境下List的不安全及解决方案
*/
public class ListTest {
public static void main(String[] args) {
//原生集合
//List<String> list = new ArrayList<>();
//1.Vector
//List<String> list = new Vector<>();
//2.使用工具类对集合进行转换
//List<String> list = Collections.synchronizedList( new ArrayList<>());
//3.使用JUC包下的CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
}).start();
}
}
}
2.CopyOnWriteArraySet
多个线程向Set集合中插入数据时,java.util.ConcurrentModificationException 并发修改异常
解决方式:
- 使用工具类转换 Collections.synchronizedSet()
- 使用JUC包下CopyOnWriteArraySet
package com.lzl.unsafe;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* 测试并发下set集合的不安全及解决方案
*/
public class SetTest {
public static void main(String[] args) {
//原始set
//Set<String> set = new HashSet<>();
//使用工具类转换
//Set<String> set = Collections.synchronizedSet(new HashSet<>());
//使用JUC包下CopyOnWriteArraySet
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
3.ConcurrentHashMap
同样,多个线程向Map中插入数据时,也会产生java.util.ConcurrentModificationException 并发修改异常
解决方式:
- JUC包下的ConcurrentHashMap
package com.lzl.unsafe;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* 测试并发下不安全的Map及解决方案
*/
public class MapTest {
public static void main(String[] args) {
//1.原生Map
//Map<String,String> map = new HashMap<>();
//2.JUC包下的ConcurrentHashMap
Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
7.Callable接口
Callable接口特点:
- 有返回值,接口泛型的类型 == 返回值类型
- Thread类 和 Runnable接口使用run()方法,Callable接口使用call()方法
- 使用get()方法时,可能会产生阻塞,一般代码置后或异步获取
- 由于缓存的存在,开启多条线程调用call()方法,只会执行一次
- 线程的启动通过FutureTask类来过渡
package com.lzl.callAble;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 测试使用callable接口开启线程
*/
public class CallAbleTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/**
* callable接口开启线程的过程:
* 首先开启线程的方式只有一个:new Thread().start();
* FutureTask 是 Runnable接口的一个实现类,而FutureTask的构造函数参数可以是Callable类型的
* 因此使用callable接口开启线程,需要用到FutureTask类过度
*
*/
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask<String>(myThread);
new Thread(futureTask,"A").start();
//开启两个线程调用callable接口时,由于缓存的存在,只会输出一次
new Thread(futureTask,"B").start();
//获取callable接口的返回值
// 此方法可能会产生阻塞,一般放在代码的最后,或者异步调用
String str = (String)futureTask.get();
System.out.println(str);
}
}
class MyThread implements Callable<String>{
@Override
public String call() {
System.out.println("call()");
return "hello,callable";
}
}
8.辅助工具类
1. CountDownLatch
减法计数器,当待执行任务数量置零时,执行后续任务,否则处于阻塞状态
方法:
- countDownLatch.countDown() --> 计数器-1
- countDownLatch.await() -->阻塞,等待计数器归0
package com.lzl.util;
import java.util.concurrent.CountDownLatch;
/**
* 线程的减法计数器
*/
public class TestCountDownLatch {
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());
//计数器-1
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//等待计数器归零
countDownLatch.await();
System.out.println("计数器已归零");
}
}
2.CyclicBarrier
加法计数器,当任务数量达到指定数量时,执行后续任务,否则处于阻塞状态
方法:
- cyclicBarrier.await() --> 等待任务数量达到指定数量
package com.lzl.util;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* 加法计数器
*/
public class TestCyclicBarrier {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(6, () -> System.out.println("计数完毕!"));
for (int i = 0; i < 6; i++) {
final int temp = i;
new Thread(()->{
System.out.println(temp);
try {
//等待
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
3. Semaphore
信号量,限制可同时执行任务量,其他待执行任务等待信号量释放执行
方法:
- semaphore.acquire() --> 从信号量中获取许可证,获取不到,等待
- semaphore.release() --> 释放许可证到信号量中
package com.lzl.util;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* 信号量
*/
public class TestSemaphore {
public static void main(String[] args) {
//设置可用信号量为3
Semaphore semaphore = new Semaphore(3);
for (int i = 0; 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();
}
},i+"").start();
}
}
}
9.读写锁
适用场景:单个写入,多个读取数据
ReentrantReadWriteLock:相较于lock锁可以更加细粒度的控制
ReentrantReadWriteLock().writeLock() : 写锁,也叫独占锁
ReentrantReadWriteLock().readLock() : 读锁,也叫共享锁
package com.lzl.readWriteLock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 测试读写锁
*/
public class TestReadWriteLock {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(()->{
myCache.put(""+temp,""+temp);
}).start();
}
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(()->{
myCache.get(""+temp);
}).start();
}
}
}
/**
* 自定义缓存类
*/
class MyCache{
private volatile Map<String,String> map = new HashMap<>();
//读写锁,相较于lock锁可以更加细粒度的控制
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put(String key,String value){
//加锁 读写锁.写锁
readWriteLock.writeLock().lock();
try {
System.out.println(key+"开始写入");
map.put(key,value);
System.out.println(key+"写入完毕");
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
readWriteLock.writeLock().unlock();
}
}
public void get(String key){
//加锁 读写锁.读锁
readWriteLock.readLock().lock();
try {
System.out.println(key+"开始读取");
map.get(key);
System.out.println(key+"读取完毕");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
10.阻塞队列
1.概念及关系图
队列:一种遵循先入先出、后入后出(FIFO)的线性表
阻塞:
- 队列已满的情况下,再次向队列中写值时
- 队列为空的情况下,从队列中取值时
队列的实现类在java中与List,set的关系图:
2.四组api介绍
方式 | 无返回值,抛异常 | 有返回值 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer(时间参数) |
取出 | remove | poll | take | poll(时间参数) |
获取队首元素 | element | peek | -- | -- |
package com.lzl;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* 测试阻塞队列的四组api
*/
public class queueTestBlockingQueue {
public static void main(String[] args) throws InterruptedException {
test4();
}
public static void test1(){
//add、remove方法,当队列已满继续插入或队列为空继续移除时,会抛出异常
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//检测当前的队首元素,队列为空时,抛出异常
System.out.println(blockingQueue.element());
//System.out.println(blockingQueue.add("d"));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
// System.out.println(blockingQueue.remove());
}
public static void test2(){
//offer、poll,当队列已满继续插入或队列为空继续移除时,返回false或null
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
//System.out.println(blockingQueue.offer("d"));
//检测当前的队首元素,队列为空时,返回null
System.out.println(blockingQueue.peek());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
}
public static void test3() throws InterruptedException {
//put、take,当队列已满继续插入或队列为空继续移除时,程序会一直处于阻塞等待的状态
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//blockingQueue.put("d");
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 {
//put、take带有参数调用时,当队列已满继续插入或队列为空继续移除时,超过定义的时间后会结束阻塞状态
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
blockingQueue.offer("d",2, TimeUnit.SECONDS);
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
}
}
3.同步队列
同步队列不会存储元素,添加一个必须取出一个,并且此处的添加和取出方法,应当使用阻塞等待,否则元素无法加入队列中
package com.lzl.queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* 测试同步队列
*/
public class TestSynchionzeQueue {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> queue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println("添加1");
queue.put("1");
System.out.println("添加2");
queue.put("2");
System.out.println("添加3");
queue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println("取出"+queue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println("取出"+queue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println("取出"+queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
11.线程池
1.使用原因及其优点
程序的运行,其本质上,是对系统资源(CPU、内存、磁盘、网络等等)的使用。如何高效的使用这些资源是我们编程优化演进的一个方向。今天说的线程池就是一种对CPU利用的优化手段
池化技术:提前保存大量的资源,以备不时之需。在机器资源有限的情况下,使用池化技术可以大大的提高资源的利用率,提升性能等。
线程池的工作原理::控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
它的主要特点为:线程复用,控制最大并发数,管理线程。
第一:降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
第三:提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配,调优和监控。
2.使用方式
线程池的创建:三大方式、七大参数、四种拒绝策略
1.三大创建方式
- 单一线程线程池:Executors.newSingleThreadExecutor()
- 固定数量的线程池:Executors.newFixedThreadPool(num)
- 可伸缩大小的线程池: Executors.newCachedThreadPool()
package com.lzl.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 测试创建线程池的三大方式
*/
public class TestThreadPool {
public static void main(String[] args) {
//创建单一线程的线程池
//ExecutorService threadPool = Executors.newSingleThreadExecutor();
//创建固定数量的线程池
//ExecutorService threadPool =Executors.newFixedThreadPool(5);
//创建一个可伸缩大小的线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ==> ok");
});
}
} finally {
//关闭线程池
threadPool.shutdown();
}
}
}
2.七大参数
三种创建方式都调用的源码分析:
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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
实际开发中,不要使用工具类提供的三种创建方式,而是自定义线程池,阿里巴巴代码规约中有明确说明
8. 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 各个方法的弊端:
1)newFixedThreadPool 和 newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM。
2)newCachedThreadPool 和 newScheduledThreadPool:
主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。
自定义线程池:
package com.lzl.pool;
import java.util.concurrent.*;
/**
* 自定义线程池
* 模拟银行办理业务场景:
* 总窗口5个,核心营业窗口2个,候客等待位置3个
* 通过调整开启的线程数量,获取运行结果,可以得出:
* 线程池的能容纳的最大线程数量为:最大线程数+待执行任务队列数 5+3
*/
public class TestCustomThreadPool {
public static void main(String[] args) {
//自定义一个核心线程数为2,最大线程数为5,线程池任务队列为3的线程池
ExecutorService threadPool = new ThreadPoolExecutor(2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
//默认的线程创建工厂
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try {
for (int i = 0; i < 9; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
3.四种拒绝策略
当待执行任务数大于线程池的最大容量时,线程池的拒绝策略
- AbortPolicy :抛出异常提示
- CallerRunsPolicy:此任务从哪个线程进来的,会被退回到哪个线程执行,如main方法进入,此任务交由main()线程执行
- DiscardPolicy:任务会被忽略
- DiscardOldestPolicy :与线程池中已有的任务进行竞争
4.如何定义最大线程数量
分为两种方式:
- CPU密集型
- IO密集型
CPU密集型,获取当前电脑的最大线程数量,作为最大线程数量的参数即可
Runtime.getRuntime().availableProcessors()
IO密集型: 当程序中调用IO资源较多时,最大线程数量应大于IO任务数量,最好为2倍。 如IO任务数量为15时,最大线程数量定义为30。
12.四大函数式接口
函数式接口:有且只有一个方法的接口
新时代程序猿必备四个技能:lambda表达式、链式编程、函数式接口、Stream流式计算
四个函数式接口原型:Function,Predicate,Consumer,Supplier
1.函数型接口
有参数,有返回值,泛型定义参数及返回值类型
package com.lzl.function;
import java.util.function.Function;
/**
* 函数型接口:传入参数及返回值类型
*/
public class TestFunction {
public static void main(String[] args) {
// Function function = new Function<String, String>() {
// @Override
// public String apply(String str) {
// return str;
// }
// };
//定义参数及返回值得类型
Function<String, String> function =(str)->{return str;};
System.out.println(function.apply("asd"));
}
}
2.断定型接口
有参数,又返回值,泛型定义参数类型,返回值为固定的布尔值
package com.lzl.function;
import java.util.function.Predicate;
/**
* 断定型接口:传入参数类型,返回布尔值
*/
public class TestPredicate {
public static void main(String[] args) {
Predicate<String> predicate = (str)->{return str.isEmpty();};
System.out.println(predicate.test("asd"));
}
}
3.消费型接口
有参数,无返回值,泛型定义参数类型
package com.lzl.function;
import java.util.function.Consumer;
/**
* 消费型接口:只有参数,无返回值
*/
public class TestConsumer {
public static void main(String[] args) {
Consumer<String> consumer = (str)->{
System.out.println(str);
};
consumer.accept("asd");
}
}
4.供给型接口
无参数,又返回值,泛型定义返回值类型
package com.lzl.function;
import java.util.function.Supplier;
/**
* 供给型接口:定义返回值类型,只有返回值,无参数
*/
public class TestSupplier {
public static void main(String[] args) {
Supplier<Integer> supplier =()->{return 1024;};
System.out.println(supplier.get());
}
}
13.Strem流式计算
将存储的数据转换为流,进行计算处理
package com.lzl.stream;
import java.util.Arrays;
import java.util.List;
/**
* 流式计算、链式编程综合题目
* 题目:请按照给出数据,找出同时满足以下条件的用户
* 也即以下条件:
* 1、全部满足偶数ID
* 2、年龄大于24
* 3、用户名转为大写
* 4、用户名字母倒排序
* 5、只输出一个用户名字 limit
*/
public class TestStream {
public static void main(String[] args) {
User u1 = new User(11, "a", 23);
User u2 = new User(12, "b", 24);
User u3 = new User(13, "c", 22);
User u4 = new User(14, "d", 28);
User u5 = new User(16, "e", 26);
//数据存储在集合中
List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
//转换为流进行条件过滤
//Strem部分流方法使用、链式编程
list.stream()
.filter((u)->{return u.getId()%2 == 0;})
.filter((u)->{return u.getAge()>24;})
.map((u)->{return u.getUserName().toUpperCase();})
.sorted((uu1,uu2)->{return uu2.compareTo(uu1); })
.limit(1)
.forEach(System.out::println);
}
}
package com.lzl.stream;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private int id;
private String userName;
private int age;
}
14.分支合并
什么是ForkJoin
从JDK1.7开始,Java提供Fork/Join框架用于并行执行任务,它的思想就是将一个大任务分割成若干小任务,最终汇总每个小任务的结果得到这个大任务的结果。
并行执行任务,大数据量!提高效率!
特点:工作窃取
简单理解,就是一个工作线程下会维护一个包含多个子任务的双端队列。而对于每个工作线程来说,会从头部到尾部依次执行任务。这时,总会有一些线程执行的速度较快,很快就把所有任务消耗完了。此时,该线程就会从其他队列中窃取任务来执行。
package com.lzl.forkJoin;
import java.util.stream.LongStream;
/**
* 场景:求和1~100_0000_0000之间的所有数字
*/
public class TestDemo {
static long start = 1L;
static long end = 100_0000_0000L;
static long sum = 0L;
public static void main(String[] args) {
//test1(); //3586
//test2();//1124
test3();//797
}
//普通求和
public static void test1(){
long timeStart = System.currentTimeMillis();
for (long i = start; i <= end; i++) {
sum+=i;
}
long timeEnd = System.currentTimeMillis();
System.out.println("结果:"+sum+",时间:"+(timeEnd-timeStart));
}
//forkJoin任务
public static void test2(){
long timeStart = System.currentTimeMillis();
sum = new MyForkJoinTask(start,end).compute();
long timeEnd = System.currentTimeMillis();
System.out.println("结果:"+sum+",时间:"+(timeEnd-timeStart));
}
//并行stream流
public static void test3(){
long timeStart = System.currentTimeMillis();
sum = LongStream.rangeClosed(start,end).parallel().reduce(0,Long::sum);
long timeEnd = System.currentTimeMillis();
System.out.println("结果:"+sum+",时间:"+(timeEnd-timeStart));
}
}
package com.lzl.forkJoin;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
/**
* 自定义forkjoin任务
*/
public class MyForkJoinTask extends RecursiveTask<Long> {
long start ;
long end;
public MyForkJoinTask(long start,long end){
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
//临界值,可以调整此值,优化效率
long critical = 10000L;
//判断是否拆分完毕
long length = end-start;
if(length <= critical){
long sum = 0L;
//如果相加数的首尾项小于临界值,直接计算
for (long i = start; i < end; i++) {
sum+=i;
}
return sum;
}else{
//拆分任务
//计算首尾项的中间值
long middle = (start+end)/2;
MyForkJoinTask task1 = new MyForkJoinTask(start,middle);
MyForkJoinTask task2 = new MyForkJoinTask(middle,end);
//拆分任务,把任务压入线程队列中
task1.fork();
task2.fork();
return task1.join()+task2.join();
}
}
}
15.异步回调
java.util.concurrent.Future CompletableFuture
package com.lzl.future;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* 测试异步任务回调
*/
public class TestCompletableFuture {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//没有返回值得异步回调
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步回调方法。。。");
});
//先打印外部输出
System.out.println("1111");
//阻塞 待任务中程序执行完毕
future.get();
//供给型接口 有返回值的异步输出
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("有返回值型接口。。");
int a=10/0;
return 1024;
});
System.out.println(future1.whenComplete((u, t) -> {
//程序执行成功时
System.out.println("u=>"+u);
System.out.println("t=>"+t);
}).exceptionally((u) -> {
//程序执行错误时
System.out.println("u=>"+u);
return 233;
}).get());
}
}
16.Volatile与JMM
- 什么是JMM?
JMM本身是一种抽象的概念,并不真实存在,是JAVA中的一种约定,规范
- JMM关于同步的规定
- 线程解锁前,必须把共享变量的值刷新回主内存
- 线程加锁前,必须读取主内存的最新值到自己的工作内存
- 加锁解锁是同一把锁
- Volatile
volatile 是 Java 虚拟机提供的轻量级的同步机制
三大特性:
- 保证可见性
- 不保证原子性
- 禁止指令重排
- JMM的内存模型
JVM在设计时候考虑到,如果JAVA线程每次读取和写入变量都直接操作主内存,对性能影响比较大,所以每条线程拥有各自的工作内存,工作内存中的变量是主内存中的一份拷贝,线程对变量的读取和写入,直接在工作内存中操作,而不能直接去操作主内存中的变量。但是这样就会出现一个问题,当一个线程修改了自己工作内存中变量,对其他线程是不可见的,会导致线程不安全的问题。此时,就需要上述的JMM规定来保证多个线程操作的安全性。
- 四组内存交互操作
- lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
- unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
- use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
- assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
- store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
- write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内
存的变量中
JMM对这八种指令的使用,制定了如下规则:
- 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量
实施use、store操作之前,必须经过assign和load操作 - 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,
必须重新load或assign操作初始化变量的值 - 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
对一个变量进行unlock操作之前,必须把此变量同步回主内存
- Volatile保证可见性的验证
package com.lzl.jmm;
import java.util.concurrent.TimeUnit;
/**
* 测试Volatile的可见性
*/
public class TestVolatile {
private volatile static int num = 0;
public static void main(String[] args) throws InterruptedException {
//如果num为0,此线程一直执行
new Thread(()->{
while (num == 0){
}
}).start();
//为保证上述线程启动,此处延迟1s
TimeUnit.SECONDS.sleep(1);
//main线程修改主内存中的变量值,如果num变量线程间可见,程序会停止,否则会一直死循环
System.out.println("main...");
num++;
}
}
- 原子性
原子性:即一个操作或者多个操作,要么全部执行,并且执行的过程不会被任何因素打断,要么就都不执行。
volatile不保证原子性的验证:
package com.lzl.jmm;
/**
* 测试Volatile是否能够保证原子性
*/
public class TestVolatile2 {
private volatile static int num=0;
public static void add(){
num++;
}
public static void main(String[] args) {
//此处开启20个线程去执行叠加操作
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
add();
}
}).start();
}
//主线程获取num的值,如果正常叠加,num值为20000
System.out.println(Thread.currentThread().getName()+"-----"+num);
}
}
- 怎样保证原子性
上述程序,在不使用lock锁、synchronized的情况下,怎样保证正确求和呢?
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
num.getAndIncrement();
}
使用原子类定义数字,调用其方法,此类的方法调用并不是简单的叠加操作,涉及到操作系统,比加锁的效率高很多。
- 指令重排
什么是指令重排:程序的执行顺序可能不是按照你的书写顺序去执行!
源代码 ---> 编译器优化的重排 --->指令并行也可能会重排 --->内存系统也会重排 -->执行
int x=1;//1
int y=2;//2
x = x+5;//3
y = x*x;//4
//书写执行顺序:1234
//指令重排后执行的可能顺序:2134 1324
//但不可能是4123这种 因为处理器在进行指令重排时,会考虑数据之间的依赖性
指令重排理论上一定存在,但实际写很多次代码,也不一定可以重现!
为什么volatile可以禁止指令重排?
系统中的CPU指令,内存屏障。它的作用:
- 保证特定操作的执行顺序
- 保证某些变量的内存可见性( 利用这些特性volatile实现了可见性 )
.
17.单例模式
1.饿汉式单例
package com.lzl.singleCase;
/**
* 单例模式 -- 饿汉式
* 程序一开始的时候,就初始化对象,当类中有比较占用空间的操作时,如下数组,比较浪费空间!
*/
public class HungryMan {
private byte[] data1 = new byte[1024];
private byte[] data2 = new byte[1024];
private byte[] data3 = new byte[1024];
private byte[] data4 = new byte[1024];
private HungryMan(){
}
private final static HungryMan hungryMan = new HungryMan();
public static HungryMan getInstance(){
return hungryMan;
}
}
- 懒汉式单例
package com.lzl.singleCase;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* 单例模式 -- 懒汉式
*/
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"---ok!");
}
private volatile static LazyMan lazyMan;
//方式一:可以实现单例,延迟加载,但多线程下不安全
public static LazyMan getInstance(){
if(lazyMan == null){
lazyMan = new LazyMan();
}
return lazyMan;
}
//方式二:双重检测锁模式 多线程下安全 也称为DCL懒汉式
public static LazyMan getInstance2(){
if(lazyMan == null){
synchronized (LazyMan.class){
if(lazyMan == null){
lazyMan = new LazyMan();
//new对象不是一个原子性操作
/*
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 所以完整的双重检测锁模式 需要使用volatile关键字去修饰
* */
}
}
}
return lazyMan;
}
//使用反射去破坏上述单例模式
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//方式一测试 构造器中输出执行的线程名称发现会有多个线程
for (int i = 0; i < 10; i++) {
new Thread(()->{
getInstance();
}).start();
}
//方式二测试
LazyMan instance2 = getInstance2();
LazyMan instance21 = getInstance2();
System.out.println(instance2);
System.out.println(instance21);
//使用反射去破坏上述的懒汉式单例
LazyMan instance22 = LazyMan.getInstance2();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
//关闭私有属性的代码检测
declaredConstructor.setAccessible(true);
LazyMan instance = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance22);
}
}
关于单例模式的其他介绍参考:单例详解
关于利用反射破解的详细介绍本章视频:玩转单例模式
18.CAS
即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。
CAS算法涉及到三个操作数:
- 需要读写的内存值 V
- 旧的预期值 A
- 拟写入的新值 B
当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
相关介绍:乐观锁与悲观锁
package com.lzl.cas;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 关于cas的理论介绍
*
* @author liangzilong
* @date 2022-11-16 9:59
*/
public class TestCas {
public static void main(String[] args) {
AtomicInteger integer = new AtomicInteger(1024);
//public final boolean compareAndSet(int expect, int update)
//如果expect是期望的值,则结果更新的update这个值
//此方法会返回布尔值,如果获得到了期望值并且更新成功了,返回true;否则返回false
System.out.println(integer.compareAndSet(1024, 2048));
//结果是2048
System.out.println(integer.get());
//未获取到期望值的结果
System.out.println(integer.compareAndSet(1024, 2048));
System.out.println(integer.get());
//原子性变量的++操作
integer.getAndIncrement();
System.out.println(integer);
/*
源码分析:
Unsafe类:首先需要明确,java是无法直接操作内存的,但是java可以操作底层的c++ 如开启线程的native方法;unSave类属于java开的一个后门,可以通过这 个类操作底层
public final int getAndIncrement() {
this:当前值
valueOffset:当前值在内存中的偏移量
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
//定义变量var5
do {
//获取当前对象在内存中的值
var5 = this.getIntVolatile(var1, var2);
比较 var1 var2 与 var5的内存值,如果var5的值是期望的值,执行var5+var4(1) +1操作,内存层面的+1 效率很高
否则,在此处进行循环(自旋锁)
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
*/
}
}
19.原子引用解决ABA问题
什么是ABA问题:假设t1线程工作时间为10秒,t2线程工作时间为2秒,那么可能在A的工作期间,主内存中的共享变量 A已经被t2线程修改了多次,只是恰好最后一次修改的值是该变量的初始值,虽然用CAS判定出来的结果是期望值,但是却不是原来那个了=======》“狸猫换太子”
原子引用解决ABA问题:实际就是通过增加版本号,类似于乐观锁中的版本号机制
package com.lzl.cas;
import java.util.concurrent.atomic.AtomicInteger;
/**
* ABA问题的还原
*/
public class TestABA {
private static AtomicInteger atomicInteger = new AtomicInteger(1);
public static void main(String[] args) {
//t2线程执行的程序
atomicInteger.compareAndSet(1,2);
System.out.println(atomicInteger.get());
atomicInteger.compareAndSet(2,1);
System.out.println(atomicInteger.get());
//t1线程执行的程序
atomicInteger.compareAndSet(1,66);
//此时的输出结果是66 但实际上比较值中的1已经被t2线程替换过了
System.out.println(atomicInteger.get());
}
}
关于包装类比较的一个坑:
.
package com.lzl.test;
public class TestDemo {
public static void main(String[] args) {
Integer a=1;
Integer b=1;
System.out.println(a==b);//true
System.out.println(a.equals(b));//true
Integer c=129;
Integer d=129;
System.out.println(c==d);//false
System.out.println(c.equals(d));//true
}
}
package com.lzl.cas;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* 原子引用解决ABA问题
*/
public class TestABA2 {
//关于包装类比较的一个坑 泛型为Integer时,此处的期望值需要在-127~128区间,否则期望值一直无法匹配成功
private static AtomicStampedReference<Integer> atomicReference = new AtomicStampedReference<Integer>(1,1);
public static void main(String[] args) {
//t2线程执行的操作
atomicReference.compareAndSet(1,2, 1,
atomicReference.getStamp()+1);
System.out.println(atomicReference.getReference());
atomicReference.compareAndSet(2,1, 2,
atomicReference.getStamp()+1);
System.out.println(atomicReference.getReference());
//t1线程执行的操作
//由于此时的版本号已经被改为了3,此处比较无法通过
atomicReference.compareAndSet(1,66, 1 ,
atomicReference.getStamp()+1);
System.out.println(atomicReference.getReference());
}
}
20.可重入锁
什么是可重入锁:可重入锁又称递归锁,是指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提是锁对象得是同一个对象),不会因为之前已经获取过锁还没有释放而阻塞。
package com.lzl.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 测试可重入锁
*/
public class TestReenTrantLock {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
public synchronized static void sms(){
System.out.println(Thread.currentThread().getName()+"--->sms");
call();
}
public synchronized static void call(){
System.out.println(Thread.currentThread().getName()+"--->call");
}
}
class Phone2{
public static void sms(){
Lock lock = new ReentrantLock();
try {
//锁需要配对,如果此处锁了2次,下面只解锁一次,就会产生死锁
lock.lock();
System.out.println(Thread.currentThread().getName()+"--->sms");
call();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void call(){
Lock lock = new ReentrantLock();
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"--->call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
21.自旋锁
是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
package com.lzl.lock;
import java.util.concurrent.atomic.AtomicReference;
/**
* 自定义自旋锁
*/
public class TestSpinLock {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock(){
//获取当前线程
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"--->myLock");
//如果当前线程不为空,在此处循环自旋
while (!atomicReference.compareAndSet(null,thread)){
}
}
public void myUnLock(){
//获取当前线程
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"--->myUnLock");
//传入当前线程,结束自旋
atomicReference.compareAndSet(thread,null);
}
}
package com.lzl.lock;
import java.util.concurrent.TimeUnit;
/**
* 测试自定义自旋锁
*/
public class TestSpinLockDemo {
public static void main(String[] args) {
TestSpinLock spinLock = new TestSpinLock();
new Thread(()->{
spinLock.myLock();
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLock.myUnLock();
},"A").start();
new Thread(()->{
spinLock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLock.myUnLock();
},"B").start();
}
}
代码分析:当A线程先拿到自定义的自旋锁时,B线程虽然也可以输出mylock,但此时只能等待A线程释放锁,B线程才能获取到锁。
22.死锁排查
package com.lzl.lock;
import java.util.concurrent.TimeUnit;
/**
* 创建死锁进程
*/
public class TestDeadLock {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new MyThread(lockA,lockB),"T1").start();
new Thread(new MyThread(lockB,lockA),"T2").start();
}
}
class MyThread implements Runnable{
String lockA;
String lockB;
public MyThread(String lockA,String lockB){
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+"lock:"+lockA+"get:"+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"lock:"+lockB+"get:"+lockA);
}
}
}
}
使用命令jsp -l,在Terminal查看当前所有进程
.
使用命令jstack查看栈信息
.