JUC并发编程学习

文章目录

    • 1. 什么是JUC
    • 2. 进程和线程
    • 3. Lock锁
      • 传统 Synchronized
      • Lock 接口
      • Synchronized 和 Lock 区别
    • 4. 生产者和消费者问题
      • Synchronized版
      • JUC版的生产者和消费者问题
      • Condition 精准的通知和唤醒线程
    • 5. 集合不安全
      • List 不安全
      • Set不安全
      • Map不安全
    • 6. Callable
    • 7. 常用的辅助类
      • 7.1 CountDownLatch
      • 7.2 CyclicBarrier
      • 7.3 Semaphore
    • 8. 读写锁 (ReadWriteLock)
    • 9. 阻塞队列
      • 抛出异常
      • 不抛出异常
      • 阻塞 等待
      • 等待,阻塞(等待超时)
      • SynchronousQueue 同步队列
    • 10. 线程池
      • 三大方法
      • 7大参数
      • 四种拒绝策略
    • 11. ForkJoin
    • 12. 异步回调
    • 13. JMM
      • 内存交互操作
    • 14. volatile
      • 保证可见性
      • 不保证原子性
      • 禁止指令重排
    • 15.单例模式
      • 单例模式攻防战
    • 16. CAS
      • Unsafe类
      • ABA问题
    • 17. 原子引用
    • 18. 各种锁的理解
      • 公平锁 非公平锁
      • 可重入锁(递归锁)
        • Synchronized 版
        • Lock 版
      • 自旋锁
      • 死锁

1. 什么是JUC

在这里插入图片描述
java.util.concurrent包的首字母缩写

2. 进程和线程

进程是并发程序执行过程中资源分配的基本单元,线程是程序运行与调度的基本单元。

Java 真的可以开启线程吗?

不能

查看源码 Thread类的start()方法

方法调了start0()这个本地方法, 底层的是C++ ,Java 无法直接操作硬件

public synchronized void start() {

    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
        }
    }
}

private native void start0();

线程有几个状态

public enum State {
    NEW,//新生
    RUNNABLE,//运行
    BLOCKED,//阻塞
    WAITING,//等待
    TIMED_WAITING,//超时等待
    TERMINATED;//终止
}

3. Lock锁

传统 Synchronized

package juc;

public class LockTest {
    @Test
    public void synTest() {
        //并发:多个线程操作同一个资源类,把资源类丢入线程
        SynTicket ticket = new SynTicket();
        new Thread(() -> {
            for (int i = 1; i < 40; i++) ticket.sale();
        }, "A").start();
        new Thread(() -> {
            for (int i = 1; i < 40; i++) ticket.sale();
        }, "B").start();
        new Thread(() -> {
            for (int i = 1; i < 40; i++)
                ticket.sale();
        }, "C").start();
    }
}

//资源类 OOP 只有属性和方法
class Ticket {
    private int number = 30;

    public synchronized void sale() {
        if (number > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票,还剩:" + number);
        }
    }
}

Lock 接口

三个实现类

ReentrantLock

一个可重入互斥Lock具有与使用synchronized方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;

    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
        ...
    }

    /**
     * 非公平锁
     * 非公平锁在实现的时候多次强调随机抢占
     */
    static final class NonfairSync extends Sync {
        ...
    }

    /**
     * 公平锁
     * 实现机理在于每次有线程来抢占锁的时候,都会检查一遍有没有等待队列,如果有,按队列依次执行
     */
    static final class FairSync extends Sync {
        ...
    }


    public ReentrantLock() {
        sync = new NonfairSync();//默认使用非公平锁
    }

    public ReentrantLock(boolean fair) {//传入true使用公平锁
        sync = fair ? new FairSync() : new NonfairSync();
    }
 	...
}

ReentrantReadWriteLock.ReadLock

读锁

ReentrantReadWriteLock.WriteLock

写锁

public class LockTest {
    @Test
    public void lockTest() {
        LockTicket ticket = new LockTicket();
        new Thread(() -> {
            for (int i = 1; i < 40; i++) ticket.sale();
        }, "A").start();
        new Thread(() -> {
            for (int i = 1; i < 40; i++) ticket.sale();
        }, "B").start();
        new Thread(() -> {
            for (int i = 1; i < 40; i++)
                ticket.sale();
        }, "C").start();
    }
}

/**
 * Lock三步曲
 * 1.new ReentrantLock();
 * 2.lock.lock();
 * 3.finally=>lock.unlock();
 */
class LockTicket {
    private int number = 30;

    Lock lock = new ReentrantLock();

    public void sale() {
        lock.lock();//加锁
        try {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票,还剩:" + number);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//解锁
        }
    }
}

Synchronized 和 Lock 区别

  1. Synchronized 内置的Java关键字, Lock 是一个Java类
  2. Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
  3. Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,会造成死锁
  4. Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去;
  5. Synchronized 可重入锁,不可以中断的,非公平; Lock ,可重入锁,可以判断锁,非公平(可以自己设置);
  6. Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!

4. 生产者和消费者问题

Synchronized版

package juc;

/**
 * 线程之间的通信问题:生产者和消费者问题!	等待唤醒,通知唤醒
 * 线程交替执行	A	B 操作同一个变量	num = 0
 * A num+1
 * B num-1
 *
 * @author Manaphy
 * @date 2020-07-05
 */
public class ProducerConsumer {
    public static void main(String[] args) {
        Data data = new Data();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
    }
}

//判断等待 业务 通知
class Data {
    private int number = 0;

    public synchronized void increment() throws InterruptedException {
        if (number != 0) {
            //等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        //通知其他线程,我+1完毕了
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        if (number == 0) {
            //等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        // 通知其他线程,我-1完毕了
        this.notifyAll();
    }
}

问题存在,A B C D 4 个线程! 虚假唤醒

JUC并发编程学习_第1张图片

用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。

这也就是为什么用while而不用if的原因了,因为线程被唤醒后,执行开始的地方是wait之后。

A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
C=>1
A=>2
C=>3
B=>2
B=>1
B=>0
C=>1
A=>2
C=>3
B=>2
B=>1
B=>0
C=>1
A=>2
C=>3
D=>2
D=>1
D=>0
B=>-1
D=>-2
D=>-3
D=>-4
D=>-5
D=>-6
D=>-7
D=>-8
C=>-7
A=>-6
C=>-5
A=>-4
C=>-3
A=>-2
C=>-1
A=>0

解决方案

if 改为 while 判断

public class ProducerConsumer {
    public static void main(String[] args) {
        Data data = new Data();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();
    }
}

//判断等待 业务 通知
class Data {
    private int number = 0;

    public synchronized void increment() throws InterruptedException {
        while (number != 0) {
            //等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        //通知其他线程,我+1完毕了
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        while (number == 0) {
            //等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        // 通知其他线程,我-1完毕了
        this.notifyAll();
    }
}

JUC版的生产者和消费者问题

public class ProducerConsumer {
    public static void main(String[] args) {
        Data data = new Data();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();
    }
}

//判断等待 业务 通知
class Data {
    private int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    public void increment() throws InterruptedException {
        lock.lock();
        try {
            while (number != 0) {
                //等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程,我+1完毕了
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number == 0) {
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

Condition 精准的通知和唤醒线程

public class ConditionExample {
    public static void main(String[] args) {
        Data2 data = new Data2();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                data.printA();
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                data.printB();
            }
        }, "B").start();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                data.printC();
            }
        }, "C").start();
    }
}

class Data2 {
    private int number = 1;
    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();

    public void printA() {
        lock.lock();
        try {
            while (number != 1) {
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            number = 2;
            condition2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB() {
        lock.lock();
        try {
            while (number != 2) {
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            number = 3;
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC() {
        lock.lock();
        try {
            while (number != 3) {
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            number = 1;
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

结果如下

A=>1
B=>2
C=>3
A=>1
B=>2
C=>3
A=>1
B=>2
C=>3
A=>1
B=>2
C=>3
A=>1
B=>2
C=>3

5. 集合不安全

List 不安全

public class ListTest {
    public static void main(String[] args) {
        /*
         * List list = new ArrayList<>();
         * java.util.ConcurrentModificationException  --> 并发修改异常
         * 并发下 ArrayList 是不安全的
         * 解决方案
         * 1.List list = new Vector<>();
         * 2.List list = Collections.synchronizedList(new ArrayList<>());
         * 3.List list = new CopyOnWriteArrayList<>();
         */
        /*
         * CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略
         * 多个线程调用的时候 list 读取的时候,固定的,写入(覆盖)
         * 在写入的时候避免覆盖,造成数据问题!
         * 底层 add() 方法使用了 lock 锁
         */

        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

CopyOnWriteArrayList类底层 add() 方法

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

Set不安全

public class SetTest {
    public static void main(String[] args) {
        /*
         * Set set = new HashSet<>();
         * 还是会出 java.util.ConcurrentModificationException 异常
         *
         * 解决方案
         * 1.Set set = Collections.synchronizedSet(new HashSet<>());
         * 2.Set set = new CopyOnWriteArraySet<>();
         */
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
    }
}

HashSet的底层

public HashSet() {
    map = new HashMap<>();
}
//add()方法 set 本质就是 map key是无法重复的!
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
//PRESENT是一个空Object 不变的值
private static final Object PRESENT = new Object();

Map不安全

public class MapTest {
    public static void main(String[] args) {
        /*
         * HashMap map = new HashMap<>();
         * 也会报 java.util.ConcurrentModificationException
         *
         * 解决方案
         * 1.Map map = Collections.synchronizedMap(new HashMap<>());
         * 2.Map map = new ConcurrentHashMap<>();
         */
        Map<String, String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
                System.out.println(map);
            }, String.valueOf(i)).start();
        }
    }
}

6. Callable

类似于 Runnable,与Runnable的区别

  • 可以有返回值
  • 可以抛出异常
  • 方法不同,run()/ call()
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //Runnable的实现类-> Class FutureTask 构造方法-> FutureTask(Callable callable)

        //new Thread().start(); 怎么启动Callable
        MyThread thread = new MyThread();
        //适配类
        FutureTask<Integer> futureTask = new FutureTask<>(thread);
        new Thread(futureTask, "A").start();
        new Thread(futureTask, "B").start();//两条线程只会打印一个 call()...
        /*
         * 获取Callable的返回结果
         * 不过这个方法可能会产生阻塞,一般把他放到最后
         * 或者使用异步通讯来处理
         */
        Integer integer = futureTask.get();
        System.out.println(integer);
    }
}

class MyThread implements Callable<Integer> {
    @Override
    public Integer call() {
        System.out.println("call()...");
        return 1024;
    }
}

7. 常用的辅助类

7.1 CountDownLatch

允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。

A CountDownLatch用给定的计数初始化

/**
 * 线程减法计数器
 *
 * @author Manaphy
 * @date 2020-07-07
 */
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //例子 所有人出门后才能关门
        CountDownLatch countDownLatch = new CountDownLatch(6);//设置6个人
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "号出门了");
                countDownLatch.countDown();//数量-1
            }, String.valueOf(i)).start();
        }
        //等待计数器归零,再往下执行
        countDownLatch.await();
        //线程执行速度不一样 有的快有的慢 部分业务需要等所有线程结束后才能再处理后面的逻辑 此时 就需要 countDownLatch
        System.out.println("关门");
    }
}

结果如下

1号出门了
3号出门了
4号出门了
2号出门了
5号出门了
6号出门了
关门

7.2 CyclicBarrier

允许一组线程全部等待彼此达到共同屏障点的同步辅助

/**
 * 线程加法计数器
 *
 * @author Manaphy
 * @date 2020-07-07
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        //例子:集齐7颗龙珠召唤神龙
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("召唤神龙");
        });

        for (int i = 1; i <= 7; i++) {
            //lambda不能操作 i ,所以定义 final temp
            final int temp = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "收集第" + temp + "颗龙珠");
                try {
                    cyclicBarrier.await();//等待集齐龙珠
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

结果如下

Thread-0收集第1颗龙珠
Thread-6收集第7颗龙珠
Thread-3收集第4颗龙珠
Thread-4收集第5颗龙珠
Thread-1收集第2颗龙珠
Thread-2收集第3颗龙珠
Thread-5收集第6颗龙珠
召唤神龙

7.3 Semaphore

一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它。

同一时间只能有指定数量个得到线程

/**
 * 信号量
 * 同一时间只能有指定数量个得到线程
 *
 * @author Manaphy
 * @date 2020-07-07
 */
public class SemaphoreDemo {
    public static void main(String[] args) {
        //例子:抢车位
        //线程数量: 停车位 
        Semaphore semaphore = new Semaphore(3);
        //共有6辆车
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();//得到
                    System.out.println(Thread.currentThread().getName() + "抢到车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName() + "离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();//释放
                }
            }).start();
        }
    }
}

结果如下

Thread-0抢到车位
Thread-2抢到车位
Thread-1抢到车位
Thread-1离开车位
Thread-0离开车位
Thread-4抢到车位
Thread-2离开车位
Thread-3抢到车位
Thread-5抢到车位
Thread-5离开车位
Thread-4离开车位
Thread-3离开车位

8. 读写锁 (ReadWriteLock)

读可以被多个线程同时读

写的时候只能有一个线程去写

package juc;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁演示
 *
 * @author Manaphy
 * @date 2020-07-10
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        // 写入
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(() -> myCache.put(temp + "", temp + ""), String.valueOf(i)).start();
        }

        // 读取
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(() -> myCache.get(temp + ""), String.valueOf(i)).start();
        }
    }
}


/**
 * 自定义缓存
 */
class MyCache {
    // 存,写
    private final Map<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() + "写入OK");
    }

    // 取,读
    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "读取" + key);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取OK");
    }
}

结果如下

1写入1
4写入4    <--写入的时候被插队了
4写入OK
2写入2
2写入OK
3写入3
5写入5
1读取1
1写入OK
1读取OK
5写入OK
3写入OK
4读取4
4读取OK
3读取3
2读取2
3读取OK
2读取OK
5读取5
5读取OK

加入读写锁

package juc;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁演示
 *
 * @author Manaphy
 * @date 2020-07-10
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCacheLock myCache = new MyCacheLock();

        // 写入
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(() -> myCache.put(temp + "", temp + ""), String.valueOf(i)).start();
        }

        // 读取
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(() -> myCache.get(temp + ""), String.valueOf(i)).start();
        }
    }
}

/**
 * 加入读写锁
 */
class MyCacheLock {
    private final Map<String, Object> map = new HashMap<>();
    // 读写锁: 更加细粒度的控制
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    // 存,写入的时候,只希望同时只有一个线程写
    public void put(String key, Object value) {
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            Object put = map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    // 取,读,所有人都可以读!
    public void get(String key) {
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }

}

效果如下

1写入1
1写入OK
3写入3
3写入OK
4写入4
4写入OK
2写入2
2写入OK
5写入5
5写入OK
1读取1
2读取2
3读取3
4读取4
4读取OK
1读取OK
5读取5
5读取OK
3读取OK
2读取OK

9. 阻塞队列

JUC并发编程学习_第2张图片

什么情况下我们会使用 阻塞队列:多线程并发处理,线程池!

四组API

方式 抛出异常 有返回值,不抛出异常 阻塞 等待 超时等待
添加 add offer() put() offer(,)
移除 remove poll() take() poll(,)
检测队首元素 element peek - -

抛出异常

@Test
public void test1() {
    // 队列的大小
    ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

    System.out.println(blockingQueue.add("a"));
    System.out.println(blockingQueue.add("b"));
    System.out.println(blockingQueue.add("c"));
    // IllegalStateException: Queue full 抛出异常!
    // System.out.println(blockingQueue.add("d"));

    System.out.println(blockingQueue.remove());
    System.out.println(blockingQueue.remove());
    System.out.println(blockingQueue.remove());

    // java.util.NoSuchElementException 抛出异常!
    // System.out.println(blockingQueue.remove());
}

不抛出异常

@Test
public void test2() {
    // 队列的大小
    ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

    System.out.println(blockingQueue.offer("a"));// true
    System.out.println(blockingQueue.offer("b"));// true
    System.out.println(blockingQueue.offer("c"));// true
    System.out.println(blockingQueue.offer("d"));// false 不抛异常

    System.out.println(blockingQueue.poll());// a
    System.out.println(blockingQueue.poll());// b
    System.out.println(blockingQueue.poll());// c
    System.out.println(blockingQueue.poll());// null 不抛异常
}

阻塞 等待

@Test
public void test03() throws InterruptedException {
    ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
    blockingQueue.put("a");
    blockingQueue.put("b");
    blockingQueue.put("c");
    // blockingQueue.put("d");// 队列没有位置了,一直阻塞

    blockingQueue.take();
    blockingQueue.take();
    blockingQueue.take();
    // blockingQueue.take();// 没有这个元素,一直阻塞
}

等待,阻塞(等待超时)

@Test
public void test04() throws InterruptedException {
    // 队列的大小
    ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);


    blockingQueue.offer("a");
    blockingQueue.offer("b");
    blockingQueue.offer("c");
    // blockingQueue.offer("d", 2, TimeUnit.SECONDS); // 等待超过2秒就退出
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    blockingQueue.poll(2, TimeUnit.SECONDS); // 等待超过2秒就退出
}

SynchronousQueue 同步队列

package juc;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

/**
 * 同步队列演示
 * *和其他的BlockingQueue 不一样, SynchronousQueue 不存储元素
 * *put了一个元素,必须从里面先take取出来,否则不能再put进去值!
 *
 * @author Manaphy
 * @date 2020-07-11
 */
public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>(); // 同步队列

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "=>" + blockingQueue.take());

                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "=>" + blockingQueue.take());

                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "=>" + blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "T2").start();

        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + " put 1");
                blockingQueue.put("1");
                System.out.println(Thread.currentThread().getName() + " put 2");
                blockingQueue.put("2");
                System.out.println(Thread.currentThread().getName() + " put 3");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "T1").start();

    }
}

结果如下

T1 put 1
T2=>1
T1 put 2
T2=>2
T1 put 3
T2=>3

10. 线程池

线程池:三大方法、7大参数、4种拒绝策略

线程池的好处

  1. 降低资源的消耗

  2. 提高响应的速度

  3. 方便管理。

线程复用、可以控制最大并发数、管理线程

三大方法

package juc;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Executors 工具类、3大方法
 *
 * @author Manaphy
 * @date 2020-07-11
 */
public class ThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小
//        ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩的,遇强则强,遇弱则弱

        try {
            for (int i = 0; i < 10; i++) {
                // 使用线程池来创建
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " ok");
                });
            }
        } finally {
            // 线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }
    }
}

单线程运行结果

pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok

固定线程池大小的运行结果

pool-1-thread-1 ok
pool-1-thread-4 ok
pool-1-thread-3 ok
pool-1-thread-3 ok
pool-1-thread-3 ok
pool-1-thread-2 ok
pool-1-thread-3 ok
pool-1-thread-4 ok
pool-1-thread-1 ok
pool-1-thread-5 ok

可扩展线程池运行结果

pool-1-thread-1 ok
pool-1-thread-3 ok
pool-1-thread-2 ok
pool-1-thread-5 ok
pool-1-thread-4 ok
pool-1-thread-7 ok
pool-1-thread-6 ok
pool-1-thread-8 ok
pool-1-thread-9 ok
pool-1-thread-10 ok

7大参数

源码分析

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,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
// 本质是ThreadPoolExecutor()
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;
}                         

查看阿里巴巴公约规范发现规约以上三种方式都不好用, 要手动创建线程池

JUC并发编程学习_第3张图片

四种拒绝策略

手动创建线程池

public class ThreadPoolDemo {
    public static void main(String[] args) {
        //ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
        //ExecutorService threadPool2 = Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小
        //ExecutorService threadPool3 = Executors.newCachedThreadPool();//可伸缩的,遇强则强,遇弱则弱


        /*
         * 自定义线程池
         * 最大承载量(队列满了) = Deque + maximumPoolSize
         *
         * 四种拒绝策略
         * new ThreadPoolExecutor.AbortPolicy() // 队列满了抛出异常 RejectedExecutionException
         * new ThreadPoolExecutor.CallerRunsPolicy() // 哪里来的去哪里
         * new ThreadPoolExecutor.DiscardPolicy() // 队列满了不会抛出异常,会丢掉任务
         * new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了尝试和最早的竞争,也不会抛出异常
         *
         * 最大线程(maximumPoolSize)如何设置
         * 1. CPU 密集型 设置为当前电脑最大核心数 Runtime.getRuntime().availableProcessors();
         * 2. IO 密集型 设置为最大任务的2倍
         */
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                Runtime.getRuntime().availableProcessors(),
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );


        try {
            for (int i = 0; i < 20; i++) {
                // 使用线程池来创建
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " ok");
                });
            }
        } finally {
            // 线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }
    }
}

11. ForkJoin

ForkJoin 在 JDK 1.7 , 并行执行任务!提高效率。在大数据量时使用

package juc;

import org.junit.Test;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;

/**
 * 计算从1到10_0000_0000的和
 *
 * @author Manaphy
 * @date 2020-07-11
 */
public class ForkJoinDemo {

    /**
     * 普通的方法
     */
    @Test
    public void sum1() {
        long start = System.currentTimeMillis();
        long sum = 0L;
        for (int i = 0; i <= 10_0000_0000; i++) {
            sum += i;
        }
        System.out.println(sum);
        System.out.println(System.currentTimeMillis() - start);//277
    }

    /**
     * 使用并行流(最快)
     */
    @Test
    public void sum2() {
        long start = System.currentTimeMillis();
        long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
        System.out.println(sum);
        System.out.println(System.currentTimeMillis() - start);//161
    }

    /**
     * 使用 ForkJoin 的方法
     */
    @Test
    public void sum3() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();

        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinSum task = new ForkJoinSum(0L, 10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        long sum = submit.get();

        System.out.println(sum);
        System.out.println(System.currentTimeMillis() - start);// 188
    }

    public static class ForkJoinSum extends RecursiveTask<Long> {

        private final long start;
        private final long end;

        public ForkJoinSum(long start, long end) {
            this.start = start;
            this.end = end;
        }

        // 计算方法
        @Override
        protected Long compute() {
            // 临界值
            long temp = 100000L;
            if ((end - start) < temp) {
                long sum = 0L;
                for (long i = start; i <= end; i++) {
                    sum += i;
                }
                return sum;
            } else { // forkJoin 递归
                long middle = (start + end) / 2; // 中间值
                ForkJoinSum task1 = new ForkJoinSum(start, middle);
                task1.fork(); // 拆分任务,把任务压入线程队列
                ForkJoinSum task2 = new ForkJoinSum(middle + 1, end);
                task2.fork(); // 拆分任务,把任务压入线程队列
                return task1.join() + task2.join();
            }
        }
    }
}

12. 异步回调

对将来的某个事件的结果进行建模

public class CompletableFutureDemo {

    @Test
    public void voidTest() throws ExecutionException, InterruptedException {
        // 没有返回值的 runAsync 异步调用
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException ignored) {
            }
            System.out.println(Thread.currentThread().getName() + "runAsync=>Void");
        });
        System.out.println("程序执行中...");
        completableFuture.get();
    }

    @Test
    public void returnTest() throws ExecutionException, InterruptedException {
        // 有返回值的 supplyAsync 异步调用
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
//            int i = 1 / 0;
            return 1024;
        });
        //和 ES6 的 Promise类似
        Integer integer = completableFuture.whenComplete((t, u) -> {
            System.out.println("t = " + t);// 程序正常执行:t = 1024 出异常:t = null
            System.out.println("u = " + u);// 程序正常执行:u = null 出异常:u = ...(异常信息) / by zero
        }).exceptionally((e) -> {
                    System.out.println(e.getMessage());//java.lang.ArithmeticException: / by zero
                    return 400;
                }
        ).get();
        System.out.println(integer);// 程序正常执行:1024 出异常:400
    }
}

13. JMM

JMM即为JAVA 内存模型(java memory model)

JUC并发编程学习_第4张图片

内存交互操作

内存交互操作有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操作之前,必须把此变量同步回主内存

14. volatile

Volatile 是 Java 虚拟机提供轻量级的同步机制

可以保持 可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!

保证可见性

public class VolatileDemo { // main线程
    //不加 volatile 程序就会死循环
    //加 volatile 可以保证可见性
    private volatile static int num = 0;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> { // 新线程 对主内存的变化是不知道的
            while (num == 0) {
            }
        }).start();

        TimeUnit.SECONDS.sleep(1);
        num = 1;
        System.out.println(num);
    }
}

不保证原子性

public class VolatileDemo2 {
    // volatile 不保证原子性
    private volatile static int num = 0;

    static void add() {
        num++;
    }

    public static void main(String[] args) {
        //理论上 num=20000
        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);// main->14667
    }
}

如果不加 lock 和 synchronized ,怎么样保证原子性

public class VolatileDemo2 {
    //不使用lock 和 synchronized 的解决方案
    private static final AtomicInteger atomicInteger = new AtomicInteger();

    static void add() {
        atomicInteger.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() + "->" + atomicInteger);// main->20000
    }
}

禁止指令重排

指令重排:你写的程序,计算机并不是按照你写的那样去执行的

源代码–>编译器优化的重排–> 指令并行也可能会重排–> 内存系统也会重排—> 执行

int x = 1; // 1
int y = 2; // 2 
x = x + 5; // 3 
y = x * x; // 4

我们所期望的顺序:1234	但是可能执行的时候会变成 2134	1324
但不可能是 4123!因为处理器在进行指令重排的时候会考虑数据之间的依赖性

15.单例模式

单例模式攻防战

标准的DCL懒汉式

class Singleton {
    private Singleton() {
    }

    private static volatile Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton6();
                }
            }
        }
        return instance;
    }
}

攻=> 通过反射破坏单例性

public static void main(String[] args) throws Exception {
    Singleton instance = Singleton.getInstance();
    Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(null);
    constructor.setAccessible(true);
    Singleton newInstance = constructor.newInstance();
    System.out.println(newInstance == newInstance2);
}

防=> 私有构造方法加判断

class Singleton {
    private Singleton() {
        synchronized (Singleton.class) {
            // 避免反射破坏
            if (instance != null) {
                throw new RuntimeException("不要试图使用反射破坏单例异常");
            }
        }
    }

    private static volatile Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

攻=> 使用反射创建两个对象来破坏单例

public static void main(String[] args) throws Exception {
    Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(null);
    constructor.setAccessible(true);
    Singleton newInstance = constructor.newInstance();
    Singleton newInstance2 = constructor.newInstance();
    System.out.println(newInstance == newInstance2);
}

防=> 定义一个别人不知道的变量

class Singleton {

    private static boolean manaphy = false;

    private Singleton() {
        synchronized (Singleton.class) {
            if (!manaphy) {
                manaphy = true;
            } else {
                throw new RuntimeException("不要试图使用反射破坏单例异常");
            }
        }
    }

    private static volatile Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

攻=> 通过反编译发现这个变量

public static void main(String[] args) throws Exception {
    Field manaphy = Singleton.class.getDeclaredField("manaphy");
    manaphy.setAccessible(true);
    Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(null);
    constructor.setAccessible(true);
    Singleton newInstance = constructor.newInstance();
    manaphy.set(newInstance, false);
    Singleton newInstance2 = constructor.newInstance();
    System.out.println(newInstance == newInstance2);
}

防=> 使用枚举类

enum Singleton {
    INSTANCE;

    public Singleton getInstance() {
        return INSTANCE;
    }
}

攻=> 常规攻击

public static void main(String[] args) throws Exception {
    Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(null);
    constructor.setAccessible(true);
    Singleton newInstance = constructor.newInstance();
    Singleton newInstance2 = constructor.newInstance();
    System.out.println(newInstance == newInstance2);
}

发现报错了java.lang.NoSuchMethodException: data.test.Singleton.()意思是没有无参构造

直接用idea查看Singleton反编译文件

enum Singleton {
    INSTANCE;

    private Singleton() {
    }

    public Singleton getInstance() {
        return INSTANCE;
    }
}

发现有私有无参构造器

使用javap -p Singleton.class命令反编译Singleton.class文件, 发现没有构造器

final class data.test.Singleton extends java.lang.Enum<data.test.Singleton> {
  public static final data.test.Singleton INSTANCE;
  private static final data.test.Singleton[] $VALUES;
  public static data.test.Singleton[] values();
  public static data.test.Singleton valueOf(java.lang.String);
  private data.test.Singleton();
  public data.test.Singleton getInstance();
  static {};
}

使用jad工具查看终极源码jad -sjava Singleton.class

package data.test;


final class Singleton extends Enum
{

    public static Singleton[] values()
    {
        return (Singleton[])$VALUES.clone();
    }

    public static Singleton valueOf(String name)
    {
        return (Singleton)Enum.valueOf(data/test/Singleton, name);
    }

    private Singleton(String s, int i)
    {
        super(s, i);
    }

    public Singleton getInstance()
    {
        return INSTANCE;
    }

    public static final Singleton INSTANCE;
    private static final Singleton $VALUES[];

    static 
    {
        INSTANCE = new Singleton("INSTANCE", 0);
        $VALUES = (new Singleton[] {
            INSTANCE
        });
    }
}

发现一个带有两个参数的构造器private Singleton(String s, int i){super(s, i);}

传入这两个参数类继续攻击

public static void main(String[] args) throws Exception {
    Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(String.class, int.class);
    constructor.setAccessible(true);
    Singleton newInstance = constructor.newInstance();
    Singleton newInstance2 = constructor.newInstance();
    System.out.println(newInstance == newInstance2);
}

发现报java.lang.IllegalArgumentException: Cannot reflectively create enum objects错误

攻击失败枚举类单例无法被破坏

16. CAS

比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!

public class CASDemo {
    //CAS compareAndSet:比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        //期望 更新
        //public final boolean compareAndSet(int expect, int update){...}
        如果期望的值达到了,那么有更新,否则就不更新 CAS 是CPU的并发原语
        System.out.println(atomicInteger.compareAndSet(2020, 2021));// true
        System.out.println(atomicInteger.get());// 2021

        System.out.println(atomicInteger.compareAndSet(2020, 2021));// false
        System.out.println(atomicInteger.get());// 2021
    }
}

源码

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
//发现使用Unsafe类调用的compareAndSwapInt()方法,点进去发现这个方法是本地方法
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

Java无法操作内存 但可以通过 native 调用c++ 的方法

c++可以操作内存, 所以Java可以通过这个类操作内存

Unsafe类

查看上文AtomicInteger类的getAndIncrement()方法源码

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}
//点进去查看源码,getIntVolatile()方法是本地方法,所以操作内存,效率高
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}
//这个do-while循环就是自旋锁 使用cas来保证原子性

缺点:

  • 循环会耗时
  • 一次性只能保证 一个共享变量的原子性
  • ABA问题

ABA问题

public class CASDemo {
    //CAS compareAndSet:比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);

        //----------------------捣乱的线程start----------------------------------
        System.out.println(atomicInteger.compareAndSet(2020, 2021));// true
        System.out.println(atomicInteger.get());// 2021

        System.out.println(atomicInteger.compareAndSet(2021, 2020));// true
        System.out.println(atomicInteger.get());// 2020
        //----------------------捣乱的线程end----------------------------------

        //----------------------期望的线程----------------------------------
        System.out.println(atomicInteger.compareAndSet(2020, 6666));// true
        System.out.println(atomicInteger.get());// 6666
    }
}

17. 原子引用

解决ABA 问题,引入原子引用! 对应的思想:乐观锁!

public class AtomicStampedReferenceDemo {
    static AtomicStampedReference<String> reference = new AtomicStampedReference<>("chen", 1);

    public static void main(String[] args) {
        new Thread(() -> {
            int stamp = reference.getStamp();// 获得版本号
            System.out.println("a1=>" + stamp);

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            reference.compareAndSet("chen", "manaphy", reference.getStamp(), reference.getStamp() + 1);
            System.out.println("a2=>" + reference.getStamp());// 第一次修改

            reference.compareAndSet("manaphy", "chen", reference.getStamp(), reference.getStamp() + 1);
            System.out.println("a3=>" + reference.getStamp());// 将值改回来
        }, "a").start();

        new Thread(() -> {
            int stamp = reference.getStamp();
            System.out.println("b1=>" + stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(reference.compareAndSet("chen", "tom", stamp, stamp + 1));// 修改失败
            System.out.println("b2=>" + reference.getStamp());
        }, "b").start();
    }
}

输出如下

a1=>1
b1=>1
a2=>2
a3=>3
false
b2=>3

18. 各种锁的理解

公平锁 非公平锁

再次贴出ReentrantLock的源码

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;

    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
        ...
    }

    /**
     * 非公平锁
     * 非公平锁在实现的时候多次强调随机抢占
     */
    static final class NonfairSync extends Sync {
        ...
    }

    /**
     * 公平锁
     * 实现机理在于每次有线程来抢占锁的时候,都会检查一遍有没有等待队列,如果有,按队列依次执行
     */
    static final class FairSync extends Sync {
        ...
    }


    public ReentrantLock() {
        sync = new NonfairSync();//默认使用非公平锁
    }

    public ReentrantLock(boolean fair) {//传入true使用公平锁
        sync = fair ? new FairSync() : new NonfairSync();
    }
 	...
}

可重入锁(递归锁)

Synchronized 版

@Test
public void synchronizedTest() {
    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();// 这里调用加锁的call()方法
    }

    public synchronized void call() {
        System.out.println(Thread.currentThread().getName() + " call");
    }
}

结果如下

A sms
A call
B sms
B call

多次执行 发现只有A线程执行完了sms()和call()方法后线程B才开始执行

Lock 版

@Test
public void lockTest() {
    Phone2 phone = new Phone2();
    new Thread(phone::sms, "A").start();
    new Thread(phone::sms, "B").start();
}

public static class Phone2 {
    Lock lock = new ReentrantLock();

    public void sms() {
        lock.lock();
        lock.lock();// lock 锁必须配对,否则就会死在里面
        try {
            System.out.println(Thread.currentThread().getName() + " sms");
            call();// 这里调用加锁的call()方法
        } finally {
            lock.unlock();
            lock.unlock();
        }
    }

    public void call() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " call");
        } finally {
            lock.unlock();
        }
    }
}

执行后是同样的效果

自旋锁

上文 Unsafe类 里就有自旋锁

do {
    var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

自定义一个自旋锁测试

package juc;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 自旋锁的演示
 *
 * @author Manaphy
 * @date 2020-07-11
 */
public class SpinlockDemo {
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    // 加锁
    public void lock() {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + "-->lock");
        // 自旋锁
        while (!atomicReference.compareAndSet(null, thread)) {
        }
    }

    //解锁
    public void unlock() {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + "-->unlock");
        atomicReference.compareAndSet(thread, null);
    }

    public static void main(String[] args) {
        SpinlockDemo lock = new SpinlockDemo();
        new Thread(() -> {
            lock.lock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "T1").start();

        new Thread(() -> {
            lock.lock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "T2").start();
    }
}

结果如下

T1-->lock
T2-->lock
T1-->unlock
T2-->unlock

分析: T2线程 lock 后就会进入自旋锁状态(在此期间 期望是null, 但是T1线程一直在执行, 所以不是null atomicReference.compareAndSet(null, thread)一直返回 false ==> while循环就一直是 true) 直到T1释放锁

死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

package juc;

import java.util.Date;

public class DeadLockTest {
    public static final String obj1 = "obj1";
    public static final String obj2 = "obj2";

    public static void main(String[] args) {
        LockA la = new LockA();
        new Thread(la).start();
        LockB lb = new LockB();
        new Thread(lb).start();
    }
}

class LockA implements Runnable {
    public void run() {
        try {
            System.out.println(new Date().toString() + " LockA 开始执行");
            while (true) {
                synchronized (DeadLockTest.obj1) {
                    System.out.println(new Date().toString() + " LockA 锁住 obj1");
                    Thread.sleep(3000); // 此处等待是给B能锁住机会
                    synchronized (DeadLockTest.obj2) {
                        System.out.println(new Date().toString() + " LockA 锁住 obj2");
                        Thread.sleep(60 * 1000); // 为测试,占用了就不放
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class LockB implements Runnable {
    public void run() {
        try {
            System.out.println(new Date().toString() + " LockB 开始执行");
            while (true) {
                synchronized (DeadLockTest.obj2) {
                    System.out.println(new Date().toString() + " LockB 锁住 obj2");
                    Thread.sleep(3000); // 此处等待是给A能锁住机会
                    synchronized (DeadLockTest.obj1) {
                        System.out.println(new Date().toString() + " LockB 锁住 obj1");
                        Thread.sleep(60 * 1000); // 为测试,占用了就不放
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

使用jps进行排查

D:\Git\JavaProject>jps -l #定位进程号
8344 juc.DeadLockTest
13916
9884 sun.tools.jps.Jps

D:\Git\JavaProject>jstack 8344 #找到死锁问题
2020-07-11 22:36:55
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.251-b08 mixed mode):

...

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at juc.LockB.run(DeadLockTest.java:46)
        - waiting to lock <0x0000000716c206d0> (a java.lang.String)
        - locked <0x0000000716c20700> (a java.lang.String)
        at java.lang.Thread.run(Thread.java:748)
"Thread-0":
        at juc.LockA.run(DeadLockTest.java:26)
        - waiting to lock <0x0000000716c20700> (a java.lang.String)
        - locked <0x0000000716c206d0> (a java.lang.String)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

你可能感兴趣的:(Java高级)