JUC并发编程
1. 什么是JUC
JUC是java.util.concurrent的简写。在jdk官方手册中可以看到juc相关的jar包有三个。
用中文概括一下,JUC的意思就是java并发编程工具包
2. 线程和进程
进程:是系统中正在运行的一个程序,程序一旦运行就是进程。
线程:是进程的一个实体,是进程的一条执行路径。
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 */
}
}
}
// native : 本地方法
private native void start0();
并发、并行
并发和并行从宏观来看,都是为进行多任务运行。
并发
并发是指两个或两个以上的任务在同一时间段内运行,即一个时间段中有几个任务都处于已启动运行到运行完毕之间,这若干任务在同一CPU上运行但任一个时刻点上只有一个任务运行。
eg:A进程的线程1和A进程的线程2在同一个核上,在同一时候只能运行一个,但在一段时间内都可以运行。
并行
并行是指两个或者两个以上任务在同一时刻同时运行
eg:A进程的线程1和B进程的线程1同时刻在不同核上运行
查看可执行多少线程
方式一:
方式二:
方式三:
作为编程人员,当然要用代码来体现
public class Test {
public static void main(String[] args) {
// 获取CPU核数
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
线程有几种状态
开源码——>
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超市等待
TIMED_WAITING,
//终止
TERMINATED;
}
wait/sleep 区别
TimeUnit.DAYS.sleep(1);
TimeUnit.SECONDS.sleep(2);
- 我们首先要知道sleep()方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
- wait会释放锁,sleep不会释放
- wait必须在同步代码块中使用,sleep可以再任何地方使用
- 外套不需要捕获异常,sleep必须要捕获异常
3. Lock(锁)
synchronized
public class SaleTicketDemo01 {
public static void main(String[] args) {
//线程是一个单独的资源类,没有任何的附属操作
//并发:多线程操作同一个资源类,将资源类丢入线程
Ticket ticket = new Ticket();
//Lambda 表达式 (参数)->{代码}
new Thread(()-> {
for (int i=1;i<60;i++){
ticket.sale();
}
},"A").start();
new Thread(()-> {
for (int i=1;i<60;i++){
ticket.sale();
}
},"B").start();
new Thread(()-> {
for (int i=1;i<60;i++){
ticket.sale();
}
},"C").start();
}
}
class Ticket{
private int number = 20;
public synchronized void sale(){
if (number>0){
System.out.println(Thread.currentThread().getName()+"买了第"+(number--)+"张票,"+"还有"+number+"张票");
}
}
}
Lock接口
公平锁和非公平锁在概念上的区分:是否优先进入队列。优先进入队列的线程是公平锁,优先抢占资源的是非公平锁。
public class SaleTicketDemo02 {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(()-> {
for (int i=1;i<60;i++){
ticket.sale();
}
},"A").start();
new Thread(()-> {
for (int i=1;i<60;i++){
ticket.sale();
}
},"B").start();
new Thread(()-> {
for (int i=1;i<60;i++){
ticket.sale();
}
},"C").start();
}
}
//1.new ReentrantLock();
//2.加锁
//3.解锁
class Ticket2{
private int number = 20;
Lock lock = new ReentrantLock();
public void sale(){
//加锁
lock.lock();
try {
if (number>0){
System.out.println(Thread.currentThread().getName()+"买了第"+(number--)+"张票,"+"还有"+number+"张票");
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//解锁
lock.unlock();
}
}
}
synchronized和Lock区别
1、首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2、synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3、synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4、用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,【lock.tryLock()】如果尝试获取没有获取到的锁,线程可以不用一直等待就结束了;
5、synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可);
6、Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
4. 生产者消费者问题
synchronized版
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) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
}
}
class Data{
private int number = 0;
// +1
public synchronized void increment() throws InterruptedException {
if (number!=0){
// 等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"==>"+number);
// 通知其他的线程 +1完毕
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
if (number==0){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"==>"+number);
this.notifyAll();
}
}
问题:当有大于2个线程时,出现问题:虚假唤醒
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) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"D").start();
}
}
解决虚假唤醒:使用while
class Data{
private int number = 0;
// +1
public synchronized void increment() throws InterruptedException {
while (number!=0){
// 等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"==>"+number);
// 通知其他的线程 +1完毕
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
while (number==0){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"==>"+number);
this.notifyAll();
}
}
Lock
public class B {
public static void main(String[] args) {
Data2 data2 = new Data2();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data2.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data2.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data2.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data2.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"D").start();
}
}
class Data2{
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// +1
public void increment() throws InterruptedException {
try {
lock.lock();
while (number!=0){
// 等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"==>"+number);
// 通知其他的线程 +1完毕
condition.signalAll();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
// -1
public void decrement() throws InterruptedException {
try {
lock.lock();
while (number==0){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"==>"+number);
condition.signalAll();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
解决:使用多个lock.newCondition(); 并使用signal来指定要唤醒的一个线程
package com.example.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* author : chenpeixue
* Date : :2022/8/2316:31
**/
public class C {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data3.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data3.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data3.increment2();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data3.decrement2();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"D").start();
}
}
class Data3{
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
Condition condition4 = lock.newCondition();
// +1
public void increment() throws InterruptedException {
try {
lock.lock();
while (number!=0){
// 等待
condition1.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"==>"+number);
// 通知其他的线程 +1完毕
condition2.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
// -1
public void decrement() throws InterruptedException {
try {
lock.lock();
while (number==0){
condition2.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"==>"+number);
condition3.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
// +1
public void increment2() throws InterruptedException {
try {
lock.lock();
while (number!=0){
// 等待
condition3.await();
}
number ++;
System.out.println(Thread.currentThread().getName()+"==>"+number);
// 通知其他的线程 +1完毕
condition4.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
// -1
public void decrement2() throws InterruptedException {
try {
lock.lock();
while (number==0){
condition4.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"==>"+number);
condition1.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
5. 深度理解锁
public class Test {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{phone.send_message();},"A").start();
// 情况1: 这种情况下,先打印哪一个? --发短信 打电话
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{phone.call();},"B").start();
}
}
class Phone{
public synchronized void send_message(){
// 情况2: 休眠4s情况下,先打印哪一个? --发短信 打电话
// synchronized 锁的对象的方法的调用者,两个方法用的是同一把锁,谁先拿到,谁先执行
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
public class Test1 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
// 情况3: 先执行那一个? --hello 发短信 普通方法,不受锁的影响
new Thread(()->{phone.send_message();},"A").start();
new Thread(()->{phone.hello();},"B").start();
}
}
class Phone2{
public synchronized void send_message(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("发短信");
}
public void hello(){
System.out.println("hello");
}
}
public class Test1 {
public static void main(String[] args) {
// 这是两个调用者,是两把锁
Phone2 phone = new Phone2();
Phone2 phone1 = new Phone2();
// 情况4: 先执行那一个? --打电话 发短信
new Thread(()->{phone.send_message();},"A").start();
new Thread(()->{phone1.call();},"B").start();
}
}
class Phone2{
public synchronized void send_message(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
public class Test2 {
public static void main(String[] args) {
Phone3 phone = new Phone3();
// 情况5:两个静态的方法 先执行那一个? -- 发短信 打电话
// static::静态方法 类一加载就有了,是class 模板 锁的是唯一的一个class,
new Thread(()->{phone.send_message();},"A").start();
new Thread(()->{phone.call();},"B").start();
}
}
class Phone3{
public static synchronized void send_message(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
public class Test2 {
public static void main(String[] args) {
Phone3 phone = new Phone3();
Phone3 phone2 = new Phone3();
// 情况6:两个对象调静态的方法 先执行那一个? -- 发短信 打电话
// static::静态方法 类一加载就有了,是class 模板 锁的是唯一的一个class,
new Thread(()->{phone.send_message();},"A").start();
new Thread(()->{phone2.call();},"B").start();
}
}
class Phone3{
public static synchronized void send_message(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
6. 线程不安全
CopyOnWriteArrayList
//java.util.ConcurrentModificationException 并发修改异常
public class Test1 {
public static void main(String[] args) {
// ArrayList : 在并发情况下是不安全的
// ArrayList<String> list = new ArrayList<>();
/**
* 解决方式:
* 1. Vector<String> list = new Vector<>();
* 2. List<String> list = Collections.synchronizedList(new ArrayList<String>());
* 3. CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
*/
Vector<String> list2 = new Vector<>();
CopyOnWriteArrayList<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();
}
}
}
vector与CopyOnWriteArrayList比较分析
Vector与CopyOnWriteArrayList简单比较分析 - 腾讯云开发者社区-腾讯云 (tencent.com)
CopyOnWriteArraySet
// java.util.ConcurrentModificationException 并发修改异常
public class SetTest {
public static void main(String[] args) {
//HashSet<String> set = new HashSet<>();
/**
* 解决方法
* 1. Set<Object> set = Collections.synchronizedSet(new HashSet<>());
* 2. CopyOnWriteArraySet<Object> set = new CopyOnWriteArraySet<>();
*/
CopyOnWriteArraySet<Object> set = new CopyOnWriteArraySet<>();
for (int i = 0; i <10 ; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
}).start();
}
}
}
ConcurrentHashMap
//java.util.ConcurrentModificationException 并发修改异常
public class MapTest {
public static void main(String[] args) {
// HashMap<String, String> map = new HashMap<String, String>();
/**
* 解决方式:
*1. ConcurrentHashMap<Object, Object> map = new ConcurrentHashMap<>();
*/
ConcurrentHashMap<Object, Object> map = new ConcurrentHashMap<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}
7. Callable
public class CallAbleTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 如何去使用Callable
// new Thread(new FutureTask<V>(Callable)).start()
MyThread thread = new MyThread();
FutureTask task = new FutureTask<>(thread);
new Thread(task,"A").start();
new Thread(task,"B").start(); // 结果会被缓存 ,提高效率
// get() 可能会阻塞,放到最后
Integer o = (Integer) task.get();
System.out.println(o);
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() {
System.out.println("call()");
return 1024;
}
}
8. 常用辅助类
CountDownLatch
//减法计数器
public class CountDownLatchTast {
public static void main(String[] args) throws InterruptedException {
//遇到必须要执行的任务时使用
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i <6 ; i++) {
new Thread(()->{
//减少锁存器的计数,如果计数达到零,释放所有等待的线程
//数量-1
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName()+" go out");
}).start();
}
//导致当前线程等到锁存器计数到零
countDownLatch.await();
System.out.println("Close Door");
}
}
CyclicBarrier
//加法计数器
public class CyclicBarrierTest {
public static void main(String[] args) {
//集齐7颗龙珠,召唤神龙
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, ()->{
System.out.println("召唤神龙成功");
});
for (int i = 0; i <7 ; i++) {
final int temp = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集"+temp+"颗龙珠");
try {
//等待
cyclicBarrier.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
}).start();
}
}
}
Semaphore
public class SemaphoreTest {
public static void main(String[] args) {
//6辆车 占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(3);
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
//释放许可
semaphore.release();
}
}).start();
}
}
}
9. ReadWriteLock
读锁:共享锁 readLock
写锁:独占锁 writeLock
读写锁 : 读的时候可以多个线程一起读,写的时候只能一个线程写
但是不能同时存在读和写进程 ,读写互斥,读读共享。
public class ReadWriteLockTest {
public static void main(String[] args) {
MyCache myCache = new MyCache();
//存
for (int i = 0; i < 10; i++) {
final int temp = i;
new Thread(()->{
myCache.put(temp+"",temp);
},String.valueOf(i)).start();
}
//取
for (int i = 0; i < 10; i++) {
final int temp = i;
new Thread(()->{
myCache.get(temp+"");
},String.valueOf(i)).start();
}
}
}
/*
自定义缓存
*/
class MyCache{
private volatile HashMap<String,Object> map = new HashMap();
//存入值
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完成");
}
//取值
public void get(String key) {
System.out.println(Thread.currentThread().getName() + "读取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取完成");
}
}
public class ReadWriteLockTest {
public static void main(String[] args) {
MyCache myCache = new MyCache();
//存
for (int i = 0; i < 10; i++) {
final int temp = i;
new Thread(()->{
myCache.put(temp+"",temp);
},String.valueOf(i)).start();
}
//取
for (int i = 0; i < 10; i++) {
final int temp = i;
new Thread(()->{
myCache.get(temp+"");
},String.valueOf(i)).start();
}
}
}
class MyCache{
private volatile HashMap<String,Object> map = new HashMap();
//读写锁
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//存入值
public void put(String key, Object value) {
//写锁
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完成");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
readWriteLock.writeLock().unlock();
}
}
//取值
public void get(String key) {
//读锁
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取完成");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
readWriteLock.readLock().unlock();
}
}
}
10. 阻塞队列
写入时,若队满,则必须阻塞等待
读取时,若队空,则必须阻塞等待生产
抛出异常
public class Test {
public static void main(String[] args) {
test();
}
public static void test(){
//这里要写容量大小
ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<>(3);
//添加元素
System.out.println(queue.add("a"));
System.out.println(queue.add("b"));
System.out.println(queue.add("c"));
//抛出异常:java.lang.IllegalStateException: Queue full
//System.out.println(queue.add("d"));
//查看队首元素
System.out.println(queue.element());
//移除元素
System.out.println(queue.remove());
System.out.println(queue.remove());
System.out.println(queue.remove());
//java.util.NoSuchElementException
//System.out.println(queue.remove());
}
}
有返回值,不跑出异常
public class Test {
public static void main(String[] args) {
test2();
}
public static void test2(){
ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<>(3);
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.peek());//a
System.out.println("=======");
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
//System.out.println(queue.poll()); null
}
}
阻塞等待
public class Test {
public static void main(String[] args) throws InterruptedException {
test3();
}
public static void test3() throws InterruptedException {
ArrayBlockingQueue<Object> 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 class Test {
public static void main(String[] args) throws InterruptedException {
test4();
}
public static void test4() throws InterruptedException {
ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<>(3);
queue.offer("a");
queue.offer("b");
queue.offer("c");
queue.offer("d", 2, TimeUnit.SECONDS);// 此时,堆满,等待2秒后退出
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
queue.poll(2,TimeUnit.SECONDS);//此时,对空,等待2秒后退出
}
}
11. 同步队列
没有容量,进去一个元素,必须等取出来后,才能再进去一个元素
SynchronousQueue
public class SynchronousQueueDemo {
public static void main(String[] args) {
//同步队列
SynchronousQueue<Object> synchronousQueue = new SynchronousQueue<>();
//存
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" put 1");
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName()+" put 2");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName()+" put 3");
synchronousQueue.put("3");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"a").start();
//取
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"b").start();
}
}
12.线程池
在执行一个异步任务或并发任务时,往往是通过直接new Thread()方法来创建新的线程,这样做弊端较多,更好的解决方案是合理地利用线程池,
线程池的优势很明显,如下:
- 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
- 提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行;
- 方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率;
- 更强大的功能,线程池提供了定时、定期以及可控线程数等功能的线程池,使用方便简单。
三大方法
//Executors 工具类 3大方法
public class Demo {
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");
});
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//程序结束,关闭线程池
threadPool.shutdown();
}
}
}
七大参数
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, //约为21亿
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
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;
}
四种拒绝策略
//1. new ThreadPoolExecutor.AbortPolicy()// 银行满了,还有人来,但不去处理,会抛出异常
// 抛出异常java.util.concurrent.RejectedExecutionException:
// 2.new ThreadPoolExecutor.CallerRunsPolicy()); //哪来的回哪里
// 3.new ThreadPoolExecutor.DiscardPolicy()); //丢掉任务,但不会抛出异常
// 4.new ThreadPoolExecutor.DiscardOldestPolicy()); //尝试和最早的竞争,但不会抛出异常
手动创建一个线程池
public class Demo {
public static void main(String[] args) {
//自定义线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy()); //尝试和最早的竞争,但不会抛出异常
//最大承载:MAX+workQueue
try {
for (int i = 0; i <9 ; i++) {
//使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//程序结束,关闭线程池
threadPool.shutdown();
}
}
}
最大核心线程池大小改如何定义
-
CPU密集型 几核就是几,保证CPU效率最高
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, Runtime.getRuntime().availableProcessors(),//获取CPU核数 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());
-
IO密集型:判断你线程中十分耗IO的线程
13.四大函数式接口
简化编程模型
函数式接口:只有一个方法的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Function函数型接口
public class Demo {
public static void main(String[] args) {
// 输出输入的值
// Function<String, String> function = new Function<String, String>() {
// @Override
// public String apply(String str) {
// return str;
// }
// };
//只要是函数式接口,就可以用Lambda表达式简化
Function<String, String> function =(str)->{ return str;};
System.out.println(function.apply("123"));
}
}
Predicate断定型接口
//断定式接口:只能有一个参数,返回只能是一个布尔值
public class Demo02 {
public static void main(String[] args) {
// 判断字符串是否为空
// Predicate<String> predicate = new Predicate<String>() {
// @Override
// public boolean test(String str) {
// return str.isEmpty();
// }
// };
Predicate<String> predicate = (str)->{ return str.isEmpty();};
System.out.println(predicate.test(""));
}
}
Consumer消费型接口
public class Demo03 {
public static void main(String[] args) {
// 消费性接口:有输入,没有返回值
// 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("123");
}
}
Supplier供给型接口
public class Demo04 {
public static void main(String[] args) {
// 供给性接口:没有参数 只有返回值
// Supplier<Integer> supplier = new Supplier<Integer>() {
// @Override
// public Integer get() {
// return 1024;
// }
// };
Supplier<Integer> supplier = ()->{return 1024;};
System.out.println(supplier.get());
}
14.Stream流式计算
/**
* 1. ID必须是偶数
* 2. 年龄必须大于23岁
* 3.用户名转换为大写字母
* 4.用户名字母到这排序
* 5.只输出一个用户
*/
public class Test {
public static void main(String[] args) {
User u1 = new User(1, "a", 21);
User u2 = new User(2, "b", 22);
User u3 = new User(3, "c", 23);
User u4 = new User(4, "d", 24);
User u5 = new User(6, "e", 25);
//Arrays.asList() 将数组转化成List集合
List<User> users = Arrays.asList(u1, u2, u3, u4, u5);
//计算交给stream流
users.stream().filter(u->{return u.getId()%2==0;})
.filter(u->{return u.getAge()>23;})
.map(u->{return u.getName().toUpperCase();})
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
.limit(1)
.forEach(System.out::println);
}
}
15.ForkJoin
ForkJoin是由JDK1.7之后提供的多线程并发处理框架。ForkJoin框架的基本思想是分而治之。什么是分而治之?分而治之就是将一个复杂的计算,**按照设定的阈值分解成多个计算,然后将各个计算结果进行汇总。**相应的,ForkJoin将复杂的计算当做一个任务,而分解的多个计算则是当做一个个子任务来并行执行。
工作窃取算法
假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,**线程和队列一一对应,**比如A线程负责处理A队列里的任务。但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。
工作窃取算法的优点:
充分利用线程进行并行计算,并减少了线程间的竞争。
工作窃取算法的缺点:
在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且该算法会消耗更多的系统资源,比如创建多个线程和多个双端队列。
/**
* 如和使用ForkJoin?
* 1.通过ForkJoinPool 来执行
* 2.计算任务 ForkJoinPool. execute(ForkJoinTask<?>
* 3.extends RecursiveTask
*/
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start = 0L;
private Long end = 10_0000_0000L;
//临界值
private Long temp = 1_0000L;
public ForkJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
//计算方法
@Override
protected Long compute() {
Long sum = 0L;
if ((end -start)<temp){
for (Long i = start; i <end ; i++) {
sum+=i;
}
}else {
//Forkjoin
//如果任务大于阈值,就分裂成两个子任务计算
Long midder = (start+end)/2;//中间值
ForkJoinDemo test1 = new ForkJoinDemo(start, midder);
//把任务压入线程队列
test1.fork();
ForkJoinDemo test2 = new ForkJoinDemo(midder + 1, end);
//把任务压入线程队列
test2.fork();
//等待任务执行结束合并其结果
Long result1 = test1.join();
Long result2 = test2.join();
sum = result1 + result2;
}
return sum;
}
}
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//test1();
//test2();
test3();
}
//方式一:sum=499999999500000000 耗时:5326
public static void test1(){
Long sum = 0L;
long start = System.currentTimeMillis();
for (Long i = 0L; i <10_0000_0000L ; i++) {
sum+=i;
}
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 耗时:"+(end - start));
}
//方式二:Forkjoin sum=499934463999828390 耗时:3490
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinDemo forkJoinDemo = new ForkJoinDemo(0L, 10_0000_0000L);
// forkJoinPool.execute(forkJoinDemo);
//提交任务
ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoinDemo);
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 耗时:"+(end - start));
}
//方式三:stream流 sum=500000000500000000 耗时:142
public static void test3(){
long start = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0, 10_0000_0000).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 耗时:"+(end - start));
}
}
16.异步回调
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// //没有返回值的异步回调
// //发起一个请求
// CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(()->{
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// System.out.println(Thread.currentThread().getName()+" runAsync====>Void");
// });
// System.out.println("========");
// //获取阻塞执行结果
// voidCompletableFuture.get();
//有返回值的异步回调
CompletableFuture<Integer> uCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName()+" runAsync====>Integer");
int i = 1/0;
return 1024;
});
uCompletableFuture.whenComplete((t,u)->{
//success
// t 是 成功返回的结果
System.out.println("t=====>"+t);
// u 是 错误信息 java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
System.out.println("u=====>"+u);
}).exceptionally((e)->{
//fail
System.out.println(e.getMessage());
return 500;
}).get();
}
}
17.JMM
谈谈你对Volatile的理解
Volatile是Java虚拟机提供的轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
什么是JMM
JMM就是Java内存模型(java memory model)。
因为在不同的硬件生产商和不同的操作系统下,内存的访问有一定的差异,所以会造成相同的代码运行在不同的系统上会出现各种问题。所以java内存模型(JMM)屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的并发效果。
Java内存模型规定所有的变量都存储在主内存中,包括实例变量,静态变量,但是不包括局部变量和方法参数。每个线程都有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行。线程不能直接读写主内存中的变量。
每个线程的工作内存都是独立的,线程操作数据只能在工作内存中进行,然后刷回到主存。这是 Java 内存模型定义的线程基本工作方式。
JMM定义了什么
- 原子性
原子性指的是一个操作是不可分割,不可中断的,一个线程在执行时不会被其他线程干扰。
面试官拿笔写了段代码,下面这几句代码能保证原子性吗?
int i = 2;
int j = i;
i++;
i = i + 1;
第一句是基本类型赋值操作,必定是原子性操作。
第二句先读取i的值,再赋值到j,两步操作,不能保证原子性。
第三和第四句其实是等效的,先读取i的值,再+1,最后赋值到i,三步操作了,不能保证原子性。
- 可见性
可见性指当一个线程修改共享变量的值,其他线程能够立即知道被修改了。Java是利用volatile关键字来提供可见性的。当变量被volatile修饰时,这个变量被修改后会立刻刷新到主内存,当其它线程需要读取该变量时,会去主内存中读取新值
-
有序性
volatile关键字是使用内存屏障达到禁止指令重排序,以保证有序性。
八种内存交互操作
关于主内存与工作内存直接的具体交互协议,即一个变量如何从主内存拷贝到工作内存,如何从工作内存同步到主内存之间的实现细节,Java 内存模型定义来以下八种同步操作(了解即可,无需死记硬背):
- 锁定(lock): 作用于主内存中的变量,将他标记为一个线程独享变量。
- 解锁(unlock): 作用于主内存中的变量,解除变量的锁定状态,被解除锁定状态的变量才能被其他线程锁定。
- read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load 动作使用。
- load(载入):把 read 操作从主内存中得到的变量值放入工作内存的变量的副本中。
- use(使用):把工作内存中的一个变量的值传给执行引擎,每当虚拟机遇到一个使用到变量的指令时都会使用该指令。
- assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的 write 操作使用。
- write(写入):作用于主内存的变量,它把 store 操作从工作内存中得到的变量的值放入主内存的变量中。
我再补充一下JMM对8种内存交互操作制定的规则吧:
- 不允许read、load、store、write操作之一单独出现,也就是read操作后必须load,store操作后必须write。
- 不允许线程丢弃他最近的assign操作,即工作内存中的变量数据改变了之后,必须告知主存。
- 不允许线程将没有assign的数据从工作内存同步到主内存。
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过load和assign操作。
- 一个变量同一时间只能有一个线程对其进行lock操作。多次lock之后,必须执行相同次数unlock才可以解锁。
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值。在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值。
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量。
- 一个线程对一个变量进行unlock操作之前,必须先把此变量同步回主内存。
18.volatile
保证线程间变量的可见性
public class Test {
private static int num =0;
public static void main(String[] args) {
new Thread(()->{
while (num==0){}
},"A").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
num = 1;
System.out.println(num);
}
}
public class Test {
private volatile static int num =0;
public static void main(String[] args) {
new Thread(()->{
while (num==0){}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
num = 1;
System.out.println(num);
}
}
不保证原子性
public class Test {
private volatile static int num = 0;
public static void add(){
//不是原子性操作
num++;
}
public static void main(String[] args) {
for (int i = 0; i <20 ; i++) {
new Thread(()->{
for (int j = 0; j <1000 ; j++) {
add();
}
}).start();
}
//判断线程存活数量
//如果存活线程数量大于2,则线程还未执行完,因为main 和 GC 线程默认执行
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
lock和synchronized能实现原子性。那再不使用它们的情况下,如何保证原子性呢?
可以使用原子类,解决原子性问题。
public class Test {
//AtomicInteger 原子类
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
// num++;
//AtomicInteger + 1 方法
num.getAndIncrement();
}
public static void main(String[] args) {
for (int i = 0; i <20 ; i++) {
new Thread(()->{
for (int j = 0; j <1000 ; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
禁止指令重排序
为了使指令更加符合CPU的执行特性,最大限度的发挥机器的性能,提高程序的执行效率,只要程序的最终结果与它顺序化情况的结果相等,那么指令的执行顺序可以与代码逻辑顺序不一致,这个过程就叫做指令的重排序。
重排序的种类分为三种,分别是:编译器重排序,指令级并行的重排序,内存系统重排序。整个过程如下所示:
指令重排序在单线程是没有问题的,不会影响执行结果,而且还提高了性能。但是在多线程的环境下就不能保证一定不会影响执行结果了。
所以在多线程环境下,就需要禁止指令重排序。
volatile关键字禁止指令重排序有两层意思:
- 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行。
- 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
private static int a;//非volatile修饰变量
private static int b;//非volatile修饰变量
private static volatile int k;//volatile修饰变量
private void hello() {
a = 1; //语句1
b = 2; //语句2
k = 3; //语句3
a = 4; //语句4
b = 5; //语句5
//以下省略...
}
变量a,b是非volatile修饰的变量,k则使用volatile修饰。所以语句3不能放在语句1、2前,也不能放在语句4、5后。但是语句1、2的顺序是不能保证的,同理,语句4、5也不能保证顺序。
并且,执行到语句3的时候,语句1,2是肯定执行完毕的,而且语句1,2的执行结果对于语句3,4,5是可见的。
volatile禁止指令重排序的原理是什么?
内存屏障,也称内存栅栏,内存栅障,屏障指令等, 是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。
首先要讲一下内存屏障,内存屏障可以分为以下几类:
- LoadLoad 屏障:对于这样的语句Load1,LoadLoad,Load2。在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
- StoreStore屏障:对于这样的语句Store1, StoreStore, Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
- LoadStore 屏障:对于这样的语句Load1, LoadStore,Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
- StoreLoad 屏障:对于这样的语句Store1, StoreLoad,Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。
在每个volatile读操作后插入LoadLoad屏障,在读操作后插入LoadStore屏障。
在每个volatile写操作的前面插入一个StoreStore屏障,后面插入一个SotreLoad屏障。
19.玩转单例模式
volatile在单例模式中有较多的使用
那让我们 先回顾一下单例模式
//饿汉式
public class Hungry {
//当数据没有被使用,会浪费空间
private byte[] date1 = new byte[1024*1024];
private byte[] date2 = new byte[1024*1024];
private byte[] date3 = new byte[1024*1024];
private byte[] date4 = new byte[1024*1024];
//1.私有化类的构造器
private Hungry(){
}
//2. 创建类的对象
private static final Hungry hungry = new Hungry();
//3. 提供公共的静态方法,返回类的对象
public static Hungry getInstance(){
return hungry;
}
}
//懒汉式
public class LazyDemo {
//1.私有化类的构造器
private LazyDemo(){
System.out.println(Thread.currentThread().getName() + " ok");
}
//2.声明当前类的对象,没有初始化。
private static LazyDemo lazyDemo;
//3. 提供公共的静态方法,返回类的对象
public static LazyDemo getInstance(){
if (lazyDemo == null){
lazyDemo = new LazyDemo();
}
return lazyDemo;
}
//在单线程下,单例确实ok, 在多并发情况下,会出问题
public static void main(String[] args) {
for (int i = 0; i <10 ; i++) {
new Thread(()->{
LazyDemo.getInstance();
},"A").start();
}
}
}
由于出现问题,所以我们需要给LazyDemo对象添加锁,
public class LazyDemo {
//1.私有化类的构造器
private LazyDemo(){
System.out.println(Thread.currentThread().getName() + " ok");
}
//2.声明当前类的对象,没有初始化。
private volatile static LazyDemo lazyDemo;
//3. 提供公共的静态方法,返回类的对象
//双重检测锁模式的单例 ,又称为DCL懒汉式
public static LazyDemo getInstance(){
//第一个检测:为了减少线程对同步锁锁的竞争
if (lazyDemo==null){
synchronized (LazyDemo.class){
//第二个检测:保证单例
if (lazyDemo == null){
lazyDemo = new LazyDemo();
/**
* 不是原子性操作
* 1. 分配内存空间
* 2. 执行构造方法 初始化对象
* 3. 把对象指向 内存空间
*
* 可能会进行指令重排
* A 线程的执行顺序可能是1 3 2
* 当A 执行完3 还没执行2的时候 线程B进来执行 由于执行完3 会认为lazyMan
* 不为null(因为已经指向了内存空间) 所以B会返回未完成构造的lazyMan
* 所以需要在lazyMan上加volatile修饰,防止指令重排
*/
}
}
}
return lazyDemo;
}
//在多并发情况下
public static void main(String[] args) {
for (int i = 0; i <10 ; i++) {
new Thread(()->{
LazyDemo.getInstance();
},"A").start();
}
}
}
20.CAS
什么是CAS?
CAS机制是一种数据更新的方式。在具体讲什么是CAS机制之前,我们先来聊下在多线程环境下,对共享变量进行数据更新的两种模式:悲观锁模式和乐观锁模式。
悲观锁更新的方式认为:在更新数据的时候大概率会有其他线程去争夺共享资源,所以悲观锁的做法是:第一个获取资源的线程会将资源锁定起来,其他没争夺到资源的线程只能进入阻塞队列,等第一个获取资源的线程释放锁之后,这些线程才能有机会重新争夺资源。synchronized就是java中悲观锁的典型实现,synchronized使用起来非常简单方便,但是会使没争抢到资源的线程进入阻塞状态,线程在阻塞状态和Runnable状态之间切换效率较低(比较慢)。比如你的更新操作其实是非常快的,这种情况下你还用synchronized将其他线程都锁住了,线程从Blocked状态切换回Runnable华的时间可能比你的更新操作的时间还要长。
乐观锁更新方式认为:在更新数据的时候其他线程争抢这个共享变量的概率非常小,所以更新数据的时候不会对共享数据加锁。但是在正式更新数据之前会检查数据是否被其他线程改变过,如果未被其他线程改变过就将共享变量更新成最新值,如果发现共享变量已经被其他线程更新过了,就重试,直到成功为止。CAS机制就是乐观锁的典型实现。
CAS,是Compare and Swap的简称,在这个机制中有三个核心的参数:
- 主内存中存放的共享变量的值:V(一般情况下这个V是内存的地址值,通过这个地址可以获得内存中的值)
- 工作内存中共享变量的副本值,也叫预期值:A
- 需要将共享变量更新到的最新值:B
主存中保存V值,线程中要使用V值要先从主存中读取V值到线程的工作内存A中,然后计算后变成B值,最后再把B值写回到内存V值中。多个线程共用V值都是如此操作。CAS的核心是在将B值写入到V之前要比较A值和V值是否相同,如果不相同证明此时V值已经被其他线程改变,重新将V值赋给A,并重新计算得到B,如果相同,则将B值赋给V。
值得注意的是CAS机制中的这步步骤是原子性的(从指令层面提供的原子操作),所以CAS机制可以解决多线程并发编程对共享变量读写的原子性问题。
Atomic操作的底层实现正是利用的CAS机制
public class CAS {
//CAS compareandset 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2000);
//如果期望达到了,就更新
System.out.println(atomicInteger.compareAndSet(2000, 2022));//true
System.out.println(atomicInteger.get());//2022
System.out.println(atomicInteger.compareAndSet(2000, 2022));//false
System.out.println(atomicInteger.get());//2022
}
}
CAS机制缺点
1. ABA问题
ABA问题:CAS在操作的时候会检查变量的值是否被更改过,如果没有则更新值,但是带来一个问题,最开始的值是A,接着变成B,最后又变成了A。经过检查这个值确实没有修改过,因为最后的值还是A,但是实际上这个值确实已经被修改过了。
public class CAS {
//CAS compareandset 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2000);
//ABA问题
System.out.println(atomicInteger.compareAndSet(2000, 2022));//true
System.out.println(atomicInteger.get());//2022
System.out.println(atomicInteger.compareAndSet(2022, 2000));//true
System.out.println(atomicInteger.get());//2000
System.out.println(atomicInteger.compareAndSet(2000, 2022));//true
System.out.println(atomicInteger.get());//2022
}
}
为了解决这个问题,在每次进行操作的时候加上一个版本号,每次操作的就是两个值,一个版本号和某个值,A——>B——>A问题就变成了1A——>2B——>3A。
在jdk中提供了AtomicStampedReference类解决ABA问题,用Pair这个内部类实现,包含两个属性,分别代表版本号和引用,在compareAndSet中先对当前引用进行检查,再对版本号标志进行检查,只有全部相等才更新值。
public class CAS {
//CAS compareandset 比较并交换
public static void main(String[] args) {
AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(1,1);
new Thread(()->{
int stamp = atomicInteger.getStamp();
System.out.println("a1===>" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
atomicInteger.compareAndSet(1, 2, atomicInteger.getStamp(), atomicInteger.getStamp()+1);
System.out.println("a2===>" + atomicInteger.getStamp());
atomicInteger.compareAndSet(2, 1, atomicInteger.getStamp(), atomicInteger.getStamp()+1);
System.out.println("a3===>" + atomicInteger.getStamp());
},"A").start();
new Thread(()->{
int stamp = atomicInteger.getStamp();
System.out.println("b1===>" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
atomicInteger.compareAndSet(1, 6, atomicInteger.getStamp(), atomicInteger.getStamp()+1);
System.out.println("b1===>" + atomicInteger.getStamp());
},"B").start();
}
}
2. 可能会消耗较高的CPU
看起来CAS比锁的效率高,从阻塞机制变成了非阻塞机制,减少了线程之间等待的时间。每个方法不能绝对的比另一个好,在线程之间竞争程度大的时候,如果使用CAS,每次都有很多的线程在竞争,也就是说CAS机制不能更新成功。这种情况下CAS机制会一直重试,这样就会比较耗费CPU。因此可以看出,如果线程之间竞争程度小,使用CAS是一个很好的选择;但是如果竞争很大,使用锁可能是个更好的选择。在并发量非常高的环境中,如果仍然想通过原子类来更新的话,可以使用AtomicLong的替代类:LongAdder。
3. 不能保证代码块的原子性
Java中的CAS机制只能保证共享变量操作的原子性,而不能保证代码块的原子性。
Java提供的CAS操作类–Unsafe类
从Java5开始引入了对CAS机制的底层的支持,在这之前需要开发人员编写相关的代码才可以实现CAS。在原子变量类Atomic**中(例如AtomicInteger、AtomicLong)可以看到CAS操作的代码,在这里的代码都是调用了底层(核心代码调用native修饰的方法)的实现方法。在AtomicInteger源码中可以看getAndSet方法和compareAndSet方法之间的关系,compareAndSet方法调用了底层的实现,该方法可以实现与一个volatile变量的读取和写入相同的效果。在前面说到了volatile不支持例如i++这样的复合操作,在Atomic*中提供了实现该操作的方法。JVM对CAS的支持通过这些原子类(Atomic)暴露出来,供我们使用。
参考链接:https://www.bilibili.com/video/BV1iJ411d7jS/?spm_id_from=333.999.0.0