一、基础知识介绍
JUC:
在 Java 5.0 提供了 java.util.concurrent
(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,
用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架;还提供了设计用于多线程上下文中
的 Collection 实现等 。
进程:
一个程序即一个进程,如QQ.exe,输入法等
线程:
一个进程中可以执行多个线程,至少执行一个线程
并发:
单核多线程操作资源,依靠线程之间的快速交替给人一种同时进行的感觉
并行:
多核操作,真正意义上的同时进行
其他:
Java默认有两个线程,main和GC;Java开不了线程,只能通过本地方法去调用,即通过底层的C++去调用,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 */
}
}
}
//通过底层C++启动线程
private native void start0();
Java程序查看电脑的核数:
public class Test01 {
public static void main(String[] args) {
//获取CPU核数
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
并发编程的本质: 充分利用CPU的资源
二、线程状态
public enum State {
//新生状态
NEW,
//运行状态
RUNNABLE,
//阻塞状态
BLOCKED,
//等待状态
WAITING,
//超时等待状态
TIMED_WAITING,
//终止状态
TERMINATED;
}
wait和sleep的区别:
- 来自不同类
- wait来自Object
- sleep来自Thread
- 锁的释放不同
- wait会释放锁
- sleep不会释放锁
- 使用的范围不同
- wait只能在同步代码块中使用
- sleep可以任何地方使用
- 是否捕获异常
- wait和sleep都需要捕获异常
三、Lock锁
3.1 Lock锁相关介绍
-
传统的synchronized锁示例
/** * 公司中的开发,降低耦合性,线层就是一个单独的资源类,没有附属的操作 */ public class SaleTicketDemo{ public static void main(String[] args) { Ticket ticket = new Ticket(); new Thread(()->{ for (int i = 0; i < 20; i++) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } ticket.sale(); } },"A").start(); new Thread(()->{ for (int i = 0; i < 20; i++) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } ticket.sale(); } },"B").start(); new Thread(()->{ for (int i = 0; i < 20; i++) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } ticket.sale(); } },"C").start(); } } class Ticket{ private int nums = 10; private int count = 0; public synchronized void sale(){ if (nums>0){ System.out.println(Thread.currentThread().getName()+"卖出了第"+(++count)+"张票,剩余票数"+(--nums)+"张"); } } }
-
Lock加锁示例:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockTest { public static void main(String[] args) { Ticket ticket = new Ticket(); new Thread(()->{for (int i = 0; i < 20; i++) ticket.sale();},"A").start(); new Thread(()->{for (int i = 0; i < 20; i++) ticket.sale();},"B").start(); new Thread(()->{for (int i = 0; i < 20; i++) ticket.sale();},"C").start(); new Thread(()->{for (int i = 0; i < 20; i++) ticket.sale();},"D").start(); } } class Ticket{ private int tickets = 20; private int count = 0; private Lock lock = new ReentrantLock(); public void sale(){ //加锁 lock.lock(); try { if (tickets>0){ System.out.println(Thread.currentThread().getName()+"卖出了第"+(++count)+"票,剩余票数为"+(--tickets)); } } finally { //解锁 lock.unlock(); } } }
3.2 Synchronized和Lock的区别
- Synchronized 是Java内置的关键字,Lock 是一个Java类
- Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到锁
- Synchronized 会自动释放锁,Lock 必须手动释放锁,如果不进行锁释放,会导致死锁
- Synchronized 遇到线程阻塞时只能等待,Lock 不一定会等待,会通过tryLock( )方法去尝试获取锁
- Synchronized 可重入锁,不可以中断,非公平的;Lock 可重入锁,可以判断锁,非公平锁(需要的话可以自己设置为公平锁)
- Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量同步代码
3.3 生产者和消费者问题
线程虚假唤醒问题示例:
/**
* 线程之间的通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
* 线程之间交替执行
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class Data{
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
if (number!=0){
wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"-->"+number);
notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
if (number==0){
wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"-->"+number);
notifyAll();
}
}
问题描述:当线程启动数量由两个变为四个时,出现了线程虚假唤醒问题,以下是出现线程虚假唤醒的其中一种情况描述。
解决方案:将 if 改为 while 判断
3.4 Lock版的生产者和消费者问题(Condition)
代码示例:
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class Data{
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//+1
public void increment() throws InterruptedException {
lock.lock();
try{
if (number!=0){
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"-->"+number);
condition.signalAll();
}finally {
lock.unlock();
}
}
//-1
public void decrement() throws InterruptedException {
lock.lock();
try {
if (number==0){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"-->"+number);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
使用Condition的好处是可以实现精准地通知和唤醒指定线程
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionTest {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 20; i++) {
data.A();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
data.B();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
data.C();
}
},"C").start();
}
}
//资源类 Lock
class Data {
private int nums = 0;
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void A(){
lock.lock();
try {
while (nums!=0){
condition1.await();
}
nums = 1;
System.out.println(Thread.currentThread().getName()+" nums = "+nums);
//唤醒 B 方法
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void B(){
lock.lock();
try {
while (nums!=1){
condition2.await();
}
nums = 2;
System.out.println(Thread.currentThread().getName()+" nums = "+nums);
//唤醒 C 方法
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void C(){
lock.lock();
try {
while (nums!=2){
condition3.await();
}
nums = 0;
System.out.println(Thread.currentThread().getName()+" nums = "+nums);
//唤醒 A 方法
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
四、线程八锁
具体代码示例可参考我的另一篇博客里的线程八锁
//线程八锁总结示例代码
public class Lock8 {
public static void main(String[] args) {
ThreadExample example = new ThreadExample();
/**
* 调用线程时要注意是否是多个线程操作同一个资源,如非注释代码即是如此
* 如果是多个线程操作多个对象,由于是不同的锁,所以是无法得到想要的效果的
*/
new Thread(()->{
example.test1();
},"A").start();
new Thread(()->{
example.test1();
},"B").start();
}
}
class ThreadExample{
/**
* 锁的是方法调用者,即哪个ThreadExample类实例对象调用这个方法就锁哪个
*/
public synchronized void test1(){}
/**
* 由于静态方法是在类加载时就被加载,所以锁的是class对象,class对象是唯一的
* ThreadExample example = new ThreadExample();
* Class<? extends ThreadExample> exampleClass = example.getClass();
* 锁的是exampleClass
*
* 一个类中如果拥有多个静态同步方法,则他们锁的都是同一个Class类模板
*/
public static synchronized void test2(){}
}
五、集合类不安全
5.1 List集合
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ListJUCTest {
public static void main(String[] args) {
/**
* List list = new ArrayList<String>(); 并发情况下ArrayList不安全,会出现并发修改异常
* 解决方案:
* 1.List<String> list = new Vector<>();
* 2.List<String> list = Collections.synchronizedList(new ArrayList<>());
* 3.List<String> list = new CopyOnWriteArrayList<>();
* CopyOnWrite 写入时复制, COW 计算机程序设计领域的一种优化策略
* 优点:在写入的时候避免覆盖,造成数据问题
* 源码:
* public boolean add(E e) {
* final ReentrantLock lock = this.lock;
* lock.lock();
* try {
* Object[] elements = getArray();
* int len = elements.length;
* Object[] newElements = Arrays.copyOf(elements, len + 1);
* newElements[len] = e;
* setArray(newElements);
* return true;
* } finally {
* lock.unlock();
* }
* }
*/
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);
},String.valueOf(i)).start();
}
}
}
5.2 Set集合
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
public class SetJUCTest {
public static void main(String[] args) {
/**
* 由HashSet并发情况下不安全
* Set<String> set = new HashSet<>(); 并发情况下会报ConcurrentModificationException
* 解决方案:
* Set<String> set = Collections.synchronizedSet(new HashSet<>());
* Set<String> set = new CopyOnWriteArraySet<>();
*/
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 20; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
HashSet底层实现:
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
//set的本质就是map,新增的值是作为key值传入map中,key为不可重复的,value值是PRESENT,是一个常量
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
5.3 Map集合
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class MapJUCTest {
public static void main(String[] args) {
/**
* HashMap集合在并发情况下不是线程安全的 ConcurrentModificationException
* Map<String, Object> map = new HashMap<>();
*
* 解决方案:
* 1.Map<String, Object> map = Collections.synchronizedMap(new HashMap<>());
* 2.Map<String, Object> map = new ConcurrentHashMap<>();
*/
Map<String, Object> map = new ConcurrentHashMap<>();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
map.put(UUID.randomUUID().toString().substring(0, 5), new Object());
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}
六、Callable集合
使用场景:常用于需要等待多个线程的返回结果进行整合得出最终结果的场景
例子:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task1 = new FutureTask<Integer>(new Task1());
FutureTask<Integer> task2 = new FutureTask<Integer>(new Task2());
FutureTask<Integer> task3 = new FutureTask<Integer>(new Task3());
new Thread(task1).start();
new Thread(task2).start();
new Thread(task3).start();
//get()方法会阻塞,直到线程执行完成之后返回结果
Integer result = task1.get()+task2.get()+task3.get();
System.out.println(result);
}
}
class Task1 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("Task1线程启动");
Thread.sleep(1000);
System.out.println("Task1线程结束");
return 10;
}
}
class Task2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("Task2线程启动");
Thread.sleep(2000);
System.out.println("Task2线程结束");
return 20;
}
}
class Task3 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("Task3线程启动");
Thread.sleep(3000);
System.out.println("Task3线程结束");
return 30;
}
}
七、常用的辅助类
7.1 CountDownLatch
例子:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
//减法计数器
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i <10 ; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" is running");
//数量减1
countDownLatch.countDown();
}).start();
}
//阻塞,直到计数器归零
countDownLatch.await();
System.out.println("线程执行结束");
}
}
7.2 CyclicBarrier
例子:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3,()->{
System.out.println("工作已完成");
});
for (int i = 0; i < 3; i++) {
/**
* 匿名内部类不能访问外部类方法中的局部变量,除非变量被声明为final类型
* 1. 这里所说的“匿名内部类”主要是指在其外部类的成员方法内定义,
* 同时完成实例化的类,若其访问该成员方法中的局部变量,局部变量必须要被final修饰。
* 2. 原因是编译程序实现上的困难:内部类对象的生命周期会超过局部变量的生命周期。
* 局部变量的生命周期:当该方法被调用时,该方法中的局部变量在栈中被创建,
* 当方法调用结束时,退栈,这些局部变量全部死亡。而内部类对象生命周期与其它类一样:
* 自创建一个匿名内部类对象,系统为该对象分配内存,直到没有引用变量指向分配给该对象
* 的内存,它才会死亡(被JVM垃圾回收)。所以完全可能出现的一种情况是:成员方法已调用结束,
* 局部变量已死亡,但匿名内部类的对象仍然活着。
* 3. 如果匿名内部类的对象访问了同一个方法中的局部变量,就要求只要匿名内部类对象还活着,
* 那么栈中的那些它要所访问的局部变量就不能“死亡”。
* 4. 解决方法:匿名内部类对象可以访问同一个方法中被定义为final类型的局部变量。
* 定义为final后,编译程序的实现方法:对于匿名内部类对象要访问的所有final类型局部变量,
* 都拷贝成为该对象中的一个数据成员。这样,即使栈中局部变量已死亡,但被定义为final类型的局
* 部变量的值永远不变,因而匿名内部类对象在局部变量死亡后,照样可以访问final类型的局部变量,
* 因为它自己拷贝了一份,且与原局部变量的值始终一致。
*/
final int temp = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"完成了第"+temp+"部分的工作");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
7.3 Semaphor
作用:多个共享互斥资源的使用。并发限流,控制最大线程数。
例子:
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* 可以想象成停车,一共只有6个停车位,共3辆车,当车位停满时,其他车辆只能等待,直到停车位有空出来才能进行停车
*/
public class SemaphoreTest {
public static void main(String[] args) {
//创建一个 Semaphore与给定数量的许可证和非公平公平设置。如停车位
final Semaphore semaphore = new Semaphore(3);
//共6辆车
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
//从此信号量获取许可证,阻止直到可用,否则线程为interrupted。如获取停车位
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "正在停车");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放许可证,将其返回到信号量。
//发放许可证,将可用许可证的数量增加一个。 如果任何线程尝试获取许可证,那么选择一个被授予刚被释放的许可证。 (重新)线程调度用于线程调度。
//没有要求发布许可证的线程必须通过调用acquire()获取该许可证。 信号量的正确使用通过应用程序中的编程惯例来确定。
// 如离开停车位
semaphore.release();
}
}).start();
}
}
}
八、读写锁(ReadWriteLock)
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 1; i <= 10; i++) {
final int temp = i;
new Thread(()->{
myCache.push(String.valueOf(temp),temp);
},String.valueOf(i)).start();
}
for (int i = 11; i <= 20; i++) {
final int temp = i;
new Thread(()->{
myCache.get(String.valueOf(temp));
},String.valueOf(i)).start();
}
System.out.println("线程已全部启动");
}
}
class MyCache{
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public volatile Map<String,Object> map = new HashMap<String,Object>();
//写
public void push(String key,Object object){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"开始写入数据"+object);
map.put(key,object);
System.out.println(Thread.currentThread().getName()+"写入数据"+object+"成功");
} catch (Exception e) {
e.printStackTrace();
}finally {
readWriteLock.writeLock().unlock();
}
}
//读
public void get(String key){
readWriteLock.readLock().lock();
try {
Object o = null;
System.out.println(Thread.currentThread().getName()+"开始读取数据"+key);
while (o==null){
o = map.get(key);
}
System.out.println(Thread.currentThread().getName()+"读取数据"+o+"成功");
}finally {
readWriteLock.readLock().unlock();
}
}
}
九、阻塞队列
队列阻塞不得不阻塞的情况:
1.向队列中写入数据时,队列数据已满,需要阻塞等待队列数据被取出,腾出空间
2.向队列中读取数据时,队列数据为空,需要阻塞等待数据存入队列
阻塞队列的一般使用场景:多线程并发处理,线程池
阻塞队列的四组API:
操作 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(,) |
移除 | remove() | poll() | take() | poll(,) |
检测队首元素 | element() | peek() | - | - |
案例:
/**
* 抛出异常
*/
public static void test1(){
ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
// System.out.println(queue.element());//抛出异常,java.util.NoSuchElementException
System.out.println(queue.add("a"));
System.out.println(queue.add("b"));
System.out.println(queue.add("c"));
// System.out.println(queue.add("d"));//抛出异常,java.lang.IllegalStateException: Queue full
System.out.println(queue.remove());
System.out.println(queue.remove());
System.out.println(queue.remove());
// System.out.println(queue.remove());//抛出异常,java.util.NoSuchElementException
}
/**
* 有返回值,不抛出异常
*/
public static void test2(){
ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
// System.out.println(queue.peek());//返回值为null
System.out.println(queue.offer("a"));
System.out.println(queue.offer("b"));
System.out.println(queue.offer("c"));
// System.out.println(queue.offer("d"));//返回值为false
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
// System.out.println(queue.poll());//返回值为null
}
/**
* 阻塞等待
*/
public static void test3() throws InterruptedException {
ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
queue.put("a");
queue.put("b");
queue.put("c");
// queue.put("d");//一直阻塞着
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
// System.out.println(queue.take());//一直阻塞着
}
/**
* 超时等待
*/
public static void test4() throws InterruptedException {
ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
System.out.println(queue.offer("a"));
System.out.println(queue.offer("b"));
System.out.println(queue.offer("c"));
long startTime = System.currentTimeMillis();
System.out.println(queue.offer("d", 1, TimeUnit.SECONDS));
long endTime = System.currentTimeMillis();
System.out.println("阻塞等待时间为"+(endTime-startTime)+"ms");
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll(1, TimeUnit.SECONDS));
}
十、同步队列(SynchronousQueue)
SynchronousQueue不存储元素,当执行了添加元素操作之后,必须先从里面取出值,才能再次进行添加操作
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class SynchronousQueueTest {
public static void main(String[] args) {
//同步队列
SynchronousQueue synchronousQueue = new SynchronousQueue();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "存入数据 - 1" );
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName() + "存入数据 - 2" );
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName() + "存入数据 - 3" );
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "移除数据" + synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "移除数据" + synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "移除数据" + synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
十一、线程池
程序的运行本质是在占用系统的资源,池化技术
的目的就是为了优化资源的使用
线程池
的好处:
- 降低资源的消耗
- 提高响应的速度
- 方便对线程的管理
- 线程复用、可以控制最大并发数
三大方法:
方法 | 作用 |
---|---|
newSingleThreadExecutor() | 创建只有一个线程的线程池,当使用此方法创建线程池进行多线程操作时,只有等线程池中唯一的线程执行当前任务结束释放之后才能被用于下一任务,不会新建其他线程。 |
newFixedThreadPool(int num) | 创建固定有num个线程的线程池 |
newCachedThreadPool() | 创建可动态变化的线程池 |
public static void threadPoolTest1(){
ExecutorService executor1 = null;
ExecutorService executor2 = null;
ExecutorService executor3 = null;
try{
//单个线程
executor1 = Executors.newSingleThreadExecutor();
//创建一个固定大小的线程池
executor2 = Executors.newFixedThreadPool(5);
//创建一个可动态变化的线程池
executor3 = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int temp = i;
executor1.execute(()->{
System.out.println(Thread.currentThread().getName()+"-> "+temp+" 正在执行!");
try {
TimeUnit.MICROSECONDS.sleep(100);
System.out.println(Thread.currentThread().getName()+"-> "+temp+" 执行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}finally {
executor1.shutdown();
}
}
源码分析:
1.newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
2.newFixedThreadPool(int num)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
3.newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static void threadPoolTest2(){
ThreadPoolExecutor executor = new ThreadPoolExecutor(2,
5,
0,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy());
try{
for (int i = 0; i < 9; i++) {
executor.execute(()->{
System.out.println(Thread.currentThread().getName()+" is running!");
});
}
}finally {
executor.shutdown();
}
}
定义线程池的最大线程数量方法依据:
- CPU密集型:电脑处理器是几核,就设为几,可以判处CPU 的效率最高
- IO密集型:判断你的程序中的IO操作有多少个,设置为大于它们的数量即可
十二、四大函数式接口
重点:lambda表达式、链式编程、函数式接口、Stream流式计算
函数式接口:
- Consumer
- Function
- Predicate
- Supplier
public static void consumerTest(){
// Consumer<String> consumer = new Consumer<String>() {
// @Override
// public void accept(String str) {
// System.out.println(str);
// }
// };
Consumer<String> consumer = (str)->{
System.out.println(str);
};
consumer.accept("consumer");
}
public static void functionTest(){
// Function<String,String> function = new Function<String, String>() {
// @Override
// public String apply(String s) {
// return s;
// }
// };
Function<String,String> function = (s)->{
return s;
};
System.out.println(function.apply("function"));
}
public static void predicateTest(){
// Predicate<String> predicate = new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return s.isEmpty()? false :true;
// }
// };
Predicate<String> predicate = (str)->{return str.isEmpty()?false:true;};
System.out.println(predicate.test("predicate"));
}
public static void supplierTest(){
// Supplier<String> supplier = new Supplier<String>() {
// @Override
// public String get() {
// return "supplier";
// }
// };
Supplier<String> supplier = ()->{return "supplier";};
System.out.println(supplier.get());
}
十三、Stream流式计算
举例(涉及知识点:lambda表达式、链式编程、函数式接口、Stream流式计算):
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 题目要求:一分钟内完成此题,只能用一行代码实现!
* 现在有五个用户!筛选:
* 1.ID必须为偶数
* 2.姓名必须转为大写字母
* 3.用户名字母倒着排序
* 4.年龄必须大于23岁
*/
public class StreamCalCulateTest {
public static void main(String[] args) {
User user1 = new User(1,"a",22);
User user2 = new User(2,"b",23);
User user3 = new User(3,"c",27);
User user4 = new User(4,"d",25);
User user5 = new User(5,"e",26);
User user6 = new User(6,"f",29);
User user7 = new User(7,"g",28);
User user8 = new User(8,"h",40);
List<User> list = Arrays.asList(user1,user2,user3,user4,user5,user6,user7,user8);
list.stream().filter(user -> {return user.getId()%2==0;})//ID必须为偶数
.filter(user->{return user.getAge()>23;})//年龄必须大于23岁
.map(user -> {return user.getName().toUpperCase();})//姓名必须转为大写字母
.sorted((u1,u2)->{return u2.compareTo(u1);})//用户名字母倒着排序
.forEach(System.out::println);
}
}
@Data
@AllArgsConstructor
class User{
private int id;
private String name;
private int age;
}
十四、ForkJoin
原理参考链接:
-
https://blog.csdn.net/tyrroo/article/details/81390202
-
https://blog.csdn.net/codingtu/article/details/88729498
-
https://blog.csdn.net/timheath/article/details/71307834
查看方式:
ForkJoinPool类–>ForkJoinPool类中的方法execute(ForkJoinTask<?> task) -->
–> RecursiveTask<V>
例子:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;
public class ForkJoinTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1();
System.out.println();
test2();
System.out.println();
test3();
}
/**
* 直接单线程执行,作为对比(慢),如果使用算法进行累加计算,比ForkJoin和流式计算更快
*/
public static void test1(){
Long sum = 0L;
Long start = System.currentTimeMillis();
for (Long i = 1L; i <=10000000000L; i++) {
sum = sum + i;
}
Long end = System.currentTimeMillis();
System.out.printf("结果为:%d,耗时:%d",sum,(end-start));
}
/**
* 使用ForkJoin(中等)
*/
public static void test2() throws ExecutionException, InterruptedException {
Long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinDemo forkJoinDemo = new ForkJoinDemo(1L,10000000000L);
ForkJoinTask<Long> forkJoinTask = forkJoinPool.submit(forkJoinDemo);
Long sum = forkJoinTask.get();
Long end = System.currentTimeMillis();
System.out.printf("结果为:%d,耗时:%d",sum,(end-start));
}
/**
* 使用流式计算(快)
*/
public static void test3(){
Long start = System.currentTimeMillis();
//使用Stream的并行流
long sum = LongStream.rangeClosed(1, 10000000000L).parallel().reduce(0, Long::sum);
Long end = System.currentTimeMillis();
System.out.printf("结果为:%d,耗时:%d",sum,(end-start));
}
}
class ForkJoinDemo extends RecursiveTask<Long>{
/**
* 此处阈值值会影响到ForkJoin的运行效率,选择合适的值可以使CPU的利用率达到最大,一般可以根据设备
* 能并行执行的线程数来决定,例如我的电脑能并行执行4个线程,要从1累加到100000,所以我选择的阈值为
* 100000/4 = 25000;不过由于ForkJoin框架中存在工作窃取机制,如果并行执行的线程提前执行完毕了,那
* 么它会去窃取新的线程来进行执行,这样的话,想要达到最佳的性能,只能自行测试得到最佳临界值
*/
private static final long THRESHOLD = 25000000L;
private Long start;
private Long end;
public ForkJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end-start<=THRESHOLD){
Long sum = 0L;
for (Long i = start; i <= end; i++) {
sum+=i;
}
return sum;
}else{
//ForkJoin
Long middle = (start + end)/2;
//拆分线程
ForkJoinDemo task1 = new ForkJoinDemo(start,middle);
//把线程压入队列
task1.fork();
ForkJoinDemo task2 = new ForkJoinDemo(middle+1,end);
task2.fork();
return task1.join()+task2.join();
}
}
}
十五、异步调用
例子:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* 异步调用 CompletableFuture ,类比Ajax
*
*/
public class FutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test2();
}
public static void test1() throws ExecutionException, InterruptedException {
//没有返回值的异步回调
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"is running");
});
System.out.println("1243532");
//获取阻塞执行结果
completableFuture.get();
}
public static void test2() throws ExecutionException, InterruptedException {
//有返回值的异步回调
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"is running");
int i = 10/0;
return 1000;
});
System.out.println(completableFuture.whenComplete((integer1, throwable) -> {
System.out.println("integer=>"+integer1); //输出正常的返回值
System.out.println("throwable=>"+throwable); //输出异常信息
}).exceptionally((e)->{
System.out.println(e.getMessage());
return 1244;
}).get());
}
}
十六、JMM(Java内存模型)
在Java中,所有实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间共享(本章用“共享变量”这个术语代指实例域,静态域和数组元素)。局部变量(Local Variables),方法定义参数(Java语言规范称之为Formal Method Parameters)和异常处理器参数(Exceptio Handler Parameters)不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。
Java线程之间的通信由Java内存模型(本文简称为JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。Java内存模型的抽象示意如下所示
-----来自《Java编程的艺术》
注意:JMM内存模式是不存在的东西,是一种概念、约定
关于JMM的一些同步的约定:
- 线程解锁前,必须把共享变量刷回主内存
- 线程加锁前,必须读取主存中的最新值到工作内存中
- 加锁和解锁是同一把锁
十七、Volatile
volatile是Java虚拟机提供的轻量级的同步机制
特性:
- 保证可见性
- 不保证原子性
- 禁止指令重排
验证:
- 可见性验证
/**
* 没加volatile之前,由于是本人电脑是多核的,每个线程运行时都会从主内存中拷贝一份flag副本到线程的本地内存中
* 运行,所以当其中一个线程修改了flag的值,另一个线程是不知晓的,visibilityTest()会出现死循环;
* 加了volatile之后,每个都直接从主内存获取共享变量flag的值,所以当flag的值被修改之后,其他线程可以及时获取
* 到相应的值
*/
private volatile static boolean flag;
/**
* 验证volatile的可见性
*/
public static void visibilityTest(){
new Thread(()->{
System.out.println("开始循环");
while (!flag){
}
System.out.println("结束循环");
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
}).start();
}
2.验证原子性
/**
* 加了volatile关键字,atomicityTest()最终得到的值不是20000,说明volatile不保证原子性。
*/
private volatile static int num = 0;
public static void add(){
num++;
}
/**
* volatile原子性测试
* 原子性:即不可分割,线程在执行任务时,不能被打扰,也不能被分割
*/
public static void atomicityTest(){
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 1; j <= 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println("num的值为"+num);
}
使用javap -c VolatileTest.class
可以对VolatileTest进行反编译,查看add()的实际执行过程如下所示
public static void add();
Code:
0: getstatic #16 // Field num:I
3: iconst_1
4: iadd
5: putstatic #16 // Field num:I
8: return
解决方案一:给add()添加synchronized关键字,即可保证原子性
解决方案二:不加lock或synchronized的情况下,使用java.util.concurrent.atomic
包下的原子类可以保证原子性
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
// num++; //非原子性操作
num.incrementAndGet(); //原子类加1的方法,CAS
}
public static void atomicityTest(){
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 1; j <= 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println("num的值为"+num);
}
原子类的底层都直接与操作系统挂钩,在内存中修改值
指令重排:
程序员编写的代码在JVM执行的时候,为了提高性能,编译器和处理器都会对代码编译后的指令进行重排序。分为3种:
1:编译器优化重排:
编译器的优化前提是在保证不改变单线程语义的情况下,对重新安排语句的执行顺序。
2:指令并行重排:
如果代码中某些语句之间不存在数据依赖,处理器可以改变语句对应机器指令的顺序
如:int x = 10;int y = 5;对于这种x y之间没有数据依赖关系的,机器指令就会进行重新排序。但是对于:int x = 10; int y = 5; int z = x+y;这种的,因为z和x y之间存在数据依赖(z=x+y)关系。在这种情况下,机器指令就不会把z排序在xy前面。
3:内存系统的重排序
处理器和主内存之间还存在一二三级缓存。这些读写缓存的存在,使得程序的加载和存取操作,可能是乱序无章的
----来自《Java并发编程之验证volatile指令重排-理论篇
volatile可以通过内存屏障避免指令重排。
作用:
- 保证特定的操作的执行顺序
- 可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)
十八、单例模式
-
饿汉式
/** * 饿汉式 * 缺点:可能会浪费内存 */ public class HungryTypeSingle { private HungryTypeSingle(){} private static final HungryTypeSingle HUNGRY_TYPE_SINGLE = new HungryTypeSingle(); public static HungryTypeSingle getInstance(){ return HUNGRY_TYPE_SINGLE; } }
-
DCL懒汉式
/** * 普通懒汉式 * 缺点:只适用于单线程下,多线程下使用有问题 */ public class LazyTypeSingle { private LazyTypeSingle(){} private static LazyTypeSingle lazyTypeSingle; public static LazyTypeSingle getInstance(){ if (lazyTypeSingle==null){ lazyTypeSingle = new LazyTypeSingle(); } return lazyTypeSingle; } } //双重检测锁模式的懒汉式单例 简称DCL懒汉式单例 private volatile static LazyTypeSingle lazyTypeSingle; public static LazyTypeSingle getInstance(){ if (lazyTypeSingle==null) { synchronized (LazyTypeSingle.class) { if (lazyTypeSingle == null) { lazyTypeSingle = new LazyTypeSingle(); //不是一个原子性操作 } } } return lazyTypeSingle; } /** * 对于两次lazyTypeSingle是否为空的判断解释: * 1.为何在synchronization外面的判断? * 为了提高性能!如果拿掉这次的判断那么在行的时候就会直接的运行synchronization, * 所以这会使每个getInstance()都会得到一个静态内部锁,这样的话锁的获得以及释放的开销 * (包括上下文切换,内存同步等)都不可避免,降低了效率。所以在synchronization前面再 * 加一次判断是否为空,则会大大降低synchronization块的执行次数。 * * 2.为何在synchronization内部还要执行一次呢? * 因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。 * 注意:双重检验情况下,保存实例的唯一的静态变量要用volatile修饰,否则由于线程安全原因,一个类仍然有 * 会生成多个实例 */
注意:
反射能破坏绝大多数单例,但是无法破坏枚举类单例单例模式被破坏:
public static void main(String[] args) throws Exception { LazyTypeSingle lazyTypeSingle = LazyTypeSingle.getInstance(); Constructor<LazyTypeSingle> constructor = LazyTypeSingle.class.getDeclaredConstructor(null); constructor.setAccessible(true); LazyTypeSingle lazyTypeSingle1 = constructor.newInstance(); LazyTypeSingle lazyTypeSingle2 = constructor.newInstance(); System.out.println(lazyTypeSingle); System.out.println(lazyTypeSingle1); System.out.println(lazyTypeSingle2); }
newInstance()源码:
@CallerSensitive public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } } if ((clazz.getModifiers() & Modifier.ENUM) != 0) //此处注意,枚举类默认是单例且不可被反射破坏 throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }
自己写的一个枚举类
源码:
public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } }
IDEA中对EnumSingle.class进行反编译和使用
javap -p EnumSingle.class
反编译之后看到的代码:IDEA :
public enum EnumSingle { INSTANCE; //此处为不正确的构造器方法,IDEA和javap -p EnumSingle.class 命令没能完整地反编译出来 private EnumSingle() { } public EnumSingle getInstance() { return INSTANCE; } }
javap -p EnumSingle.class
:public final class cn.shentianlan.single.EnumSingle extends java.lang.Enum<cn.she ntianlan.single.EnumSingle> { public static final cn.shentianlan.single.EnumSingle INSTANCE; private static final cn.shentianlan.single.EnumSingle[] $VALUES; public static cn.shentianlan.single.EnumSingle[] values(); public static cn.shentianlan.single.EnumSingle valueOf(java.lang.String); private cn.shentianlan.single.EnumSingle(); public cn.shentianlan.single.EnumSingle getInstance(); static {}; }
使用jad.exe工具,命令为
jad -sjava EnumSingle.class
:// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) // Source File Name: EnumSingle.java package cn.shentianlan.single; public final class EnumSingle extends Enum { public static EnumSingle[] values() { return (EnumSingle[])$VALUES.clone(); } public static EnumSingle valueOf(String name) { return (EnumSingle)Enum.valueOf(cn/shentianlan/single/EnumSingle, name); } private EnumSingle(String s, int i) { super(s, i); } public EnumSingle getInstance() { return INSTANCE; } public static final EnumSingle INSTANCE; private static final EnumSingle $VALUES[]; static { INSTANCE = new EnumSingle("INSTANCE", 0); $VALUES = (new EnumSingle[] { INSTANCE }); } }
十九、理解CAS(CompareAndSet)
CAS是CPU的并发原语
原语:
计算机进程的控制通常由原语完成。所谓原语,一般是指由若干条指令组成的程序段,用来实现某个特定功能,在执行过程中不可被中断。在操作系统中,某些被进程调用的操作,如队列操作、对信号量的操作、检查启动外设操作等,一旦开始执行,就不能被中断,否则就会出现操作错误,造成系统混乱。所以,这些操作都要用原语来实现 原语是操作系统核心(不是由进程,而是由一组程序模块组成)的一个组成部分,并且常驻内存,通常在管态下执行。原语一旦开始执行,就要连续执行完,不允许中断 (来自百度百科)
CAS例子:
package cn.shentianlan.cas;
import java.util.concurrent.atomic.AtomicInteger;
/**
* CAS: compareAndSet 比较并交换
*/
public class CASTest {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//达到期望值2020,此时atomicInteger.get()的值将被交换为2021
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
//由于上面的程序已经将atomicInteger.get()的值交换为2021,所以达不到期望值2020,tomicInteger.get()的 //值为2021
System.out.println(atomicInteger.compareAndSet(2020, 2022));
System.out.println(atomicInteger.get());
}
}
atomicInteger.incrementAndGet();//加1操作,直接在内存中取值进行加1操作,效率很高
查看源码:
AtomicInteger类:
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
/**
* Atomically increments by one the current value.
* 原子地将当前的值加1
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
Unsafe类:
存在的意义:Java无法直接操作计算机内存,但是Java可以通过调用C++操作内存,也就是native方法,Unsafe类相当于Java的后门,通过这个类Java可以进行内存操作
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//从内存中获取值
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
CAS:比较当前工作内存的值和主内存的值,如果这个值是期望的,则执行操作,否则一直循环(自旋锁)
缺点:
- 循环会耗时
- 一次性只能保证一个共享变量的原子性
- ABA问题
二十、原子引用解决ABA问题
ABA问题
- 在多线程场景下CAS会出现ABA问题,关于ABA问题这里简单科普下,例如有2个线程同时对同一个值(初始值为A)进行CAS操作,这三个线程如下
- 1.线程1,期望值为A,欲更新的值为B
- 2.线程2,期望值为A,欲更新的值为B
- 线程1抢先获得CPU时间片,而线程2因为其他原因阻塞了,线程1取值与期望的A值比较,发现相等然后将值更新为B,然后这个时候出现了线程3,期望值为B,欲更新的值为A,线程3取值与期望的值B比较,发现相等则将值更新为A,此时线程2从阻塞中恢复,并且获得了CPU时间片,这时候线程2取值与期望的值A比较,发现相等则将值更新为B,虽然线程2也完成了操作,但是线程2并不知道值已经经过了A->B->A的变化过程。
ABA问题带来的危害:
小明在提款机,提取了50元,因为提款机问题,有两个线程,同时把余额从100变为50
线程1(提款机):获取当前值100,期望更新为50,
线程2(提款机):获取当前值100,期望更新为50,
线程1成功执行,线程2某种原因block了,这时,某人给小明汇款50
线程3(默认):获取当前值50,期望更新为100,
这时候线程3成功执行,余额变为100,
线程2从Block中恢复,获取到的也是100,compare之后,继续更新余额为50!!!
此时可以看到,实际余额应该为100(100-50+50),但是实际上变为了50(100-50+50-50)这就是ABA问题带来的成功提交。解决方法: 在变量前面加上版本号,每次变量更新的时候变量的版本号都+1,即A->B->A就变成了1A->2B->3A。
----来自《CAS的ABA问题详解》
有坑的例子:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* 原子引用
*/
public class AtomicStampedReferenceTest {
public static void main(String[] args) {
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(2020,1);
new Thread(()->{
//获取版本号
int stamp = atomicStampedReference.getStamp();
System.out.println("a1:stamp = "+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(2020, 2021,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a2:stamp = "+atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(2021, 2020,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a3:stamp = "+atomicStampedReference.getStamp());
},"a").start();
new Thread(()->{
//获取版本号
int stamp = atomicStampedReference.getStamp();
System.out.println("b1:stamp = "+stamp);
//与当前值进行比较
System.out.println(atomicStampedReference.compareAndSet(2020, 2315, stamp, stamp + 1));
System.out.println("b2:stamp = "+atomicStampedReference.getStamp());
},"b").start();
}
}
注意:上面的例子由于使用的泛型对象为Integer,此处存在大坑
- 【强制】所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
说明:对于 Integer var = ? 在-128 至 127 之间的赋值,Integer 对象是在 IntegerCache.cache 产生,
会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都
会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。 —来自《阿里巴巴Java开发手册》
正确的例子(将Integer的赋值设定在-128 至 127 之间):
package cn.shentianlan.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* 原子引用
*/
public class AtomicStampedReferenceTest {
public static void main(String[] args) {
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(1,1);
new Thread(()->{
//获取版本号
int stamp = atomicStampedReference.getStamp();
System.out.println("a1:stamp = "+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 2,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a2:stamp = "+atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(2, 1,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a3:stamp = "+atomicStampedReference.getStamp());
},"a").start();
new Thread(()->{
//获取版本号
int stamp = atomicStampedReference.getStamp();
System.out.println("b1:stamp = "+stamp);
//与当前值进行比较
System.out.println(atomicStampedReference.compareAndSet(1, 3, stamp, stamp + 1));
System.out.println("b2:stamp = "+atomicStampedReference.getStamp());
},"b").start();
}
}
二十一、各种锁的理解
21.1 可重入锁
可重入锁(递归锁):可以在获取到锁的情况下继续获取锁而不会造成死锁
验证方法:
-
使用synchronized
/** * 可重入锁 */ public class ReentrantLockTest1 { public static void main(String[] args) { new Thread(()->{ Person.say(); }).start(); new Thread(()->{ Person.eat(); }).start(); } } class Person{ public static synchronized void eat(){ System.out.println(Thread.currentThread().getName()+" eating"); say();//say已被加锁 } public static synchronized void say(){ System.out.println(Thread.currentThread().getName()+" saying"); } }
-
使用ReentrantLock
package cn.shentianlan.lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockTest2 { public static void main(String[] args) { new Thread(()->{ Person2.say(); }).start(); new Thread(()->{ Person2.eat(); }).start(); } } class Person2{ static Lock lock = new ReentrantLock(); public static void eat(){ lock.lock(); try { System.out.println(Thread.currentThread().getName()+" eating"); say();//say已被加锁 } finally { lock.unlock(); } } public static void say(){ lock.lock(); //lock.lock() 此次若取消注释,将会出现死锁,因为取消注释之后这里将拥有两把不同的锁,解锁只是解了 //一把锁 try { System.out.println(Thread.currentThread().getName()+" saying"); } finally { lock.unlock(); } } }
注意:使用ReentrantLock
和使用synchronized不同,加锁的同时必须注意所加的锁的数量,lock.lock()和lock.unlock()的数量必须一一配对,不会有可能会造成死锁的情况发生
21.2 自旋锁
自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
package cn.shentianlan.lock;
import cn.shentianlan.day07.pc.A;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* 自旋锁测试
*/
class SpinLockTest{
public static void main(String[] args) {
MySpinlock mySpinlock = new MySpinlock();
new Thread(()->{
mySpinlock.myLock();
try {
TimeUnit.SECONDS.sleep(6);
} catch (Exception e) {
e.printStackTrace();
} finally {
mySpinlock.myUnLock();
}
},"A").start();
new Thread(()->{
mySpinlock.myLock();
try {
} finally {
mySpinlock.myUnLock();
}
},"B").start();
}
}
//自定义自旋锁
class MySpinlock {
AtomicReference atomicReference = new AtomicReference();
public void myLock() {
Thread thread = Thread.currentThread();
while (!atomicReference.compareAndSet(null, thread)) {
}
System.out.println(thread.getName()+" myLock");
}
public void myUnLock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(thread.getName()+" myUnLock");
}
}
21.3 死锁排查
package cn.shentianlan.lock;
import org.omg.CORBA.TIMEOUT;
import java.util.concurrent.TimeUnit;
public class DeadLockTest {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new MyThread(lockA,lockB)).start();
new Thread(new MyThread(lockB,lockA)).start();
}
}
class MyThread implements Runnable{
private String lockA;
private 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(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+" lock "+lockB+"; get "+lockA);
}
}
}
}
排查方法
-
使用
jps -l
定位进程号(jps是Java自带的工具) -
使用
jstack 进程号
找到死锁问题(jstack是Java自带的工具)