JUC并发编程
1,什么是JUC(java.util.concurrent)
java.util工具包
业务:普通的线程代码:Thread
Runnable 没有返回值,效率相对callable较低
以及lock锁
2,线程和进程
线程,进程
线程:
一个程序,WeChat.exe,QQ.exe,程序的集合
一个进程可以包含多个线程,至少包含一个
java默认有两个线程,一个是main线程,一个是负责垃圾回收的GC线程进程
线程:
例如开启了一个进程(腾讯视频),播放声音是一个线程,播放音乐是一个线程,每一个同时执行的任务都是不同的线程负责的,
对于Java而言:Thread,Runnable,Callable
Java是运行在虚拟机上的,本质上不能直接开启线程,我们创建的线程通过.start()方法调用后,是走了start0()这个native方法调用底层的C++实现的,Java本质上不能直接操作硬件
public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } //本地方法,调用底层C++,Java不能直接操作硬件 private native void start0();
并发,并行
并发(多个线程操作同一个资源)
- 一核CPU,模拟出来多条线程,线程间快速交替,产生并行的假象
并行(多个人一起行走)
多核CPU,多个线程同时运行执行 , 线程池
public class ThreadTest { public static void main(String[] args) { //查看电脑的CPU核数,CPU密集型,IO密集型 System.out.println(Runtime.getRuntime().availableProcessors()); } }
并发编程的本质:充分利用CPU的资源
线程有几个状态
以下代码来自Thread.State源码
* @since 1.5
* @see #getState
*/
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
Java定义了六种线程状态
- 新生,运行,阻塞,等待,超时等待,终止
wait/sleep的区别
- 来自不同的类,wait来自Object,sleep来自Thread
- 使用范围不同,sleep可以在任何地方使用,wait只能在同步代码块中使用
- 不会释放锁,sleep不会释放锁,wait会释放锁
- 自JDK1.8以后,两个方法都需要抛出InterruptedException这个中断异常
3,Lock锁
传统的Synchronized
在方法返回值之前加上synchronized关键字
public class SellTicketDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
//JDK1.8之后的lambda表达式
new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sellTicket();
}
},"A").start();
new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sellTicket();
}
},"B").start();
new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sellTicket();
}
},"C").start();
}
}
//OOP
class Ticket {
//属性,方法
private int ticketNumber = 30;
public synchronized void sellTicket(){
if(ticketNumber>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+ticketNumber--+"张票,还剩下"+ticketNumber+"张票");
}
}
}
synchronized本质上就是队列,锁,本质上就是让线程排队,让所有不讲规矩的人规规矩矩的排队
Lock接口
package com.hai.demo03;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Muzi
* @version 3.6.3
* @package com.hai.demo03
* @date 2020/12/7 16:02
* @project juc
**/
public class SellTicketDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
//JDK1.8之后的lambda表达式
new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sellTicket();
}
},"A").start();
new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sellTicket();
}
},"B").start();
new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sellTicket();
}
},"C").start();
}
}
//OOP
//三部曲
//1.new ReentrantLock() 创建锁
//2.lock.lock() 上锁
//3.lock.unlock() 解锁
class Ticket {
//属性,方法
private int ticketNumber = 30;
Lock lock = new ReentrantLock();
public void sellTicket(){
lock.lock();
try {
if(ticketNumber>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+ticketNumber--+"张票,还剩下"+ticketNumber+"张票");
}
}finally {
lock.unlock();
}
}
}
Synchronized和Lock锁的区别
- synchronized是java内置关键字,Lock是一个类
- synchronized会自动释放锁,Lock必须手动释放锁
- synchronized在其他线程阻塞的情况下,傻等释放锁,Lock阻塞时会尝试获取锁
- synchronized不能判断锁的状态,Lock可以判断时候获取到了锁
- synchronized可重入锁,不可中断的,非公平锁,Lock可重入锁,可以判断锁,公平锁(可以自己设置)
- synchronized适合锁少量代码块,Lock适合锁大量同步代码块
锁是什么,如何判断锁的是谁
4,生产者和消费者问题
生产者和消费者问题,synchronized版
//生产者消费者问题,实现线程之间的通信
public class PC {
public static void main(String[] args) {
Date date = new Date();
new Thread(()->{
for (int i = 0; i < 10; i++) {
date.increment();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
date.decrement();
}
},"B").start();
}
}
class Date{
private int number = 0;
//+1
public synchronized void increment(){
if(number!=0){
//等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//业务
number++;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//唤醒
this.notifyAll();
}
//-1
public synchronized void decrement(){
if(number==0){
//等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//业务
number--;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//唤醒
this.notifyAll();
}
}
在只有两个线程的情况下,这是没有问题的,那要是三个四个线程呢?看效果
可见数据产生了负数,如果用到线上环境发生这种情况,那你基本要蹲号子了,
还引发了一个问题,程序并没有结束运行,也没有任何输出,线程之间的通信做不好甚至会引起死锁的情况
问题发生的原因:
- 是因为我们做了if判断,因为if判断只判断一次,在两个线程都进入的情况下,两个线程看到的数据都是可进入的数据,两个线程都将数据进行了操作,
问题解决,将if判断时候while循环,让它一致判断,JDK帮助文档明显指出使用if判断可能存在虚假唤醒的问题,应该将if换成while
使用while判断后,4个线程同时执行
//生产者消费者问题,实现线程之间的通信
public class PC {
public static void main(String[] args) {
Date date = new Date();
new Thread(()->{
for (int i = 0; i < 10; i++) {
date.increment();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
date.decrement();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
date.increment();
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
date.decrement();
}
},"D").start();
}
}
class Date{
private int number = 0;
//+1
public synchronized void increment(){
while (number!=0){
//等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//业务
number++;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//唤醒
this.notifyAll();
}
//-1
public synchronized void decrement(){
while (number==0){
//等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//业务
number--;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//唤醒
this.notifyAll();
}
}
修复问题后家庭幸福美满
juc版的生产者和消费者问题
通过Lock找到Condition
代码实现:
package com.hai.providerandcosumer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Muzi
* @version 3.6.3
* @package com.hai.providerandcosumer
* @date 2020/12/7 17:15
* @project juc
**/
public class PC2 {
public static void main(String[] args) {
Date2 date = new Date2();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
date.increment();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
date.decrement();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
date.increment();
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
date.decrement();
}
}, "D").start();
}
}
class Date2 {
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//+1
public void increment() {
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();
}
//唤醒
}
//-1
public void decrement() {
lock.lock();
try {
while (number == 0) {
//等待
condition.await();
}
//业务
number--;
System.out.println(Thread.currentThread().getName() + "==>" + number);
//唤醒
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
运行效果:
运行效果可见,确实是010101运行了,但是我想让他有序ABCD这样运行该怎么做呢??
Condition精准唤醒
代码测试:
package com.hai.providerandcosumer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Muzi
* @version 3.6.3
* @package com.hai.providerandcosumer
* @date 2020/12/7 17:31
* @project juc
**/
public class PC3 {
public static void main(String[] args) {
Date3 date3 = new Date3();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
date3.printA();
}
}, "A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
date3.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
date3.printC();
}
},"C").start();
}
}
class Date3 {
private Lock lock = new ReentrantLock();
//构建三个监视器监视不同的资源
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
Condition conditionC = lock.newCondition();
private int num = 1;//1A,2B,3C
public void printA() {
lock.lock();
try {
while (num != 1) {
//等待
conditionA.await();
}
num = 2;
System.out.println(Thread.currentThread().getName()+"==>"+num);
conditionB.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
while (num != 2) {
conditionB.await();
}
num = 3;
System.out.println(Thread.currentThread().getName()+"==>"+num);
conditionC.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
while (num!=3) {
conditionC.await();
}
num=1;
System.out.println(Thread.currentThread().getName()+"==>"+num);
conditionA.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
运行效果:
5,八锁现象
什么是锁,
锁只会锁两个东西,一个是对象,一个是类的Calss模板
new this 是一个具体的对象
static class 是一个全局唯一的类模板
代码:无
6,集合类不安全
List不安全
代码:
public class ListTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
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();
}
}
}
以上代码看似没什么问题,但是运行时却出现了.ConcurrentModificationException并发修改异常
因此我们可以看出,List在多线程的情况下是不安全的,那么怎样使它变得安全呢
有以下三种解决方案:
1. 使用 new Vector<>();
2. 使用 Collections.synchronizedList(new ArrayList<>());
3. 使用 new CopyOnWriteArrayList<>();
Vector的add方法上了synchronized,解决了并发修改异常,但是加synchronized代表线程需要进行排队,降低了效率
源码:
public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
Collections.synchronizedList(new ArrayList<>())的这是Java提供的一个将list转换为安全集合的方法
new CopyOnWriteArrayList<>() 操作数据的方法是通过Lock锁锁上的
- CopyOnWrite写入时复制,COW计算机设计领域的一种是优化策略
- 在写入的时候避免覆盖,造成数据问题
源码:
public boolean add(E e) { //构建Lock锁 final ReentrantLock lock = this.lock; //加锁 lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { //解锁 lock.unlock(); } }
三种方式均能解决List集合并发问题
Set不安全
跟List同理,在多线程并发情况下,Set同样是不安全的,也同样会抛出并发修改异常
解决方案:
使用Collections工具类转化
Set<String> set = Collections.synchronizedSet(new HashSet<>());
使用写入前复制的CopyOnWriteSet<>()
Set<String> set = new CopyOnWriteArraySet<>();
代码:
public class SetTest {
public static void main(String[] args) {
// Set<String> set = new HashSet<>();
// Set<String> set = new CopyOnWriteArraySet<>();
Set<String> set = Collections.synchronizedSet(new HashSet<>());
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的本质是什么?
本质就是一个HashMap,从源码可以看出,底层就是new了一个HashMap
public HashSet() {
map = new HashMap<>();
}
为什么HashSet存储的值是去重的,因为set的add方法就是调用了Map的put方法,将set的值作为Map的键存进去,而Map的键是不能重复的,HashSet就是将HashMap做了一个封装
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
Map不安全
public class MapTest {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
for (int i = 0; i < 30; i++) {
int finalI = i;
new Thread(()->{
map.put(Thread.currentThread().getName(),String.valueOf(finalI));
System.out.println(map);
}).start();
}
}
}
还是我们熟悉的异常信息
这里的解决方案与之前的List和Set有一个不同
解决方案:
Collections工具类转化
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
使用ConcurrentHashMap
Map<String, String> map = new ConcurrentHashMap<>();
Map<String, String> map = new HashMap<>();等价于什么?
- 在没有任何参数情况下,new HashMap<>()默认的参数有加载因子0.75,默认容量16,通过1>>4位运算得来
- 等价于
Map<String, String> map = new HashMap<>(16,0.75f);
7,Callable
- 可以有返回值
- 可以抛出异常
- 使用call()方法调用
线程代码:
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("call()");
return 1024;
}
}
问题:
线程启动得方法有且仅有一个,就是.start()方法,这个方法归属于Thread类,但是Thread类里只能有Runnable这个参数,那该如何启动我们的线程?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2MGl37pc-1608011313176)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20201208200315123.png)]
解决:通过JDK文档,我们找到了Runnable得实现类Futuretask
而且futureTask的构造函数时可以传入一个Callable线程的,
因此我们可以将futureTask作为媒介类来启动我们的Callable线程
public class CallableTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
FutureTask<Integer> futureTask = new FutureTask<Integer>(myThread);
new Thread(futureTask).start();
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("call()");
return 1024;
}
}
线程完美启动并成功调用call()方法
那么返回值呢?
通过futureTask调用get()方法获取
结果如期而至,但是值得一提的是,这个get方法需要等到call方法结束后才能接收到返回值,如果call方法执行的是比较耗时的任务时,get方法会阻塞,所以一般get方法用在最后一行或者使用异步通信
当多条线程执行时,会发现call方法只执行了一次,由此可以得出,callable的结果为了提高效率是有缓存的
8,常用辅助类
8.1,CountDownLatch(减法计数器)
模拟一个任务完成关机行为
public class CountDownLatchDemo {
public static void main(String[] args) {
for (int i = 1; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"号任务结束");
},String.valueOf(i)).start();
}
System.out.println("线程任务已执行结束,正在关机");
}
}
在不适用CountDownLatch的情况下,可见结果并不理想
使用后:
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch count = new CountDownLatch(5);
for (int i = 1; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"号任务结束");
count.countDown();//计数器-1
},String.valueOf(i)).start();
}
count.await();//等待计数器归零,执行以下代码
System.out.println("线程任务已执行结束,正在关机");
}
}
完成要求
8.2,CyclicBarrier(加法计数器)
官方的话语过于官方,简单来说就是,多少条线程执行结束之后,在执行指定线程
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(5,()->{
System.out.println("车辆已经全部让行,救护车通过前方道路");
});
for (int i = 1; i <= 5; i++) {
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"号车正在让行,塞车中...");
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
结果
8.3,Semaphore(信号量)
一般用于限流,多个共享资源互斥使用等
public class SemaphoreDemo {
public static void main(String[] args) {
//将服务器请求限制为2个
Semaphore semaphore = new Semaphore(2);
//共有10个用户请求我的服务器
for (int i = 0; i < 10; 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 {
//完成后释放,信号量+1
semaphore.release();
}
},String.valueOf(i+1)).start();
}
}
}
效果:
9,读写锁
ReadWruteLock
缓存资源类
class MyCacheLock{
private volatile Map<String,Object> map;
public MyCacheLock(){
map=new HashMap<>();
}
public void put(String k,Object v){
System.out.println(Thread.currentThread().getName()+"写入"+k);
map.put(k, v);
System.out.println(Thread.currentThread().getName()+"写入OK");
}
public void get(String k){
System.out.println(Thread.currentThread().getName()+"读取");
map.get(k);
System.out.println(Thread.currentThread().getName()+"读取OK");
}
}
线程操作结果
在不加锁的情况下,发生了如上情况,使用其他的锁也能达到目的,但是相对使用读写锁来说,更细粒度的控制,从而提高效率
加锁后:
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCacheLock lock = new MyCacheLock();
//十条线程写入
for (int i = 1; i <= 10; i++) {
int finalI = i;
new Thread(()->{
lock.put(finalI +"", finalI +"");
},String.valueOf(i)).start();
}
//读取
for (int i = 1; i <= 10; i++) {
int finalI = i;
new Thread(()->{
lock.get(finalI+"");
},String.valueOf(i)).start();
}
}
}
class MyCacheLock{
private volatile Map<String,Object> map;
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public MyCacheLock(){
map=new HashMap<>();
}
public void put(String k,Object v){
try {
//加写锁
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName()+"写入"+k);
map.put(k, v);
System.out.println(Thread.currentThread().getName()+"写入OK");
} finally {
//解锁
readWriteLock.writeLock().unlock();
}
}
public void get(String k){
try {
//加读锁
readWriteLock.readLock().lock();
System.out.println(Thread.currentThread().getName()+"读取");
map.get(k);
System.out.println(Thread.currentThread().getName()+"读取OK");
} finally {
//解锁
readWriteLock.readLock().unlock();
}
}
}
效果:
- 读锁(共享锁)多个线程可以同方式操作,线程间可以共存,
- 写锁(独占锁)只能有一个线程操作,其他线程需要等待,线程间不能共存
10,阻塞队列
阻塞队列:
BlockingQueue 大家对它其实并不陌生,它和List跟Set一样都是从Collection衍生过来的,
看一下类图
什么时候使用阻塞队列:并发处理,线程池等
学会使用队列
添加,移除
BlockingQueue对应四组API
方式 | 抛出异常 | 不抛异常,有返回值 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(value,time,TimeUnit) |
移除 | remove() | poll() | take() | poll(time,TimeUnit) |
获得首元素 | element() | peek() | … | … |
测试代码:
/**
* 会抛出异常
**/
public static void test1(){
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.add("1");
blockingQueue.add("2");
blockingQueue.add("3");
//获得队列首元素
System.out.println(blockingQueue.element());
//Exception in thread "main" java.lang.IllegalStateException: Queue full 队列满
//blockingQueue.add("1");
System.out.println("==============");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//Exception in thread "main" java.util.NoSuchElementException 队列空
//System.out.println(blockingQueue.remove());
}
/**
* 不会抛出异常,有返回值
**/
public static void test2(){
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("1"));
System.out.println(blockingQueue.offer("2"));
System.out.println(blockingQueue.offer("3"));
System.out.println(blockingQueue.offer("4"));//false
//获得队列的首元素
System.out.println(blockingQueue.peek());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());//null
}
/**
* 阻塞等待,死等
**/
public static void test3() throws InterruptedException {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("1");
blockingQueue.put("2");
blockingQueue.put("3");
//程序阻塞
//blockingQueue.put("4");
System.out.println("======");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
//同样阻塞
//System.out.println(blockingQueue.take());
}
/**
* 超时等待,规定时间内等不到就结束不等了
**/
public static void test4() throws InterruptedException {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("1"));
System.out.println(blockingQueue.offer("2"));
System.out.println(blockingQueue.offer("3"));
//等待2秒放不进去就放弃
System.out.println(blockingQueue.offer("4",2,TimeUnit.SECONDS));
System.out.println("===============");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//等待2秒取不出来就放弃
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
}
- 抛出异常
- 不会抛出异常
- 阻塞等待
- 超时等待
SynchronousQueue 同步队列
没有容量,一次只能放一个,必须等到放进去put的元素取出来take才能放入下一个元素
测试代码:
public class SynchronousQueueDemo {
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "存入" + 1);
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName() + "存入" + 2);
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName() + "存入" + 3);
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "取==>" + synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "取==>" + synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "取==>" + synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "B").start();
}
}
可见在多线程 情况下,必须将元素取出来才能重新存储,类似于我们的semaphore信号量
11,线程池
线程池:三大方法,七大参数四种拒绝策略
池化技术
程序运行的本质,占用系统资源,使用池化技术减少系统资源的消耗,优化系统资源的使用
线程池,连接池,内存池,对象池等。频繁的创建和销毁,十分的浪费系统内资源
池化技术:实现准备好资源,用就来拿,用完就还给我
线程池的好处
减少资源消耗
提高响应速度
管理更加方便
线程服用,可以控制最大并发数,管理线程
线程池:三大方法
//遇强则强,可伸缩的线程池大小
ExecutorService executorService = Executors.newCachedThreadPool();
//单个线程的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
//固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
以上就是线程池创建的三大方法。但是我们发现其底层都是将ThreadPoolExecutor进行了封装,
new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
阿里巴巴开发手册明确指出,不允许使用者三个方法创建线程,我们应该通过ThreadPoolExecutor自定义一个线程池,
源码
线程池启动
使用完记得归还,不然会让程序卡死
public class ExecutorsTest {
public static void main(String[] args) {
// ExecutorService executorService = Executors.newCachedThreadPool();
// ExecutorService executorService = Executors.newSingleThreadExecutor();
ExecutorService executorService = Executors.newFixedThreadPool(5);
try {
for (int i = 0; i < 10; i++) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"号线程执行");
});
}
} finally {
//使用完归还
executorService.shutdown();
}
}
}
七大参数
源码分析:
Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
Executors.newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
Executors.newFixedThreadPool(5);
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
通过者三个方法的线程创建方式我们可以看到,底层都是new了一个ThreadPoolExecutor,那他到底是个什么东西?
本质: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;
}
手动创建一个线程池
ExecutorService executorService = new ThreadPoolExecutor(
2,//核心2个
3,//最大3个
3,//超时3秒释放
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),//BlockingQueue
Executors.defaultThreadFactory(),//使用默认线程工厂
new ThreadPoolExecutor.AbortPolicy()//
);
当队列大小和最大线程大小之和小于了当前线程数时,AbortPolicy()这个决绝策略会抛出异常
四种拒绝策略
AbortPolicy,超出处理数量抛出异常
DiscardPolicy,队列满了,就丢弃任务,不抛出异常
CallerRunsPolicy,谁放进来的谁来处理(main)
DiscardOldestPolicy,队列满了,尝试与最早的任务竞争,也不会抛出异常
如何定义最大线程数(了解,调优)
CPU密集型
根据电脑的CPU核数分配最大的线程池大小,每个电脑配置不一样,CPU核数应该动态获取,而不是写死
ExecutorService executorService = new ThreadPoolExecutor( 2,//核心2个 Runtime.getRuntime().availableProcessors(),//获取设备可用处理器 3,//超时3秒释放 TimeUnit.SECONDS, new LinkedBlockingDeque<>(3),//BlockingQueue Executors.defaultThreadFactory(),//使用默认线程工厂 new ThreadPoolExecutor.AbortPolicy()// );
IO密集型
根据程序大型IO资源消耗的任务开启线程池大小,如果IO资源消耗过大的任务有10个,那我们一般默认开启的线程池的大小时它的两倍
12,四大函数式接口(JDK1.8新特性)
JDK1.8四大新特性:函数式接口,链式编程,Lambda表达式,Stream流式计算
作用:简化编程模型,新框架底层大量应用
函数型接口
public class FunctionDemo {
public static void main(String[] args) {
Function<String, Integer> function = new Function<String, Integer>() {
@Override
public Integer apply(String str) {
return str.isEmpty() ? 1 : 0;
}
};
//lambda
// Function<String, Integer> function =(str)-> str.isEmpty() ? 1 : 0;
System.out.println(function.apply("111"));
}
}
Function,泛型规约参数和返回值
断定型接口
public class PredicateDemo {
public static void main(String[] args) {
// Predicate<String> stringPredicate = new Predicate<String>() {
// @Override
// public boolean test(String str) {
// return str.isEmpty();
// }
// };
//lambda
Predicate<String> stringPredicate = String::isEmpty;
System.out.println(stringPredicate.test("02"));
}
Predicate,泛型规约参数类型,返回值为布尔值,可用做非空工具类判断
消费型接口
public class ConsumerDemo {
public static void main(String[] args) {
// Consumer consumer = new Consumer<String>() {
// @Override
// public void accept(String username) {
// System.out.println(username+"调用消费型接口");
// }
// };
//lambda
Consumer consumer =(username)->{
System.out.println(username+"调用消费型接口");
};
consumer.accept("海金");
}
}
Consumer,泛型规约参数类型,顾名思义消费者,只有支出没有收入,只有参数,没有返回值
供给型接口
public class SupplierDemo {
public static void main(String[] args) {
// Supplier supplier = new Supplier<String>() {
// @Override
// public String get() {
// return null;
// }
// };
//lambda
Supplier supplier = ()-> "供给型接口";
System.out.println(supplier.get());
}
}
Supplier,泛型规约返回值类型,不需要参数,他是个供应商,只负责提供
可以看到都是@since都是1.8,这代表着最早使用到此类的JDK版本,这都是JDK1.8的新特性
13,Stream流式计算
什么是Stream流式计算
大数据时代,本质就是存储和计算,计算都应该交给流来操作
测试代码
public class StreamDemo {
/**
* 题目要求:根据条件查找用户名
* 1.id为偶数
* 2.年龄大于23
* 3.倒序排列
* 4.用户名字转换大写
* 5.只显示一个用户
**/
public static void main(String[] args) {
User user1 = new User("u", 22, 3);
User user2 = new User("v", 12, 7);
User user3 = new User("x", 25, 2);
User user4 = new User("y", 72, 4);
User user5 = new User("z", 13, 9);
//解:
long start = System.currentTimeMillis();
List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
list.stream().filter(u -> u.getId() % 2 == 0)
.filter(u -> u.getAge() > 23)
.map(u -> u.getUsername().toUpperCase())
.sorted((u1,u2)->{ return u2.compareTo(u1);})
.limit(1)
.forEach(System.out::println);//X
long end = System.currentTimeMillis();
System.out.println("流式计算耗时"+(end-start)+"ms");
long start1 = System.currentTimeMillis();
//将多个对象转换为List集合
List<User> list2 = Arrays.asList(user1, user2, user3, user4, user5);
ArrayList<User> users = new ArrayList<>();
for (User user : list2) {
//过滤掉id基数和年龄小于23的
if (user.getId() % 2 == 0 && user.getAge() > 23) {
//将名字转换为大写,并添加到新的List集合,
user.setUsername(user.getUsername().toUpperCase());
users.add(user);
}
}
//因为List集合是有序的,相当于已经给我们自动排好序了,只不过是正序,而我们只需要取一个用户,那就是最后一个
System.out.println(users.get(users.size()-1).getUsername());
long end1 = System.currentTimeMillis();
System.out.println("普通计算耗时"+(end1-start1)+"ms");
}
}
运行结果:少数据量的情况下,流式计算好像没起到什么作用,反而更鸡肋了
14,ForkJoin
在JDK1.7退出,并行执行,提高效率,适合大数据量
什么是ForkJoin
分支合并,将一个任务进行拆分,最后合并结果并返回
ForkJoin特点:工作窃取,当A,B两条线程执行不同任务时,B线程执行完了自己的任务,他会偷A线程没有完成的任务去执行,双端队列,两头都能取
找到它的实现类
测试代码:
public class ForkJoinDemo extends RecursiveTask<Long> {
private final Long startV;
private final Long endV;
public ForkJoinDemo(Long startV, Long endV) {
this.startV = startV;
this.endV = endV;
}
@Override
protected Long compute() {
if ((endV - startV) > 10_0000) {
long middle = (endV + startV) / 2;
ForkJoinDemo task1 = new ForkJoinDemo(startV, middle);
ForkJoinDemo task2 = new ForkJoinDemo(middle+1, endV);
//开始计算
task1.fork();
task2.fork();
//合并结果
return task1.join()+task2.join();
}
long sum= 0L ;
for (Long i = startV; i < endV; i++) {
sum+=i;
}
return sum;
}
}
工作原理就是递归拆分任务,最后将结果合并返回调用者,
- 继承RecursiveTask类,泛型约束任务方法的返回值
- 开始递归调用,自己创建自己
15,异步回调
future设计的初衷就是为了为未来建模
异步回调,,类似于前端的Ajax
public class CompletableFutureDemo {
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");
});
System.out.println("执行");
System.out.println(future.get());//获取结果时会阻塞但是任务是后台执行的,类似于Ajax
//有返回值的
System.out.println(CompletableFuture.supplyAsync(() -> {
int i = 1 / 0;
return 1024;
}).whenComplete((t, u) -> {
System.out.println("u" + u);//u是异常信息
System.out.println("t" + t);//t是返回值
}).exceptionally((e) -> {
e.getMessage();
return 500;//发生错误返回状态码
}).get());
}
}
16,JMM(Java Memory Model)
JMM内存模型,它不是真实存在的,它是一种约定概念
关于JMM的一些同步约定
- 线程加锁前必须把主存中的最新值加载到工作内存中
- 线程解锁前必须把共享变量立刻刷回主存
- 加锁跟解锁是同一把锁
线程操作变量时,并不是直接修改变量的值,而是先将主存的变量加载到自己的工作内存,再提供给执行引擎,最后返还给内存,然后在写入主存中
JMM有八大原子性操作
关于主内存与工作内存之间的交互协议,即一个变量如何从主内存拷贝到工作内存。如何从工作内存同步到主内存中的实现细节。java内存模型定义了8种操作来完成。这8种操作每一种都是原子操作。8种操作如下:
- lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;
- read(读取):作用于主内存,它把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用;
- load(载入):作用于工作内存,它把read操作的值放入工作内存中的变量副本中;
- use(使用):作用于工作内存,它把工作内存中的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;
- assign(赋值):作用于工作内存,它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候,执行该操作;
- store(存储):作用于工作内存,它把工作内存中的一个变量传送给主内存中,以备随后的write操作使用;
- write(写入):作用于主内存,它把store传送值放到主内存中的变量中。
- unlock(解锁):作用于主内存,它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;
Java内存模型还规定了执行上述8种基本操作时必须满足如下规则:
不允许read和load、store和write操作之一单独出现(即不允许一个变量从主存读取了但是工作内存不接受,或者从工作内存发起会写了但是主存不接受的情况),以上两个操作必须按顺序执行,但没有保证必须连续执行,也就是说,read与load之间、store与write之间是可插入其他指令的。
不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
一个新的变量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
一个变量在同一个时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
如果一个变量实现没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。
对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)。
测试:
public class VolatileDemo {
private 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);
}
}
可以看到,程序并没有停止运行,直到我打完这行字,他也没有停止,因为线程拿到的变量一直是0;
问题:当线程A在使用变量时,线程B将变量做出了修改,但是线程A没能收到通知,这个问题该如何解决?volatile
17,Volatile
volatile是什么?
volatile是Java虚拟机提供的轻量级同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
1,保证可见性
public class VolatileDemo {
//不加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);
}
}
2,不保证原子性
public class VDemo02 {
private volatile static int num = 0;
private static void add(){
num++;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){}
//正常结果应该等于20000
System.out.println(Thread.currentThread().getName()+"===>"+num);
}
}
结果应该是20000,但是输出结果却是少于20000,说明了即使添加了volatile关键字,线程修改还是不能保证原子性,还是会被其他线程插入,那应该怎么解决呢?,除了使用synchronized和Lock以外,我们还能使用原子类这个包下的类
为什么一个num++还不能保证原子性呢?,因为在我们看来就是一个+1操作,但是在计算机那里,分为了几个指令,我们可以通过javap -c 查看字节码文件
计算机指令给它分为了四步走。
public class VDemo02 {
private volatile static AtomicInteger num = new AtomicInteger();
private static void add(){
num.getAndIncrement();
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){}
System.out.println(Thread.currentThread().getName()+"===>"+num);
}
}
3,禁止指令重排
在我们写的程序中,计算机并不是直接按照你写的那样去执行的,程序从编译到运行,要经过很多步骤
而在这些步骤中,指令是可能发生的,但是概率极低,并不排除不会发生的可能性
int a = 1; //1
int b = 1; //2
a = a + 2; //3
b = a * a; //4
我们希望程序1234依次执行,但是程序可能会执行:2134,1324等,
但是不可能是4123,因为那样会改变程序的运行结果,处理器在进行指令重排的时候,要考虑数据依赖性的,
volatile是怎么禁止指令重排的呢?
内存屏障,CPU指令,
作用:
1,保证特定的操作的执行顺序
2,可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)
volatile可以保证可见性,但是不能保证原子性,由于内存屏障,很好的解决了指令重排的问题
18,单例模式
饿汉式单例
public class HungryMan {
private HungryMan(){}
private static final HungryMan hungryMan = new HungryMan();
public HungryMan getInstance(){
return hungryMan;
}
}
会存在空间的浪费,但是对象构建速度是很快的
懒汉式单例
public class LazyMan{
private LazyMan(){}
private static LazyMan lazyman;
public static LazyMan getInstance(){
if(lazyman==null){
lazyman = new LazyMan();
}
return lazyman;
}
}
在单线程情况下,普通懒汉式单例是安全的,但是在多线程情况下,可能会创建多个实例,
DCL懒汉式单例
public class LazyMan{
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"ok");
}
//避免指令重排
private volatile static LazyMan lazyman;
//双重检测锁模式
public static LazyMan getInstance(){
if(lazyman==null){
synchronized (LazyMan.class){
if(lazyman==null){
lazyman = new LazyMan();//构建对象时可能会发生指令重排,加上volatile关键字
}
}
}
return lazyman;
}
}
解决了多线程下单例模式失效的问题。
静态内部类单例也能解决:
public class Holder {
private Holder() {
}
public static Holder getInstance() {
return InnerClass.HOLDER;
}
public static class InnerClass {
private static final Holder HOLDER = new Holder();
}
}
单例真的安全吗?反射破坏单例
public class ReflectSingle {
private ReflectSingle(){}
private static ReflectSingle reflectSingle;
public static ReflectSingle getInstance(){
if(reflectSingle==null){
synchronized(ReflectSingle.class){
if(reflectSingle==null){
reflectSingle = new ReflectSingle();
}
}
}
return reflectSingle;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<ReflectSingle> constructor = ReflectSingle.class.getDeclaredConstructor();
constructor.setAccessible(true);
ReflectSingle reflectSingle1 = constructor.newInstance();
ReflectSingle reflectSingle2 = getInstance();
System.out.println(reflectSingle1);
System.out.println(reflectSingle2);
}
}
测试结果:
可见在反射下,我们的单例模式是不安全的,难道真的没有办法保证单例的安全性吗?
枚举
通过反射的newInstance()方法的源码我们发现,枚举是不能被破坏的
@CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
//如果是ENUM就Cannot reflectively create enum objects抛出异常
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
真的不能吗?,我想试试看。。
测试代码:
package com.hai.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle enumSingle=EnumSingle.INSTANCE;
Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor();
constructor.setAccessible(true);
EnumSingle enumSingle1 = constructor.newInstance();
System.out.println(enumSingle);
System.out.println(enumSingle1);
}
}
通过编译的.class字节码文件还原后,发现有个无参构造器
但是通过反射抛出的异常却告诉我们没有无参的构造器,而不是我们预想中的Cannot reflectively create enum objects这个异常信息
好像IDEA骗了我,通过javap反编译后
那为什么??通过jad将class转换为jad源码
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingle.java
package com.hai.single;
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/hai/single/EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstance()
{
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}
存在的构造器确实不是无参的,而是带着两个参数
那我们将这两个参数传入
结果抛出了newInstance()方法预先声明的异常,说明枚举确实是不能被反射破坏的,我们也因此得知了枚举没有无参构造,而是有两个参数的有参构造
19,CAS理解
CAS比较并交换
public class CASTest {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2000);
//CAS比较并交换
//如果是期望值,那么就更新并返回true,如果不是就返回false
System.out.println(atomicInteger.compareAndSet(2000, 2002));
System.out.println(atomicInteger);
System.out.println(atomicInteger.compareAndSet(2000, 2002));
System.out.println(atomicInteger);
}
}
结果
源码:
直接修改内存中的值,是一个循环判断内存偏移量,自旋锁
CAS:比较当前工作内存中的值和主存中的值,如果是期望值,就执行操作,不是就一直循环
缺点:一次性只能保证一个共享变量的原子性,循环比较耗时,存在ABA问题
ABA问题
20,原子引用
解决ABA问题
带版本号的原子引用,类似于乐观锁
原子引用解决ABA问题
public class ABADemo {
public static void main(String[] args) {
AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(10,1);
new Thread(()->{
System.out.println("A::"+reference.getStamp());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
reference.compareAndSet(10, 11, reference.getStamp(), reference.getStamp() + 1);
System.out.println("A:::"+reference.getStamp());
System.out.println(reference.compareAndSet(11, 10, reference.getStamp(), reference.getStamp() + 1));
},"A").start();
new Thread(()->{
int stamp = reference.getStamp();
System.out.println("B::"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(reference.compareAndSet(10, 11, stamp, reference.getStamp() + 1));
System.out.println("B:::"+reference.getStamp());
System.out.println("reference = " + reference.getReference());
},"B").start();
}
}
运行结果:
可见A线程将10改成了11,又把11,改回了10,但是它的版本号也随之增加,当B线程拿到10时,检查了版本号的变化,发现版本号和与其不同,发现这个10是被人修改过的,是的,它不纯洁了,B线程发现之后,对它很失望,于是绝对不对它的值进行修改,直接返回一个false,此原子引用解决了ABA问题,貌似很简单
21, 各种锁
1,可重入锁
可重入锁(递归锁)
代码测试:
synchronized版
public class LockDemo {
public static void main(String[] args) {
MyLock lock = new MyLock();
new Thread(lock::call,"A").start();
new Thread(lock::call,"B").start();
}
}
class MyLock{
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"call");
sms();
}
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+"sms");
}
}
结果:
call()方法在调用sms()方法时,也可以获得sms()方法的锁,因为他们存在包含关系,可重入的
Lock版
public class LockDemo02 {
public static void main(String[] args) {
MyLock2 lock = new MyLock2();
new Thread(lock::call, "A").start();
new Thread(lock::call, "B").start();
}
}
class MyLock2 {
Lock lock = new ReentrantLock();
public void call() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"call");
sms();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void sms() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "sms");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
Lock锁注意加锁和解锁,必须成对出现,否则可能造成死锁现象
2,公平锁/非公平锁
公平锁:很公平,不能插队执行
非公平锁:不公平,可以插队执行,(默认都是非公平锁)
实现方式:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
3,自旋锁
使用cas定义一个锁
public class SpinLock {
AtomicReference<Thread> threadAtomicReference = new AtomicReference<>();
public void lock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"===Lock");
while (threadAtomicReference.compareAndSet(null,thread)){
}
}
public void unLock(){
Thread thread = Thread.currentThread();
threadAtomicReference.compareAndSet(thread,null);
System.out.println(thread.getName()+"===unLock");
}
}
测试:
class Test{
public static void main(String[] args) {
SpinLock lock = new SpinLock();
new Thread(()->{
lock.lock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unLock();
}
},"A").start();
new Thread(()->{
lock.lock();
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unLock();
}
},"B").start();
}
}
结果:
A解锁后B才能解锁
4,死锁
产生死锁的情况
public class DeadLock {
public static void main(String[] args) {
new Thread(new MyThread("lock-A","lock-B"),"线程1").start();
new Thread(new MyThread("lock-B","lock-A"),"线程2").start();
}
}
class MyThread implements Runnable {
String lockA;
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() +
",得到锁:" + lockA + ",get:" + lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() +
",得到锁:" + lockB + ",get:" + lockA);
}
}
}
}
发生死锁
死锁排查jps-l
D:\T248\juc>jps -l
58976 com.hai.lock.DeadLock
68144 org.jetbrains.jps.cmdline.Launcher
81632 org.jetbrains.jps.cmdline.Launcher
7892 sun.tools.jps.Jps
90708 org.jetbrains.jps.cmdline.Launcher
50552
查看进程堆栈信息jstack 进程号
"线程2":
at com.hai.lock.MyThread.run(DeadLock.java:39)
- waiting to lock <0x00000000d670cc90> (a java.lang.String)
- locked <0x00000000d670ccc8> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
"线程1":
at com.hai.lock.MyThread.run(DeadLock.java:39)
- waiting to lock <0x00000000d670ccc8> (a java.lang.String)
- locked <0x00000000d670cc90> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
相关代码笔记:GitHub代码仓库