JUC并发编程(三)
文章目录
十二. 四大函数式接口(必须掌握)
程序员必须掌握的几种操作:
1.lambda表达式
2.链式编程
3.函数式接口
4.sream流式计算
函数式接口:只有一个方法的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
我们把这样的接口统称为函数式接口,这样的优点在于:简化编程框架,在新版本的框架底层中大量应用.
1.Function函数式接口
有一个输入参数,一个输出参数
只要是函数式接口,就可以用lambda表达式简化
代码测试:
import java.util.function.Function;
//FunctionalInterface 函数型接口
public class Demo01 {
public static void main(String[] args) {
//写一个没用的接口,作用是:输出输入的值
/*Function 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("asdf"));
}
}
---------------结果---------------
asdf
2.Predicate断定型接口
import java.util.function.Predicate;
//段定型接口,有一个输入参数,返回值是布尔值
public class Demo02 {
public static void main(String[] args) {
/*Predicate 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(""));
}
}
---------------结果---------------
true
3. Consumer 消费型接口
//Consumer 消费型接口 只有一个输入,没有输出
import java.util.function.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("asdfghjkl");
}
}
---------------结果---------------
asdfghjkl
4. Supplier 供给型接口
//Supplier 供给型接口,没有参数,只有返回值
import java.util.function.Supplier;
public class Demo04 {
public static void main(String[] args) {
/*Supplier<String> supplier = new Supplier<String>() {
@Override
public String get() {
return "执行成功";
}
};*/
Supplier<String> supplier = ()->{return "执行成功";};
System.out.println(supplier.get());
}
}
---------------结果---------------
执行成功
十三. Stream流式计算
什么是 Strean流式计算
大数据:存储+计算
集合、 MySQL本质就是存储东西的;
计算都应该交给流来操作!
用一个实例来演示四个四大函数式接口
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
/*题要求:一分钟内完成此题月能用一行代码实现!
现在有5个用户!筛选
1、ID必须是偶数
2、年龄必须大于23岁
3、用户名转为大写字母
4、用户名字母倒着排序字
5、只输出一个用户!
*/
public class Demo01 {
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(5, "e", 25);
USER U6 = new USER(6, "f", 26);
//集合就是存储
List<USER> list = Arrays.asList(U1, U2, U3, U4, U5,U6);
//计算交给Stream流
list.stream()
.filter(u->{return u.getId() % 2 == 0;})
.filter(u->{return u.getAge() > 23;})
.map(u->{return u.getName().toUpperCase(Locale.ROOT);})
.sorted((u1,u2)->{return u2.compareTo(u1);})
.limit(1)
.forEach(System.out::println);
}
}
---------------结果---------------
F
public class USER {
private int id;
private String name;
private int age;
public USER(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public USER() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "USER{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
十四. ForkJoin 分支合并
什么是ForkJoin
并行执行一些任务,提高效率的,前提是大量数据
常用于大数据,在大数据中会学Map Reduce (把大任务,拆成小任务)
ForkJoin的特点:工作窃取
窃取别的线程的任务来执行,提高工作效率
这里面维护的都是双端队列
import java.util.concurrent.RecursiveTask;
//求和计算的任务
//如何使用ForkJoin
//1.ForkJoinPool 通过它来执行
//2.计算任务 ForkJoinPool.excute(ForkJoinTask task)
//3.计算类要继承 ForkJoinTask
public class ForkJoinDemo01 extends RecursiveTask<Long> {
private long start;
private long end;
private long temp = 10000L;//临界值
public ForkJoinDemo01(long start, long end) {
this.start = start;
this.end = end;
}
//计算方法
@Override
protected Long compute() {
if(end-start > temp){
long middle = (start + end) / 2; //中间值
ForkJoinDemo01 task1 = new ForkJoinDemo01(start, middle);
task1.fork();
ForkJoinDemo01 task2 = new ForkJoinDemo01(middle+1, end);
task2.fork();
return task1.join()+task2.join();
}else{
long sum= 0L;
for (long i = start; i <= end; i++) {
sum+=i;
}
return sum;
}
}
}
测试类
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//test1();//3753
//test2();//412
test3();//263
}
public static void test1(){
long sum = 0L;
long start = System.currentTimeMillis();
for (Long i = 0L; i <= 10_0000_0000; i++) {
sum+=i;
}
long end = System.currentTimeMillis();
System.out.println("一共执行了:"+(end-start));
System.out.println("结果为:"+sum);
}
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo01(0L, 10_0000_0000);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("一共执行了:"+(end-start));
System.out.println("结果为:"+sum);
}
public static void test3(){
long start = System.currentTimeMillis();
//Stream 并行流
long sum = LongStream.rangeClosed(0L, 10_0000_0000).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("一共执行了:"+(end-start));
System.out.println("结果为:"+sum);
}
}
十五. 异步回溯
Future 它设计的初衷就是:对将来的某个事件结果进行建模
实例:
//异步调用:CompletableFuture
//异步执行
//成功回调
//失败回调
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//发起一个请求
//无返回值的异步回调
CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"runAsync=>Void");
});
System.out.println("1111");
future.get();//异步回调 阻塞获取结果
}
}
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class Demo02 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//有返回值的异步回调
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
return 1024;
});
System.out.println(future.whenComplete((t, u) -> {
System.out.println("t->" + t); //正常的返回结果
System.out.println("u->" + u); //错误信息
}).exceptionally((t) -> {
System.out.println(t.getMessage());
return 2333;
}).get());
}
}
十六. JMM
面试的时候,经常会问到你对Volatile的理解:
Volatile是Java虚拟机提供的轻量级的同步机制
1.保证可见性
2.不保证原子性
3.禁止指令重排
这是就要提到JMM了
什么是JMM
JMM:Java内存模型,不存在的东西,概念 约定
关于JMM的一些同步的约定
1.线程解锁前:必须把共享变量,立刻刷回主存
2.线程加锁前:必须读取主存中的最新值到工作内存中
3.加锁和解锁是同一把锁
线程:工作内存,主内存
八种操作
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
- lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
- unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
- use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
- assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
- store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
- write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
- 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存
问题:线程1不知道主内存的值已经被修改过了,这时候就要引出volatile的三大特性
import java.util.concurrent.TimeUnit;
public class Demo01 {
private static int num=0;
public static void main(String[] args) { //main线程
new Thread(()->{ //线程1
while (num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1 );
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num);
}
}
//线程会一直执行
十七. volatile
三大特性
1.保证可见性
import java.util.concurrent.TimeUnit;
public class Demo01 {
//不加volatile 程序就会死循环
//加volatile 可以保证可见性
private volatile static int num=0;
public static void main(String[] args) { //main线程
new Thread(()->{ //线程1 不可见 对主内存的变化是不知道的
while (num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1 );
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num);
}
}
2.不保证原子性
要了解不保证原子性,就要先理解原子性是什么
原子性:不可分割就是原子性
线程A在执行任务的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败
//不保证原子性
public class Demo02 {
private volatile static int num = 0;
public static void add(){
num++;
}
public static void main(String[] args) {
//理论上结果应该是20000
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){ //Java中有两个默认执行的线程 1.main 2.jc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+ num);
}
}
---------------结果---------------
main 18418
如果不加lock或者synchronized,怎么样保证原子性
当我们打开这个文件的反编译文件时,发现它是不安全的
所以我们可以用原子类来解决原子性问题
import java.util.concurrent.atomic.AtomicInteger;
//不保证原子性
public class Demo02 {
//原子类的Integer
private static AtomicInteger num = new AtomicInteger();
public static void add(){
num.getAndIncrement();//AtomicInteger 的+1方法 底层的CAS操作
}
public static void main(String[] args) {
//理论上结果应该是20000
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){ //Java中有两个默认执行的线程 1.main 2.jc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+ num);
}
}
这些类的底层都直接和操作系统挂钩,getAndIncrement 不只是+1 ,而是在内存中修改值
我们看到getAndIncrement 的源码中,有
unsafe类是一个非常特殊的存在
3.禁止指令重排
什么是指令重排:你写的程序,并不是按你写的那样执行的
源代码->编译器优化重排->指令并行重排->内存系统重排->执行
int x = 1; //1
int y = 2; //2
x = x + 5; //3
y = x * x; //4
我们所希望的执行顺序:1234 但可能的执行顺序会变成:2134 1324
可不可能是4123?
处理器在进行指令重排的时候会考虑:数据之间的依赖性!
volatile可以避免指令重排
内存屏障,CPU指令作用:
1.保证特定的操作执行顺序
2.可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现了可见性)
volatile可以保证可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排现象的产生
十八. 深入单例模式
饿汉式,DCL懒汉式
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];
private Hungry(){
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
public class LazyMan {
//懒汉式单例
private LazyMan(){
System.out.println(Thread.currentThread().getName()+" ok");
}
public volatile static LazyMan lazyMan;
//双重检测锁模式的懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
//加锁
if (lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan();//不是一个原子性操作
/**
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向这个空间
* 可能出现的问题
* 线程A 执行顺序:132
* 线程B 执行的时候,他会认为lazyMan已经创建,不是一个null,但是lazyMan还没有完成构造
* 就会出现问题
* 解决方法,添加volatile
*/
}
}
}
return lazyMan;
}
//多线程测试
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
静态内部类型的单例模式
//静态内部类
public class Holder {
private Holder(){
//构造器私有
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
利用反射来破坏单例
import java.lang.reflect.Constructor;
public class LazyMan {
//懒汉式单例
private LazyMan(){
System.out.println(Thread.currentThread().getName()+" ok");
}
public volatile static LazyMan lazyMan;
//双重检测锁模式的懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
//加锁
if (lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan();//不是一个原子性操作
/**
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向这个空间
* 可能出现的问题
* 线程A 执行顺序:132
* 线程B 执行的时候,他会认为lazyMan已经创建,不是一个null,但是lazyMan还没有完成构造
* 就会出现问题
* 解决方法,添加volatile
*/
}
}
}
return lazyMan;
}
//反射 利用反射来创建单例
public static void main(String[] args) throws Exception {
LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
//根据结果发现,两个单例不相同
}
}
---------------结果---------------
main ok
main ok
com.mnm.javaSe.JUCStudy.single.LazyMan@135fbaa4
com.mnm.javaSe.JUCStudy.single.LazyMan@45ee12a7
如何解决用反射破坏单例
import java.lang.reflect.Constructor;
public class LazyMan {
//懒汉式单例
private LazyMan(){
synchronized (LazyMan.class){
if(lazyMan!=null){
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
public volatile static LazyMan lazyMan;
//双重检测锁模式的懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
//加锁
if (lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan();//不是一个原子性操作
}
}
}
return lazyMan;
}
//反射 利用反射来创建单例
public static void main(String[] args) throws Exception {
LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
//根据结果发现,两个单例不相同
}
}
---------------结果---------------
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mnm.javaSe.JUCStudy.single.LazyMan.main(LazyMan.java:43)
Caused by: java.lang.RuntimeException: 不要试图使用反射破坏异常
at com.mnm.javaSe.JUCStudy.single.LazyMan.<init>(LazyMan.java:10)
... 5 more
如果两个对象都用反射来创建呢?
import java.lang.reflect.Constructor;
public class LazyMan {
//懒汉式单例
private LazyMan(){
synchronized (LazyMan.class){
if(lazyMan!=null){
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
public volatile static LazyMan lazyMan;
//双重检测锁模式的懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
//加锁
if (lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan();//不是一个原子性操作
}
}
}
return lazyMan;
}
//反射 利用反射来创建单例
public static void main(String[] args) throws Exception {
//如果两个对象都用反射来创建呢
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
LazyMan instance1 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
//根据结果发现,两个单例不相同
}
}
---------------结果---------------
com.mnm.javaSe.JUCStudy.single.LazyMan@135fbaa4
com.mnm.javaSe.JUCStudy.single.LazyMan@45ee12a7
十九. 深入理解CAS
了解CAS
Unsafe 类
import java.util.concurrent.atomic.AtomicInteger;
public class Demo01 {
//CAS compareAndSet 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//源码:public final boolean compareAndSet(int expect, int update) {
//如果期望的值达到了,那么就更新,否则不更新
//cas是 CPU的并发原语
atomicInteger.compareAndSet(2020,2021);
System.out.println(atomicInteger.get());
atomicInteger.getAndIncrement();//相当于num++
}
}
总结:
CAS:比较当前工作内存中的值,和主内存中的值,如果这个值是期望的,则执行操作,如果不是,则一直循环(自旋锁)
缺点:
1.循环会耗时
2.一次性只能保证一个共享变量的原子性
3.会出现ABA问题
ABA问题(狸猫换太子)
import java.util.concurrent.atomic.AtomicInteger;
public class Demo01 {
//CAS compareAndSet 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//对于我们平时写的SQL:乐观锁
//源码:public final boolean compareAndSet(int expect, int update) {
//如果期望的值达到了,那么就更新,否则不更新
//cas是 CPU的并发原语
//捣乱的线程
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println(atomicInteger.get());
//期望的线程
System.out.println(atomicInteger.compareAndSet(2020, 6666));
System.out.println(atomicInteger.get());
}
}
二十. 原子引用
解决ABA问题,只需要引入原子引用,对应的思想:乐观锁.
带版本号的原子操作
注意点
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class Demo01 {
//如果泛型是一个包装类,要注意对象的引用问题
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);
//CAS compareAndSet 比较并交换
public static void main(String[] args) {
new Thread(()->{
int stamp = atomicStampedReference.getStamp(); //获取版本号
System.out.println("a1=>"+stamp); //输出当前版本号
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 2,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a2=>"+atomicStampedReference.getStamp()); //输出当前版本号
System.out.println(atomicStampedReference.compareAndSet(2, 1,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a3=>"+atomicStampedReference.getStamp()); //输出当前版本号
},"a").start();
//和乐观锁的原理相同
new Thread(()->{
int stamp = atomicStampedReference.getStamp(); //获取版本号
System.out.println("b1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 6,
stamp, stamp+1));
System.out.println("b2=>"+atomicStampedReference.getStamp());
},"b").start();
}
}
---------------结果---------------
a1=>1
b1=>1
true
a2=>2
true
a3=>3
false
b2=>3
二十一. 各种锁的理解
1.公平锁,非公平锁
公平锁:非常公平,不能插队 先来后到
非公平锁:可以存在插队执行(默认都是非公平锁)
源码:
public ReentrantLock() {
sync = new NonfairSync();
}
//当参数中增加一个true就会变成公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2.可重入锁
可重入锁(递归锁)
synchronized锁版
public class Demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+"->sms");
call();//这里也有锁
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"->call");
}
}
Lock锁版
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
Lock lock = new ReentrantLock();
public void sms(){
lock.lock();//锁必须配对,否则就会死在里面
try {
System.out.println(Thread.currentThread().getName()+"->sms");
call();//这里也有锁
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"->call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
3.自旋锁
spinlock 自旋锁
我们先定义一个锁测试
import java.util.concurrent.atomic.AtomicReference;
//自旋锁
public class SpinlockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
//加锁
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"=> mylock");
while (!atomicReference.compareAndSet(null,thread)){
}
}
//解锁
public void myUnLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"=> myUnlock");
atomicReference.compareAndSet(thread,null);
}
}
测试结果:
import java.util.concurrent.TimeUnit;
public class TestSpinlock {
public static void main(String[] args) throws InterruptedException {
/*ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
reentrantLock.unlock();*/
//底层使用自旋锁cas
SpinlockDemo lock = new SpinlockDemo();
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnLock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnLock();
}
},"T2").start();
}
}
---------------结果---------------
T1=> mylock
T2=> mylock
T1=> myUnlock
T2=> myUnlock
4.死锁
死锁是什么:
死锁测试,如何排除死锁
import java.util.concurrent.TimeUnit;
public class DeadlockDemo {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new MyThread(lockA,lockB),"T1").start();
new Thread(new MyThread(lockB,lockA),"T2").start();
}
}
class MyThread implements Runnable{
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);
}
}
}
}
---------------结果---------------
T1lock:lockA=>getlockB
T2lock:lockB=>getlockA
如何解决问题:
1.Java自带的"jps - l" 定位进程号
2.使用"jstack 进程号"查看进程信息
面试或者工作中,排查问题:
1.日志
2.堆栈信息