一 线程基础知识复习
1.四大口诀
- 高内聚低耦合前提下,封装思想,线程操作资源类
- 判断,干活,通知
- 防止虚假唤醒,wait方法要注意使用while判断
- 注意标志位flag,可能是volatile
2.为什么多线程极其重要?
1.硬件方面
摩尔定律失效:它是由英特尔创始人之一Gordon Moore(戈登·摩尔)提出来的。其内容为: 当价格不变时,集成电路上可容纳的元器件的数目约每隔18-24个月便会增加一倍,性能也将提升一倍。 换言之,每一美元所能买到的电脑性能,将每隔18-24个月翻一倍以上。这一定律揭示了信息技术进步的速度。
可是从2003年开始CPU主频已经不再翻倍,而是采用多核而不是更快的主频。
2.软件方面
高并发系统,异步+回调等生产需求。
3.java多线程相关概念
1.进程
是程序的一次执行,是系统进行资源分配和调度的独立单位,每一个进程都有它自己的内存空间和系统资源
2.线程
在同一个进程内又可以执行多个任务,而这每一个任务我们就可以看做是一个线程
3.管程
Monitor(监视器),也就是我们平时所说的锁。
4.用户线程和守护线程
Java线程分为用户线程和守护线程,线程的daemon属性为true表示是守护线程,false表示是用户线程
1.守护线程
是一种特殊的线程,在后台默默完成一些系统性服务,如垃圾回收线程
2.用户线程
是系统的工作线程,它会完成这个程序需要完成的业务操作。
# 当程序中所有用户线程执行完毕后,不管守护线程是否结束,系统都会自动退出。
# 设置守护线程,需要在start()方法之前进行。
# main线程是用户线程。Thread线程是守护线程。
二 CompletableFuture
1.Future和Callable接口
-
Future接口定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等。
-
Callable接口中定义了需要有返回的任务需要实现的方法。
2.FutureTask
package com.zzyy.study.test;
import java.util.concurrent.*;
/**
* @auther zzyy
* @create 2020-06-14 17:02
*/
public class CompletableFutureDemo{
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException
{
FutureTask<String> futureTask = new FutureTask<>(() -> {
System.out.println("-----come in FutureTask");
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
return ""+ThreadLocalRandom.current().nextInt(100);
});
Thread t1 = new Thread(futureTask,"t1");
t1.start();
//3秒钟后才出来结果,还没有计算你提前来拿(只要一调用get方法,对于结果就是不见不散,会导致阻塞)
//System.out.println(Thread.currentThread().getName()+"\t"+futureTask.get());
//3秒钟后才出来结果,我只想等待1秒钟,过时不候
System.out.println(Thread.currentThread().getName()+"\t"+futureTask.get(1L,TimeUnit.SECONDS));
System.out.println(Thread.currentThread().getName()+"\t"+" run... here");
}
}
get()方法阻塞。一旦调用,都会阻塞,直到获取该线程执行结果才会往后继续执行。
package com.zzyy.study.test;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
/**
* @auther zzyy
* @create 2020-06-16 20:16
*/
public class CompletableFutureDemo2
{
public static void main(String[] args) throws ExecutionException, InterruptedException
{
FutureTask<String> futureTask = new FutureTask<>(() -> {
System.out.println("-----come in FutureTask");
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
return ""+ThreadLocalRandom.current().nextInt(100);
});
new Thread(futureTask,"t1").start();
System.out.println(Thread.currentThread().getName()+"\t"+"线程完成任务");
/**
* 用于阻塞式获取结果,如果想要异步获取结果,通常都会以轮询的方式去获取结果
*/
while(true)
{
if (futureTask.isDone())
{
System.out.println(futureTask.get());
break;
}
}
}
}
isDone()方法会进行轮询。
3.想要的改进
- Future完成是,会有回调通知
- 将多个异步计算合并成一个。
- 等待Future集合中所有任务都完成。
3.对FatureTask改进
CompletableFuture 类架构
接口CompletionStage:代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段。
类CompletableFuture:对Future进行扩展,简化异步计算
四个核心静态方法
- runAsync (无返回值)
-
public static CompletableFuture runAsync(Runnable runnable)
-
public static CompletableFuture runAsync(Runnable runnable, Executor executor)
-
- supplyAsync (有返回值)
-
public static CompletableFuture supplyAsync(Supplier supplier)
-
public static CompletableFuture supplyAsync(Supplier supplier,Executor executor)
-
上述Executor executor参数说明:没有指定Executor的方法,直接使用默认的ForkJoinPool.commonPool()作为它的线程池执行异步代码。
如果指定线程池,则使用我们自定义的或者特别指定的线程池执行异步代码
减少阻塞和轮询
package com.atguigu.juc.senior.inner.completablefuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
/**
* @auther zzyy
* @create 2020-06-23 12:59
*/
public class cfuture4
{
public static void main(String[] args) throws Exception
{
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "-----come in");
int result = ThreadLocalRandom.current().nextInt(10);
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("-----计算结束耗时1秒钟,result: "+result);
if(result > 6)
{
int age = 10/0;
}
return result;
}).whenComplete((v,e) ->{
if(e == null)
{
System.out.println("-----result: "+v);
}
}).exceptionally(e -> {
System.out.println("-----exception: "+e.getCause()+"\t"+e.getMessage());
return -44;
});
//主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:暂停3秒钟线程
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
4.案例:电商比价需求
get和join方法对比:都是获取结果的方法。唯一不同时get会抛出异常,join不会抛出异常。
package com.zzyy.study.day628;
import lombok.Getter;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @auther zzyy
* @create 2020-06-28 10:07
*/
public class T1
{
static List<NetMall> list = Arrays.asList(
new NetMall("jd"),
new NetMall("tmall"),
new NetMall("pdd"),
new NetMall("mi")
);
public static List<String> findPriceSync(List<NetMall> list,String productName)
{
return list.stream().map(mall -> String.format(productName+" %s price is %.2f",mall.getNetMallName(),mall.getPriceByName(productName))).collect(Collectors.toList());
}
public static List<String> findPriceASync(List<NetMall> list,String productName)
{
return list.stream().map(mall -> CompletableFuture.supplyAsync(() -> String.format(productName + " %s price is %.2f", mall.getNetMallName(), mall.getPriceByName(productName)))).collect(Collectors.toList()).stream().map(CompletableFuture::join).collect(Collectors.toList());
}
public static void main(String[] args)
{
long startTime = System.currentTimeMillis();
List<String> list1 = findPriceSync(list, "thinking in java");
for (String element : list1) {
System.out.println(element);
}
long endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒");
long startTime2 = System.currentTimeMillis();
List<String> list2 = findPriceASync(list, "thinking in java");
for (String element : list2) {
System.out.println(element);
}
long endTime2 = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime2 - startTime2) +" 毫秒");
}
}
class NetMall
{
@Getter
private String netMallName;
public NetMall(String netMallName)
{
this.netMallName = netMallName;
}
public double getPriceByName(String productName)
{
return calcPrice(productName);
}
private double calcPrice(String productName)
{
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
return ThreadLocalRandom.current().nextDouble() + productName.charAt(0);
}
}
5. CompletableFuture常用方法
1.获得结果和触发计算
public T get() #不见不散
public T get(long timeout, TimeUnit unit) # 过时不候
public T getNow(T valueIfAbsent) # 获取结果,没有计算完成就给一个代替值
public T join() #不见不散
public boolean complete(T value) # 判断是否计算完成,完成获取结果,没有就返回代替值
2.对计算结果进行处理
thenApply # 计算结果存在依赖关系,这两个线程串行化,出现异常会停下
handle # 有异常也可以继续往下走
总结:whenComplete和whenCompleteAsync区别
whenComplete:是执行当前任务的线程执行继续执行whenComplete的任务。
whenCompleteAsync:是执行把whenCompleteAsync这个任务继续提交给线程池来进行执行。
3.对计算结果进行消费
thenRun # A执行完B执行,B不需要A结果
thenAccept # A执行完B执行,B需要A结果,B无返回值
thenApply # A执行完B执行,B需要A结果,B有返回值
4.对计算速度进行选用
谁快用谁 applyToEither
package com.zzyy.study.test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* @auther zzyy
* @create 2020-06-16 20:16
*/
public class CompletableFutureDemo2
{
public static void main(String[] args) throws ExecutionException, InterruptedException
{
CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "---come in ");
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
return 10;
});
CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "---come in ");
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
return 20;
});
CompletableFuture<Integer> thenCombineResult = completableFuture1.applyToEither(completableFuture2,f -> {
System.out.println(Thread.currentThread().getName() + "\t" + "---come in ");
return f + 1;
});
System.out.println(Thread.currentThread().getName() + "\t" + thenCombineResult.get());
}
}
5.对计算结果进行合并
thenCombine
package com.zzyy.study.test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* @auther zzyy
* @create 2020-06-16 20:16
*/
public class CompletableFutureDemo2
{
public static void main(String[] args) throws ExecutionException, InterruptedException
{
CompletableFuture<Integer> thenCombineResult = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "---come in 1");
return 10;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "---come in 2");
return 20;
}), (x,y) -> {
System.out.println(Thread.currentThread().getName() + "\t" + "---come in 3");
return x + y;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "---come in 4");
return 30;
}),(a,b) -> {
System.out.println(Thread.currentThread().getName() + "\t" + "---come in 5");
return a + b;
});
System.out.println("-----主线程结束,END");
System.out.println(thenCombineResult.get());
// 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
三 锁
1. 8锁案例
```
#对象锁和类锁
* 1-2
* * 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,
* * 其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一的一个线程去访问这些synchronized方法
* * 锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法
*
* 3-4
* * 加个普通方法后发现和同步锁无关
* * 换成两个对象后,不是同一把锁了,情况立刻变化。
*
* 5-6 都换成静态同步方法后,情况又变化
* 三种 synchronized 锁的内容有一些差别:
* 对于普通同步方法,锁的是当前实例对象,通常指this,具体的一部部手机,所有的普通同步方法用的都是同一把锁——实例对象本身,
* 对于静态同步方法,锁的是当前类的Class对象,如Phone.class唯一的一个模板
* 对于同步方法块,锁的是 synchronized 括号内的对象
*
* 7-8
* 当一个线程试图访问同步代码时它首先必须得到锁,退出或抛出异常时必须释放锁。
* *
* * 所有的普通同步方法用的都是同一把锁——实例对象本身,就是new出来的具体实例对象本身,本类this
* * 也就是说如果一个实例对象的普通同步方法获取锁后,该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能获取锁。
* *
* * 所有的静态同步方法用的也是同一把锁——类对象本身,就是我们说过的唯一模板Class
* * 具体实例对象this和唯一模板Class,这两把锁是两个不同的对象,所以静态同步方法与普通同步方法之间是不会有竞态条件的
* * 但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁。
2.乐观锁和悲观锁
悲观锁:认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。
乐观锁:乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。
乐观锁的两种实现方式:版本号机制和CAS(比较并交换)
3.公平锁和非公平锁
按序排队公平锁,就是判断同步队列是否还有先驱节点的存在,如果没有先驱节点才能获取锁;
先占先得非公平锁,是不管这个事的,只要能抢获到同步状态就可以
4.可重入锁(递归锁)
是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞
可重入锁种类:
隐式锁:synchronized
显示锁:lock
5.死锁
死锁原因:
- 系统资源不足
- 进程推进顺序不当
- 资源分配不当
死锁代码演示:
package com.atguigu.juc.senior.prepare;
import java.util.concurrent.TimeUnit;
/**
* @auther zzyy
* @create 2020-05-14 10:56
*/
public class DeadLockDemo
{
public static void main(String[] args)
{
final Object objectLockA = new Object();
final Object objectLockB = new Object();
new Thread(() -> {
synchronized (objectLockA)
{
System.out.println(Thread.currentThread().getName()+"\t"+"自己持有A,希望获得B");
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (objectLockB)
{
System.out.println(Thread.currentThread().getName()+"\t"+"A-------已经获得B");
}
}
},"A").start();
new Thread(() -> {
synchronized (objectLockB)
{
System.out.println(Thread.currentThread().getName()+"\t"+"自己持有B,希望获得A");
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (objectLockA)
{
System.out.println(Thread.currentThread().getName()+"\t"+"B-------已经获得A");
}
}
},"B").start();
}
}
如何排查死锁:
- 命令:
jps -l
jstack 进程编号
- 图形化
jconsole
6.读锁(共享锁)/写锁(独占锁)
7.自旋锁
四 LockSupport与线程中断
1.线程中断机制
什么是中断?
首先
一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。
所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。
其次
在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。
因此,Java提供了一种用于停止线程的机制——中断。
中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true;
中断相关api
public void interrupt()
# 实例方法interrupt()仅仅是设置线程的中断状态为true,不会停止线程
public static boolean interrupted()
# 判断线程是否被中断,并清除当前中断状态
# 这个方法做了两件事:
# 1 返回当前线程的中断状态
# 2 将当前线程的中断状态设为false
public boolean isInterrupted()
# 判断当前线程是否被中断(通过检查中断标志位)
如何使用中断标识停止线程?
# 在需要中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理业务逻辑。
方法:3个
- 通过一个volatile变量实现
package com.atguigu.juc.senior.test;
import java.util.concurrent.TimeUnit;
/**
* @auther zzyy
* @create 2020-05-12 14:19
*/
public class InterruptDemo
{
private static volatile boolean isStop = false;
public static void main(String[] args)
{
new Thread(() -> {
while(true)
{
if(isStop)
{
System.out.println(Thread.currentThread().getName()+"线程------isStop = true,自己退出了");
break;
}
System.out.println("-------hello interrupt");
}
},"t1").start();
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
isStop = true;
}
}
- 通过AtomicBoolean
package com.zzyy.study.test;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @auther zzyy
* @create 2020-05-26 23:24
*/
public class StopThreadDemo
{
private final static AtomicBoolean atomicBoolean = new AtomicBoolean(true);
public static void main(String[] args)
{
Thread t1 = new Thread(() -> {
while(atomicBoolean.get())
{
try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("-----hello");
}
}, "t1");
t1.start();
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
atomicBoolean.set(false);
}
}
- 通过Thread类自带的中断api方法实现
package com.atguigu.itdachang;
import java.util.concurrent.TimeUnit;
/**
* @auther zzyy
* @create 2020-07-10 17:33
*/
public class InterruptDemo
{
public static void main(String[] args)
{
Thread t1 = new Thread(() -> {
while(true)
{
if(Thread.currentThread().isInterrupted())
{
System.out.println("-----t1 线程被中断了,break,程序结束");
break;
}
System.out.println("-----hello");
}
}, "t1");
t1.start();
System.out.println("**************"+t1.isInterrupted());
//暂停5毫秒
try { TimeUnit.MILLISECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
t1.interrupt();
System.out.println("**************"+t1.isInterrupted());
}
}
当前线程的中断标识为true,是不是就立刻停止?
# 当线程带await或者sleep,调用 interrupt()方法去修改线程标志位,会出现报错,同时会清除线程的标志位,即把线程标志位置为 false。
小结:中断只是一种协同机制,修改中断标识位仅此而已,不是立刻stop打断。
2.LockSupport
LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程
是什么
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit),
permit只有两个值1和零,默认是零。
可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。
代码:
package com.atguigu.juc.prepare;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
* @auther zzyy
* @create 2020-04-13 20:30
*/
public class LockSupportDemo3
{
public static void main(String[] args)
{
//正常使用+不需要锁块
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName()+" "+"1111111111111");
LockSupport.park();
System.out.println(Thread.currentThread().getName()+" "+"2222222222222------end被唤醒");
},"t1");
t1.start();
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName()+" -----LockSupport.unparrk() invoked over");
}
}
同时LockSupport的park和unpark方法,没有Object和Condition的两个限制。
3.线程等待唤醒机制
3种让线程等待和唤醒的方法
方式1:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程。
方式2:使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
方式3: LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
方式1:
package com.atguigu.juc.prepare;
import java.util.concurrent.TimeUnit;
public class LockSupportDemo
{
public static void main(String[] args)//main方法,主线程一切程序入口
{
Object objectLock = new Object(); //同一把锁,类似资源类
new Thread(() -> {
synchronized (objectLock) {
try {
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"\t"+"被唤醒了");
},"t1").start();
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
synchronized (objectLock) {
objectLock.notify();
}
//objectLock.notify();
/*synchronized (objectLock) {
try {
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
},"t2").start();
}
}
方式2:
package com.atguigu.juc.prepare;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @auther zzyy
* @create 2020-04-13 17:55
*/
public class LockSupportDemo2
{
public static void main(String[] args)
{
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
try
{
System.out.println(Thread.currentThread().getName()+"\t"+"start");
condition.await();
System.out.println(Thread.currentThread().getName()+"\t"+"被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
},"t1").start();
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
lock.lock();
try
{
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println(Thread.currentThread().getName()+"\t"+"通知了");
},"t2").start();
}
}
Object和Condition条件限制:
线程先要获得并持有锁,必须在锁块(synchronized或lock)中
必须要先等待后唤醒,线程才能够被唤醒
五 Java内存模型之JMM
1.计算机硬件存储体系
计算机存储结构,从本地磁盘到主存到CPU缓存,也就是从硬盘到内存,到CPU。
一般对应的程序的操作就是从数据库查数据到内存然后到CPU进行计算
需要知道JMM原因?
因为有这么多级的缓存(cpu和物理主内存的速度不一致的),
CPU的运行并不是直接操作内存而是先把内存里边的数据读到缓存,而内存的读和写操作的时候就会造成不一致的问题
Java虚拟机规范中试图定义一种Java内存模型(java Memory Model,简称JMM) 来屏蔽掉各种硬件和操作系统的内存访问差异,
以实现让Java程序在各种平台下都能达到一致的内存访问效果。
2.Java内存模型 Java Memory Model
是什么?
JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念并不真实存在它仅仅描述的是一组约定或规范,通过这组规范定义了程序中(尤其是多线程)各个变量的读写访问方式并决定一个线程对共享变量的写入何时以及如何变成对另一个线程可见,关键技术点都是围绕多线程的原子性、可见性和有序性展开的。
原则:
JMM的关键技术点都是围绕多线程的原子性、可见性和有序性展开的
3。JMM规范下,三大特性
可见性:
是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道该变更。
# JMM规定了所有变量都是存储在主内存中,多线程对内存变量的操作都是复制一份到本地内存进行操作的,不可直接操作主内存。
原子性:
指一个操作是不可中断的,即多线程环境下,操作不能被其他线程干扰。
有序性:
对于一个线程的执行代码而言,我们总是习惯性认为代码的执行总是从上到下,有序执行。
但为了提高性能,编译器和处理器通常会对指令序列进行重新排序。
4.JMM规范下,多线程对变量的读写过程
读取过程:
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到的线程自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。
小总结:
我们定义的所有共享变量都储存在物理主内存中
每个线程都有自已独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)
线程对共享变量所有的操作都必须先在线程自己的工作内存中进行后写回主内存,不能直接从主内存中读写(不能越级)
不同线程之间也无法直接访问其他线程的工作内存中的变量,线程间变量值的传递需要通过主内存来进行(同级不能相互访问)
5.JMM规范下,多线程先行发生原则之happens-before
先行发生原则说明:
如果Java内存模型中所有的有序性都仅靠volatile和synchronized来完成,那么有很多操作都将会变得非常啰嗦,
但是我们在编写Java并发代码的时候并没有察觉到这一点。
我们没有时时、处处、次次,添加volatile和synchronized来完成程序,这是因为Java语言中JMM原则下
有一个“先行发生”(Happens-Before)的原则限制和规矩
这个原则非常重要:
它是判断数据是否存在竞争,线程是否安全的非常有用的手段。依赖这个原则,我们可以通过几条简单规则一揽子解决并发环境下两个操
作之间是否可能存在冲突的所有问题,而不需要陷入Java内存模型苦涩难懂的底层编译原理之中。
happens-before之8条
1.次序规则:
前一个操作的结果可以被后续的操作获取。
2.锁定规则:
一个unLock操作先行发生于后面((这里的“后面”是指时间上的先后))对同一个锁的lock操作;
3.volatile变量规则:
对一个volatile变量的写操作先行发生于后面对这个变量的读操作。
前面的写对后面的读是可见的,这里的“后面”同样是指时间上的先后。
4.传递规则:
如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
5.线程启动规则:
Thread对象的start()方法先行发生于此线程的每一个动作
6.线程中断规则:
对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
7.线程终止规则:
线程中的所有操作都先行发生于对此线程的终止检
8.线程终结规则:
一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始
六 volatile与java内存模型
JMM是理念,volatile是落地实现
1.被volatile修饰的变量2大特点
可见性
有序性
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。
当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量
2.内存屏障
是什么?
内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序
四大内存屏障指令
| 屏障类型 | 指令示例 | 说明 |
|---|---|---|
| LoadLoad | Load1; LoadLoad; Load2 | 保证load1的读取操作在load2及后续读取操作之前执行 |
| StoreStore | Store1; StoreStore; Store2 | 在store2及其后的写操作执行前,保证store1的写操作已刷新到主内存 |
| LoadStore | Load1; LoadStore; Store2 | 在stroe2及其后的写操作执行前,保证load1的读操作已读取结束 |
| storeLoad | Store1; StoreLoad; Load2 | 保证store1的写操作已刷新到主内存之后,load2及其后的读操作才能执行 |
JMM就将内存屏障插入策略分为4种
写:
1.在每个volatile 写操作的前面插入一个StoreStore 屏障
2.在每个volatile 写操作的后面插入一个StoreLoad屏障
读:
3.在每个volatile读操作的后面插入一个LoadLoad屏障
4.在每个volatile读操作的后面插入一个LoadStore 屏障
3.volatile特性
保证可见性
# 保证不同线程对这个变量进行操作时的可见性,即变量一旦改变所有线程立即可见
package com.zzyy.study.juc;
import java.util.concurrent.TimeUnit;
/**
* @auther zzyy
* @create 2020-06-30 11:29
*/
public class VolatileSeeDemo
{
static boolean flag = true; //不加volatile,没有可见性
//static volatile boolean flag = true; //加了volatile,保证可见性
public static void main(String[] args)
{
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t come in");
while (flag)
{
}
System.out.println(Thread.currentThread().getName()+"\t flag被修改为false,退出.....");
},"t1").start();
//暂停2秒钟后让main线程修改flag值
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
flag = false;
System.out.println("main线程修改完成");
}
}
没有原子性
# volatile变量的复合操作(如i++)不具有原子性
package com.zzyy.study.juc;
import java.util.concurrent.TimeUnit;
class MyNumber
{
volatile int number = 0;
public void addPlusPlus()
{
number++;
}
}
public class VolatileNoAtomicDemo
{
public static void main(String[] args) throws InterruptedException
{
MyNumber myNumber = new MyNumber();
for (int i = 1; i <=10; i++) {
new Thread(() -> {
for (int j = 1; j <= 1000; j++) {
myNumber.addPlusPlus();
}
},String.valueOf(i)).start();
}
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "\t" + myNumber.number);
}
}
指令禁重排
通过内存屏障实现指令禁重排
4.volatile变量读写过程
Java内存模型中定义的8种工作内存与主内存之间的原子操作
read(读取)→load(加载)→use(使用)→assign(赋值)→store(存储)→write(写入)→lock(锁定)→unlock(解锁)
read: 作用于主内存,将变量的值从主内存传输到工作内存,主内存到工作内存
load: 作用于工作内存,将read从主内存传输的变量值放入工作内存变量副本中,即数据加载
use: 作用于工作内存,将工作内存变量副本的值传递给执行引擎,每当JVM遇到需要该变量的字节码指令时会执行该操作
assign: 作用于工作内存,将从执行引擎接收到的值赋值给工作内存变量,每当JVM遇到一个给变量赋值字节码指令时会执行该操作
store: 作用于工作内存,将赋值完毕的工作变量的值写回给主内存
write: 作用于主内存,将store传输过来的变量值赋值给主内存中的变量
由于上述只能保证单条指令的原子性,针对多条指令的组合性原子保证,没有大面积加锁,所以,JVM提供了另外两个原子指令:
lock: 作用于主内存,将一个变量标记为一个线程独占的状态,只是写时候加锁,就只是锁了写变量的过程。
unlock: 作用于主内存,把一个处于锁定状态的变量释放,然后才能被其他线程占用
5.如何正确使用volatile
单一赋值可以,but含复合运算赋值不可以(i++之类)
状态标志,判断业务是否结束
开销较低的读,写锁策略
七 CAS
1,没有CAS之前
多线程环境主要通过加锁或者使用原子类保证线程安全。
package com.atguigu.juc.prepare;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @auther zzyy
* @create 2020-04-15 10:41
*/
public class T3
{
volatile int number = 0;
//读取
public int getNumber()
{
return number;
}
//写入加锁保证原子性
public synchronized void setNumber()
{
number++;
}
}
package com.atguigu.juc.prepare;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @auther zzyy
* @create 2020-04-15 10:41
*/
public class T3
{
volatile int number = 0;
//读取
public int getNumber()
{
return number;
}
//写入加锁保证原子性
public synchronized void setNumber()
{
number++;
}
//=================================
AtomicInteger atomicInteger = new AtomicInteger();
public int getAtomicInteger()
{
return atomicInteger.get();
}
public void setAtomicInteger()
{
atomicInteger.getAndIncrement();
}
}
2,是什么
CAS
compare and swap的缩写,中文翻译成比较并交换,实现并发算法时常用到的一种技术。它包含三个操作数——内存位置、预期原值及更新值。
执行CAS操作的时候,将内存位置的值与预期原值比较:
如果相匹配,那么处理器会自动将该位置值更新为新值,
如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功。
硬件级别保证
CAS是JDK提供的非阻塞原子性操作,它通过硬件保证了比较-更新的原子性。
CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。
Demo
package com.atguigu.juc.prepare;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @auther zzyy
* @create 2020-04-15 7:51
*/
public class CASDemo
{
public static void main(String[] args) throws InterruptedException
{
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5, 2020)+"\t"+atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5, 1024)+"\t"+atomicInteger.get());
}
}
源码分析
上面三个方法都是类似的,主要对4个参数做一下说明。
var1:表示要操作的对象
var2:表示要操作对象中属性地址的偏移量
var4:表示需要修改数据的期望的值
var5/var6:表示需要修改为的新值
3,CAS底层原理
CAS主要通过Unsafe类实现的。
是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。
4,原子引用
package com.atguigu.Interview.study.thread;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import java.util.concurrent.atomic.AtomicReference;
@Getter
@ToString
@AllArgsConstructor
class User
{
String userName;
int age;
}
/**
* @auther zzyy
* @create 2018-12-31 17:22
*/
public class AtomicReferenceDemo
{
public static void main(String[] args)
{
User z3 = new User("z3",24);
User li4 = new User("li4",26);
AtomicReference<User> atomicReferenceUser = new AtomicReference<>();
atomicReferenceUser.set(z3);
System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
}
}
5,自旋锁
自旋锁(spinlock)
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,
当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU
Demo
package com.atguigu.Interview.study.thread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* @auther zzyy
* @create 2018-12-28 17:57
* 题目:实现一个自旋锁
* 自旋锁好处:循环比较获取没有类似wait的阻塞。
*
* 通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒钟,B随后进来后发现
* 当前有线程持有锁,不是null,所以只能通过自旋等待,直到A释放锁后B随后抢到。
*/
public class SpinLockDemo
{
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock()
{
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"\t come in");
while(!atomicReference.compareAndSet(null,thread))
{
}
}
public void myUnLock()
{
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"\t myUnLock over");
}
public static void main(String[] args)
{
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.myLock();
try { TimeUnit.SECONDS.sleep( 5 ); } catch (InterruptedException e) { e.printStackTrace(); }
spinLockDemo.myUnLock();
},"A").start();
//暂停一会儿线程,保证A线程先于B线程启动并完成
try { TimeUnit.SECONDS.sleep( 1 ); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
spinLockDemo.myLock();
spinLockDemo.myUnLock();
},"B").start();
}
}
6,CAS缺点
循环时间长开销很大。
引起ABA问题。
CAS会导致“ABA问题”。
CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B,
然后线程two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功。
尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。
ABADemo
package com.atguigu.Interview.study.thread;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @auther zzyy
* @create 2018-11-20 17:14
*/
public class ABADemo
{
static AtomicInteger atomicInteger = new AtomicInteger(100);
static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);
public static void main(String[] args)
{
new Thread(() -> {
atomicInteger.compareAndSet(100,101);
atomicInteger.compareAndSet(101,100);
},"t1").start();
new Thread(() -> {
//暂停一会儿线程
try { Thread.sleep( 500 ); } catch (InterruptedException e) { e.printStackTrace(); }; System.out.println(atomicInteger.compareAndSet(100, 2019)+"\t"+atomicInteger.get());
},"t2").start();
//暂停一会儿线程,main彻底等待上面的ABA出现演示完成。
try { Thread.sleep( 2000 ); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("============以下是ABA问题的解决=============================");
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 首次版本号:"+stamp);//1
//暂停一会儿线程,
try { Thread.sleep( 1000 ); } catch (InterruptedException e) { e.printStackTrace(); }
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 2次版本号:"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 3次版本号:"+atomicStampedReference.getStamp());
},"t3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 首次版本号:"+stamp);//1
//暂停一会儿线程,获得初始值100和初始版本号1,故意暂停3秒钟让t3线程完成一次ABA操作产生问题
try { Thread.sleep( 3000 ); } catch (InterruptedException e) { e.printStackTrace(); }
boolean result = atomicStampedReference.compareAndSet(100,2019,stamp,stamp+1);
System.out.println(Thread.currentThread().getName()+"\t"+result+"\t"+atomicStampedReference.getReference());
},"t4").start();
}
}
使用AtomicStampedReference可以检测的ABA问题。
八 原子操作类
基本类型原子类
AtomicInteger
AtomicBoolean
AtomicLong
常用API简介:
Demo:
package com.atguigu.juc.senior.test2;
import lombok.Getter;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
class MyNumber
{
@Getter
private AtomicInteger atomicInteger = new AtomicInteger();
public void addPlusPlus()
{
atomicInteger.incrementAndGet();
}
}
/**
* @auther zzyy
* @create 2020-07-03 17:16
*/
public class AtomicIntegerDemo
{
public static void main(String[] args) throws InterruptedException
{
MyNumber myNumber = new MyNumber();
CountDownLatch countDownLatch = new CountDownLatch(100);
for (int i = 1; i <=100; i++) {
new Thread(() -> {
try
{
for (int j = 1; j <=5000; j++)
{
myNumber.addPlusPlus();
}
}finally {
countDownLatch.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(myNumber.getAtomicInteger().get());
}
}
数组类型原子类
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray Demo:
package com.atguigu.juc.prepare;
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
* @auther zzyy
* @create 2020-04-16 14:59
*/
public class AtomicIntegerArrayDemo
{
public static void main(String[] args)
{
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);
//AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5);
//AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1,2,3,4,5});
for (int i = 0; i <atomicIntegerArray.length(); i++) {
System.out.println(atomicIntegerArray.get(i));
}
System.out.println();
System.out.println();
System.out.println();
int tmpInt = 0;
tmpInt = atomicIntegerArray.getAndSet(0,1122);
System.out.println(tmpInt+"\t"+atomicIntegerArray.get(0));
atomicIntegerArray.getAndIncrement(1);
atomicIntegerArray.getAndIncrement(1);
tmpInt = atomicIntegerArray.getAndIncrement(1);
System.out.println(tmpInt+"\t"+atomicIntegerArray.get(1));
}
}
引用类型原子类
AtomicReference
package com.atguigu.Interview.study.thread;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import java.util.concurrent.atomic.AtomicReference;
@Getter
@ToString
@AllArgsConstructor
class User
{
String userName;
int age;
}
/**
* @auther zzyy
* @create 2018-12-31 17:22
*/
public class AtomicReferenceDemo
{
public static void main(String[] args)
{
User z3 = new User("z3",24);
User li4 = new User("li4",26);
AtomicReference<User> atomicReferenceUser = new AtomicReference<>();
atomicReferenceUser.set(z3);
System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
}
}
AtomicStampedReference
# 携带版本号的引用类型原子类,可以解决ABA问题。
# 解决修改过几次
package com.atguigu.juc.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @auther zzyy
* @create 2021-03-18 15:34
*/
public class ABADemo
{
static AtomicInteger atomicInteger = new AtomicInteger(100);
static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);
public static void main(String[] args)
{
abaProblem();
abaResolve();
}
public static void abaResolve()
{
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println("t3 ----第1次stamp "+stamp);
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);
System.out.println("t3 ----第2次stamp "+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println("t3 ----第3次stamp "+atomicStampedReference.getStamp());
},"t3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println("t4 ----第1次stamp "+stamp);
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
boolean result = atomicStampedReference.compareAndSet(100, 20210308, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName()+"\t"+result+"\t"+atomicStampedReference.getReference());
},"t4").start();
}
public static void abaProblem()
{
new Thread(() -> {
atomicInteger.compareAndSet(100,101);
atomicInteger.compareAndSet(101,100);
},"t1").start();
try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
atomicInteger.compareAndSet(100,20210308);
System.out.println(atomicInteger.get());
},"t2").start();
}
}
AtomicMarkableReference
# 原子更新带有标记位的引用类型对象
# 解决是否修改过
package com.atguigu.juc.senior.inner.atomic;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @auther zzyy
* @create 2020-05-23 10:56
*/
public class ABADemo
{
static AtomicInteger atomicInteger = new AtomicInteger(100);
static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1);
static AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<>(100,false);
public static void main(String[] args)
{
new Thread(() -> {
atomicInteger.compareAndSet(100,101);
atomicInteger.compareAndSet(101,100);
System.out.println(Thread.currentThread().getName()+"\t"+"update ok");
},"t1").start();
new Thread(() -> {
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
atomicInteger.compareAndSet(100,2020);
},"t2").start();
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(atomicInteger.get());
System.out.println();
System.out.println();
System.out.println();
System.out.println("============以下是ABA问题的解决,让我们知道引用变量中途被更改了几次=========================");
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t 1次版本号"+stampedReference.getStamp());
//故意暂停200毫秒,让后面的t4线程拿到和t3一样的版本号
try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 2次版本号"+stampedReference.getStamp());
stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 3次版本号"+stampedReference.getStamp());
},"t3").start();
new Thread(() -> {
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t =======1次版本号"+stamp);
//暂停2秒钟,让t3先完成ABA操作了,看看自己还能否修改
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
boolean b = stampedReference.compareAndSet(100, 2020, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName()+"\t=======2次版本号"+stampedReference.getStamp()+"\t"+stampedReference.getReference());
},"t4").start();
System.out.println();
System.out.println();
System.out.println();
System.out.println("============AtomicMarkableReference不关心引用变量更改过几次,只关心是否更改过======================");
new Thread(() -> {
boolean marked = markableReference.isMarked();
System.out.println(Thread.currentThread().getName()+"\t 1次版本号"+marked);
try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
markableReference.compareAndSet(100,101,marked,!marked);
System.out.println(Thread.currentThread().getName()+"\t 2次版本号"+markableReference.isMarked());
markableReference.compareAndSet(101,100,markableReference.isMarked(),!markableReference.isMarked());
System.out.println(Thread.currentThread().getName()+"\t 3次版本号"+markableReference.isMarked());
},"t5").start();
new Thread(() -> {
boolean marked = markableReference.isMarked();
System.out.println(Thread.currentThread().getName()+"\t 1次版本号"+marked);
//暂停几秒钟线程
try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
markableReference.compareAndSet(100,2020,marked,!marked);
System.out.println(Thread.currentThread().getName()+"\t"+markableReference.getReference()+"\t"+markableReference.isMarked());
},"t6").start();
}
}
对象的属性修改原子类
AtomicIntegerFieldUpdater:原子更新对象中int类型字段的值。
AtomicLongFieldUpdater:原子更新对象中Long类型字段的值。
AtomicReferenceFieldUpdater:原子更新引用类型字段的值。
使用目的:以一种线程安全的方式操作非线程安全对象内的某些字段。
使用要求:
更新的对象属性必须使用public volatile修饰符。
因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须 使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
原子操作增强类
DoubleAccumulator;DoubleAdder;LongAccumulator;LongAdder;
如果是jdk8,推荐使用LongAdder对象,比AtomicLong性能更好。
点赞计数器
LongAdder只能用来计算加法,且从零开始计算;LongAccumulator提供了自定义的函数操作。
package com.atguigu.juc.atomics;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;
/**
* @auther zzyy
* @create 2021-03-19 15:59
*/
public class LongAdderAPIDemo
{
public static void main(String[] args)
{
LongAdder longAdder = new LongAdder();
longAdder.increment();
longAdder.increment();
longAdder.increment();
System.out.println(longAdder.longValue());
LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x * y,2);
longAccumulator.accumulate(1);
longAccumulator.accumulate(2);
longAccumulator.accumulate(3);
System.out.println(longAccumulator.longValue());
}
}
LongAdder的性能:
package com.zzyy.study.day524;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;
class ClickNumberNet
{
int number = 0;
public synchronized void clickBySync()
{
number++;
}
AtomicLong atomicLong = new AtomicLong(0);
public void clickByAtomicLong()
{
atomicLong.incrementAndGet();
}
LongAdder longAdder = new LongAdder();
public void clickByLongAdder()
{
longAdder.increment();
}
LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x + y,0);
public void clickByLongAccumulator()
{
longAccumulator.accumulate(1);
}
}
/**
* @auther zzyy
* @create 2020-05-21 22:23
* 50个线程,每个线程100W次,总点赞数出来
*/
public class LongAdderDemo2
{
public static void main(String[] args) throws InterruptedException
{
ClickNumberNet clickNumberNet = new ClickNumberNet();
long startTime;
long endTime;
CountDownLatch countDownLatch = new CountDownLatch(50);
CountDownLatch countDownLatch2 = new CountDownLatch(50);
CountDownLatch countDownLatch3 = new CountDownLatch(50);
CountDownLatch countDownLatch4 = new CountDownLatch(50);
startTime = System.currentTimeMillis();
for (int i = 1; i <=50; i++) {
new Thread(() -> {
try
{
for (int j = 1; j <=100 * 10000; j++) {
clickNumberNet.clickBySync();
}
}finally {
countDownLatch.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickBySync result: "+clickNumberNet.number);
startTime = System.currentTimeMillis();
for (int i = 1; i <=50; i++) {
new Thread(() -> {
try
{
for (int j = 1; j <=100 * 10000; j++) {
clickNumberNet.clickByAtomicLong();
}
}finally {
countDownLatch2.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch2.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByAtomicLong result: "+clickNumberNet.atomicLong);
startTime = System.currentTimeMillis();
for (int i = 1; i <=50; i++) {
new Thread(() -> {
try
{
for (int j = 1; j <=100 * 10000; j++) {
clickNumberNet.clickByLongAdder();
}
}finally {
countDownLatch3.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch3.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByLongAdder result: "+clickNumberNet.longAdder.sum());
startTime = System.currentTimeMillis();
for (int i = 1; i <=50; i++) {
new Thread(() -> {
try
{
for (int j = 1; j <=100 * 10000; j++) {
clickNumberNet.clickByLongAccumulator();
}
}finally {
countDownLatch4.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch4.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByLongAccumulator result: "+clickNumberNet.longAccumulator.longValue());
}
}
LongAdder快的原因:
LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。
九 ThreadLocal
1,ThreadLocal简介
是什么
ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候(通过其get或set方法)都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户ID或事务ID)与线程关联起来。
能干什么
实现每一个线程都有自己专属的本地变量副本,主要解决了让每个线程绑定自己的值,通过使用get()和set()方法,获取默认值或将其值更改为当前线程所存的副本的值从而避免了线程安全问题。
API介绍
Demo
package com.atguigu.juc.tl;
class MovieTicket
{
int number = 50;
public synchronized void saleTicket()
{
if(number > 0)
{
System.out.println(Thread.currentThread().getName()+"\t"+"号售票员卖出第: "+(number--));
}else{
System.out.println("--------卖完了");
}
}
}
class House
{
ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
public void saleHouse()
{
Integer value = threadLocal.get();
value++;
threadLocal.set(value);
}
}
/**
* @auther zzyy
* @create 2021-03-23 15:03
* 1 三个售票员卖完50张票务,总量完成即可,吃大锅饭,售票员每个月固定月薪
*
* 2 分灶吃饭,各个销售自己动手,丰衣足食
*/
public class ThreadLocalDemo
{
public static void main(String[] args)
{
/*MovieTicket movieTicket = new MovieTicket();
for (int i = 1; i <=3; i++) {
new Thread(() -> {
for (int j = 0; j <20; j++) {
movieTicket.saleTicket();
try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
}
},String.valueOf(i)).start();
}*/
//===========================================
House house = new House();
new Thread(() -> {
try {
for (int i = 1; i <=3; i++) {
house.saleHouse();
}
System.out.println(Thread.currentThread().getName()+"\t"+"---"+house.threadLocal.get());
}finally {
house.threadLocal.remove();//如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题
}
},"t1").start();
new Thread(() -> {
try {
for (int i = 1; i <=2; i++) {
house.saleHouse();
}
System.out.println(Thread.currentThread().getName()+"\t"+"---"+house.threadLocal.get());
}finally {
house.threadLocal.remove();
}
},"t2").start();
new Thread(() -> {
try {
for (int i = 1; i <=5; i++) {
house.saleHouse();
}
System.out.println(Thread.currentThread().getName()+"\t"+"---"+house.threadLocal.get());
}finally {
house.threadLocal.remove();
}
},"t3").start();
System.out.println(Thread.currentThread().getName()+"\t"+"---"+house.threadLocal.get());
}
}
Demo总结:
因为每个Thread内有自己的实例副本且该副本只由当前线程自己使用既然其它Thread不可访问,那就不存在多线程间共享的问题。
统一设置初始值,但是每个线程对这个
值的修改都是各自线程互相独立的
2,阿里ThreadLocak规范
非线程安全的SimpleDateFormat
package com.atguigu.itdachang;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @auther zzyy
* @create 2020-07-17 16:42
*/
public class DateUtils
{
public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 模拟并发环境下使用SimpleDateFormat的parse方法将字符串转换成Date对象
* @param stringDate
* @return
* @throws Exception
*/
public static Date parseDate(String stringDate)throws Exception
{
return sdf.parse(stringDate);
}
public static void main(String[] args) throws Exception
{
for (int i = 1; i <=30; i++) {
new Thread(() -> {
try {
System.out.println(DateUtils.parseDate("2020-11-11 11:11:11"));
} catch (Exception e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
# 结论分析
SimpleDateFormat类内部有一个Calendar对象引用,它用来储存和这个SimpleDateFormat相关的日期信息,例如sdf.parse(dateStr),sdf.format(date) 诸如此类的方法参数传入的日期相关String,Date等等, 都是交由Calendar引用来储存的.这样就会导致一个问题如果你的SimpleDateFormat是个static的, 那么多个thread 之间就会共享这个SimpleDateFormat, 同时也是共享这个Calendar引用。
解决
法一:将SimpleDateFormat定义成局部变量
法二:加锁
法三:使用ThreadLocal
package com.atguigu.itdachang;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @auther zzyy
* @create 2020-07-17 16:42
*/
public class DateUtils
{
private static final ThreadLocal<SimpleDateFormat> sdf_threadLocal =
ThreadLocal.withInitial(()-> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
/**
* ThreadLocal可以确保每个线程都可以得到各自单独的一个SimpleDateFormat的对象,那么自然也就不存在竞争问题了。
* @param stringDate
* @return
* @throws Exception
*/
public static Date parseDateTL(String stringDate)throws Exception
{
return sdf_threadLocal.get().parse(stringDate);
}
public static void main(String[] args) throws Exception
{
for (int i = 1; i <=30; i++) {
new Thread(() -> {
try {
System.out.println(DateUtils.parseDateTL("2020-11-11 11:11:11"));
} catch (Exception e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
3,ThreadLocal源码分析
Thread,ThreadLocal,ThreadLocalMap关系
threadLocalMap实际上就是一个以threadLocal实例为key,任意对象为value的Entry对象。
当我们为threadLocal变量赋值,实际上就是以当前threadLocal实例为key,值为value的Entry往这个threadLocalMap中存放
4,ThreadLocal内存泄露问题
什么是内存泄漏?
不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。
原因
强引用
当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收。
软引用
当系统内存充足是,不会回收,当内存不足时,会被回收。
弱引用
对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。
虚引用
顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,
它不能单独使用也不能通过它访问对象,虚引用必须和引用队列 (ReferenceQueue)联合使用。
软引用和弱引用适用场景如应用需要读取大量本地图片。