JUC编程
与JUC编程相关的包
一.线程和进程
一个进程往往包含多个线程,至少包含一个线程
java默认有几个线程?
mian线程和GC线程(垃圾回收)
对于java而言:Thread、Runnable、Callable
一般是他们来开启进程
java真的能开启线程么?
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 */
}
}
}
//调用start0 native是本地方法,需要去调用C++操作硬件的,java实现不了
private native void start0();
并行、并发
并发编程:并行、并发
并发(多线程操作同一个资源)
·CPU一核,模拟出来多条线程,快速交替
并行(多个人一起行走)
·CPU多核,多个线程可以同时进行
public class Test1 {
public static void main(String[] args) {
//获取CPU的核数
//CPU密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
并发编程的本质:充分利用CPU的资源
线程的几个状态
public enum State {
//新建
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
wait/sleep区别
1.来自不同的类
wait=》Object
sleep=》Thread
2.关于锁的释放
wait回释放锁的,而sleep是不会释放的
3.使用的范围不同
wait:必须在同步代码块中
sleep:可以在任何地方使用
4.是否需要捕获异常
wait不需要捕获异常
sleep必须要捕获异常(因为他可能会有超时等待)
二.Lock锁
传统的Synchronized
public class SaleTicketDemo01 {
public static void main(String[] args) {
//并发,多线程操作同一个资源类
Ticket ticket = new Ticket();
new Thread(()->{
for (int i=0;i<60;i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i=0;i<60;i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i=0;i<60;i++) {
ticket.sale();
}
},"C").start();
}
}
//资源类 OOP
class Ticket{
//属性,方法
private int number = 50;
//买票的方式
public synchronized void sale(){
if (number>0)
{
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,还剩"+number+"张");
}
}
}
如果我们的买票方法没有synchronized修饰,则会出乱,没有顺序(synchronized也是非公平锁)
Lock接口
公平锁:可以先来后到:必须排队
非公平锁:不公平,可以插队(默认使用非公平锁)
public class SaleTicketDemo02 {
public static void main(String[] args) {
//并发,多线程操作同一个资源类
Ticket2 ticket = new Ticket2();
new Thread(()->{ for (int i=0;i<60;i++) ticket.sale(); },"A").start();
new Thread(()->{
for (int i=0;i<60;i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i=0;i<60;i++) {
ticket.sale();
}
},"C").start();
}
}
//lock3部曲
//1.new ReentrantLock()
//2.lock.lock()加锁
//3.finally { //解锁 lock.unlock(); }
//资源类 OOP
class Ticket2{
//属性,方法
private int number = 50;
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
Synchronized和Lock区别
1.Synchroized是一个内置的Java关键字,Lock是一个类
2.Synchroized无法判断读取锁的状态,Lock可以判断是否获取到锁
3.Synchroized会自动释放锁,lock必须手动释放锁,如果不释放锁,就会死锁
4.Synchroized 线程1(获得锁:阻塞)、线程2(等待锁,傻等),而lock锁不会傻等(lock.trylock()尝试获取锁)
5.Synchroized 可重入锁,不可中断的,非公平锁;
lock锁,也是可重入锁,可以判断锁状态,可以自己设置是非公平锁还是公平锁
6.Synchroized 适合锁少量的代码同步问题,Lock锁适合锁大量的同步代码
什么是锁,如何判断锁的是谁?
三.生产者和消费者问题
synchronized版本
package com.hkd.pc;
/**
* @author 王庆华
* @version 1.0
* @date 2020/11/13 11:50
* @Description 线程之间的通信问题:生产者和消费者问题 等待唤醒,通知唤醒
* @pojectname 线程交替执行 A B 操作同一个变量 num = 0
* A num+1 B num-1
*/
public class A {
public static void main(String[] args) {
Date date = new Date();
new Thread(()->{
for (int i=0;i<10;i++)
{
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i=0;i<10;i++)
{
try {
date.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
//判断等待,通知,业务
class Date{
public int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
if (number!=0) {
//等待
this.wait();
}
number++;
//通知其他线程,我+1完毕了
System.out.println(Thread.currentThread().getName()+"=>"+number);
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if (number==0)
{
//等待
this.wait();
}
number--;
//通知其他线程我-1完毕了
System.out.println(Thread.currentThread().getName()+"=>"+number);
this.notifyAll();
}
}
问题:如果存在A B C D四个线程呢,线程还安全么
虚假唤醒问题
if改为while判断
即可解决虚假唤醒问题
JUC版本
-
class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } }
代码实现:
package com.hkd.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 王庆华
* @version 1.0
* @date 2020/11/13 12:14
* @Description TODO
* @pojectname 线程相关
*/
public class B {
public static void main(String[] args) {
Date date = new Date();
new Thread(()->{
for (int i=0;i<10;i++)
{
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i=0;i<10;i++)
{
try {
date.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i=0;i<10;i++)
{
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i=0;i<10;i++)
{
try {
date.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//判断等待,通知,业务
class Date1{
public int number = 0;
//+1
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void increment() throws InterruptedException {
lock.lock();
try {
while (number!=0) {
//等待
condition.await();//等待
}
number++;
//通知其他线程,我+1完毕了
System.out.println(Thread.currentThread().getName()+"=>"+number);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number==0) {
//等待
condition.await();//等待
}
number--;
//通知其他线程,我-1完毕了
System.out.println(Thread.currentThread().getName()+"=>"+number);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
任何一个新技术,绝不是仅仅只是覆盖了原来的技术,而是优势和补充
问题:
不是ABCD的顺序
Condition精准的通知和唤醒技术
有序性
代码测试
package com.hkd.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 王庆华
* @version 1.0
* @date 2020/11/13 19:12
* @Description TODO
* @pojectname 线程相关
*/
public class C {
public static void main(String[] args) {
Data3 data = new Data3();
//A执行完 调用B,B执行完调用C ,C执行完调用A
new Thread(()->{
for (int i =0;i<10;i++)
{
data.printA();
}
},"A").start();
new Thread(()->{
for (int i =0;i<10;i++)
{
data.printB();
}
},"B").start();
new Thread(()->{
for (int i =0;i<10;i++)
{
data.printC();
}
},"C").start();
}
}
//资源类 lock锁
class Data3{
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number=1;//1 A 2B 3C
public void printA(){
lock.lock();
try {
//业务 判断 执行
while (number!=1)
{
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"=>AAA");
//唤醒,唤醒指定的人
number=2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
//业务 判断 执行
while (number!=2)
{
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"=>AAA");
//唤醒,唤醒指定的人
number=3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
//业务 判断 执行
while (number!=3)
{
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"=>AAA");
//唤醒,唤醒指定的人
number=1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
保证了我们的有序性
四.八锁现象
对象、Class
情况一
synchronized关键字
锁的对象是方法的调用者
package com.hkd.lock8;
import java.util.concurrent.TimeUnit;
/**
* @author 王庆华
* @version 1.0
* @date 2020/11/13 19:28
* @Description TODO
* //1.标准情况下:先打印是发短信还是打电话?--------》发短信
* 2.我们发短信业务中暂停3S,现在先打印是发短信还是打电话?-----》发短信
* 因为sendSms和call用的是synchronized关键字 锁的对象是方法的调用者
* 两个方法是一个对象,用的同一把锁,谁先拿到谁先执行
* @pojectname 线程相关
*/
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{phone.sendSms();},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{phone.call();},"B").start();
}
}
class Phone{
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信snedSms");
}
public synchronized void call(){
System.out.println("call");
}
}
增加一个普通方法呢?
Phone2 phone = new Phone2();
new Thread(()->{phone.sendSms();},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{phone.hello();},"B").start();
public void hello(){
System.out.println("hello hello");
}
我们发现1S过后hello方法先执行
因为普通方法不受锁的影响
情况二
两个对象,两个同步方法
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(()->{phone1.sendSms();},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{phone2.call();},"B").start();
class Phone2{
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信snedSms");
}
public synchronized void call(){
System.out.println("call");
}
}
call先打印,这个时候就跟延迟有关了,因为锁的对象不一样,谁快先调用谁
情况三
方法变成静态同步方法(static修饰)
public class Test3 {
public static void main(String[] args) throws InterruptedException {
Phone3 phone = new Phone3();
new Thread(()->{phone.sendSms();},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{phone.call();},"B").start();
}
}
class Phone3{
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信snedSms");
}
public static synchronized void call(){
System.out.println("call");
}
}
我们发现发短信先打印
原因:
**1.**synchronized关键字锁的对象是方法的调用者,同一个对象,谁先拿到谁执行
**2.**static : 静态方法 类一加载就有了 是一个Class 模板
Phone3只有唯一的一个class对象 -–-–> Class phone3Class = Phone3.class;,因此,我们这个地方锁的的class,全局唯一,用的肯定是同一个锁
如:
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{
phone1.sendSms();},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone2.call();},"B").start();
还是发短信先打印,也就印证了我们的Static修饰的同步方法,锁的就是Class,一定是谁先拿到谁先执行
情况四
一个对象+静态同步方法+普通同步方法
public class Test4 {
public static void main(String[] args) throws InterruptedException {
Phone4 phone = new Phone4();
new Thread(()->{
phone.sendSms();},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.call();},"B").start();
}
}
class Phone4{
//静态同步方法
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信snedSms");
}
//普通同步方法
public synchronized void call(){
System.out.println("call");
}
}
打电话先输出
原因:
锁的对象不同,前者锁的是Class类模板,后者锁的调用者
由于前者有延迟,锁对象不一样,后者不需要等解锁
两个对象+静态同步方法+普通同步方法
Phone4 phone = new Phone4();
Phone4 phone1 = new Phone4();
new Thread(()->{
phone.sendSms();},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone1.call();},"B").start();
谁没有延迟(谁的低),谁先执行,
锁的对象不同,前者锁的是Class类模板,后者锁的调用者
由于前者有延迟,锁对象不一样,后者不需要等解锁
总结:
一个是new出来的对象 ,是一个具体的实例,可以多个同一类型的实例,那么锁就有可能不一样了
一个是Class类模板,锁的是这个类型的对象模板,Class模板全局唯一,锁的对象一定一样
五.集合类不安全
List不安全
代码
public class ListTest {
public static void main(String[] args) {
List<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();
}
}
}
错误:
Exception in thread “0” Exception in thread “4” Exception in thread “2” java.util.ConcurrentModificationException
并发修改异常
我们发现并发下List是不安全的
解决方案
1.使用Vector<>()
List<String> list = new Vector<>();
为什么安全呢?
我们的Vector源码中,官方在添加元素的时候给我们写了synchronized修饰add方法(比ArrayList早出来)
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
2.List顶层老大Collections工具类
List<String> list = Collections.synchronizedList(new ArrayList<>());
把我们的List变成线程安全
3.CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>();
//CopyOnWriteArrayList源码
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
private transient volatile Object[] array;
CopyOnWrite 写入时赋值
COW计算机程序设计领域的一种优化策略
多个线程调用的时候,list,读取的时候是固定的,写入的时候(覆盖操作)在写入的时候避免覆盖,造成数据问题
优点:
我们的Vector的add方法是synchronized来修饰了,效率比较低
我们再来看看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();
}
}
我们可以看出,CopyOnWriteArrayList是用lock锁,效率高
Set不安全
代码:
public class SetTest {
public static void main(String[] args) {
HashSet<String> set = 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();
}
}
}
同理错误类型为:java.util.ConcurrentModificationException
解决方案
1.Collections工具类
Set<String> set = Collections.synchronizedSet(new HashSet<>());
2.CopyOnWriteSet
Set<String> set = new CopyOnWriteArraySet<>();
HashSet底层源码
HashSet就是一个HashMap
public HashSet() {
map = new HashMap<>();
}
//HashSet的add方法
//set本质就是map的key,key是唯一的,所以set是无序的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();//常量
HashMap不安全
//map是这样用的么,
//默认等价于什么
Map<String,String> map = new HashMap<>();
Map<String,String> map = new HashMap<>(16,0.75);
Map源码
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 位运算16
static final int MAXIMUM_CAPACITY = 1 << 30;
static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认加载因子
//初始容量
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
//加载因子
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
代码
public class MapTest {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
for(int i=0;i<=60;i++)
{
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
也会有并发修改错误
解决方案
1.Collections工具类
Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
2.ConcurrentHashMap
Map<String,String> map = new ConcurrentHashMap<>();
//ConcurrentHashMap源码
public ConcurrentHashMap() {
}
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
六.Callable
1.可以有返回值
2.可以抛出异常
3.方法不同,run()/start
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
函数式接口
我们发现函数泛型就是我们返回值的类型,泛型写那种类型,我们就返回那种类型
那么我们如何启动Callable呢?
我们看源码发现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2dwA682L-1605609859700)(C:\Users\王庆华\AppData\Roaming\Typora\typora-user-images\image-20201114184939059.png)]
Thread的构造函数重载都是Runable类型
那么Callable怎么跟Thread搭上关系
package com.hkd.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author 王庆华
* @version 1.0
* @date 2020/11/14 18:45
* @Description TODO
* @pojectname 线程相关
*/
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//怎么启动callable
//new Thread(new Runnable()).start();
// new Thread(new FutureTask<>(Callable)).start();;
new Thread().start();
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread);
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();//结果会被缓存,效率高
Object o = futureTask.get();
System.out.println(o);
}
}
class MyThread implements Callable<String>{
@Override
public String call() throws Exception {
return "1234";
}
}
细节:
1.有缓存的
2.结果可能需要等待,会阻塞
七.JUC常用的辅助类
1.CountDownLatch
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//总数是6,必须要执行任务的时候,再使用
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i=1;i<=6;i++)
{
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"走了");
//倒计时完毕 减一
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//等待计数器归零然后在向下执行
countDownLatch.await();
System.out.println("关门");
}
}
原理:
countDownLatch.countDown(); :数量减一
countDownLatch.await();:等待计数器归零,然后在向下执行
每次有线程调用,他就会减一,当计数器为0的时候,await就会被唤醒,然后执行
2.CyclicBarrier
加法计数器
一个只是计数,一个是计数完,调用线程
public class CyclicBarrierTest {
//集齐七颗龙珠,召唤龙珠
//召唤龙珠的线程
public static void main(String[] args) {
CyclicBarrier cyclicBarrier =new CyclicBarrier(7,()->{
System.out.println("召唤神龙");
});
for(int i=1 ;i<=7;i++ )
{
final int temp = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集第"+temp+"颗龙珠");
try {
cyclicBarrier.await();//等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
我们会在循环的时候不停的调用线程收集龙珠,直到7颗龙珠,也就是7个线程调用完,才会执行下面的操作
如果我们
new CyclicBarrier(8,()->{
System.out.println(“召唤神龙”);
});
然而我们的for还是7,那么他就会一直等待,收集满8个才会向下执行,这个时候就卡死到这了
3.Semaphore
Semaphore:信号量
6个汽车,三个车位 123进 456等 3走了可能4要进来
这个int可以理解为线程数量(停车位)
public class SemaphoreDemo {
//限流
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for(int i=1;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();
}
},String.valueOf(i)).start();
}
}
}
原理:
**acquire()😗*获得,假如已经满了,其他的就会等待,等到释放为止
**release():**释放,会将当前的信号量释放+1,然后唤醒等待的线程
作用:
多个共享资源互斥的使用!并发限流,控制我们的最大线程数!!
八.读写锁
也叫作: 独占锁:写锁 一次只能被一个线程占有
共享锁:读锁 一次可以被多个线程占用
读的时候可以被多线程同时读
写的时候,只能有一个线程去写
代码
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 volatile 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()+"写入完毕");
}
//取 读
public void get(String key){
System.out.println(Thread.currentThread().getName()+"读取数据"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取数据OK"+key);
}
}
问题
解决办法
将我们的自定义缓存加读写锁
class MyCacheLock{
private volatile Map<String,Object> map = new HashMap<>();
private ReadWriteLock lock = new ReentrantReadWriteLock();
//存 写
public void put(String key, Object value){
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入数据"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入完毕");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
//取 读
public void get(String key){
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取数据"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取数据OK"+key);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
}
小结
读——读 可以共存
读——写 不可以共存的
写——写 不能共存的
九.阻塞队列
阻塞队列:
BlockingQueue
使用场景
多线程并发处理,线程池
四组API
方式 | 抛出异常 | 不会抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(,) |
移除 | remove() | poll() | take() | poll(,) |
判断首尾 | element() | pick() | - | - |
1.抛出异常
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D3V0DQxc-1605609859710)(C:\Users\王庆华\AppData\Roaming\Typora\typora-user-images\image-20201114204944870.png)]
public boolean add(E e) {
return super.add(e);
}
源码中队列的add方法是返回一个boolean值,添加成功就为true,错误为false
错误类型:java.lang.IllegalStateException: Queue full
说明队列已满
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
源码remove是不用传任何参数的。返回值是刚才我们弹出的元素
public E element() {
E x = peek();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
正常情况下:
public static void test1(){
//队列大小
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.add("a"));
System.out.println(arrayBlockingQueue.add("b"));
System.out.println(arrayBlockingQueue.add("c"));
System.out.println("===========");
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
}
这时候队列已经没有元素的,如果我们再去取,就会报错
同样的没有元素的情况下,判断队首元素也会是这个错误
Exception in thread “main” java.util.NoSuchElementException:没有元素了
2.不会抛出异常
队满返回false,队空取元素以及判断队首为null。
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
不同点:
我们可以看出,相对于抛出错误来说,不抛错误的方法中都加锁了
相同点:
添加元素方法都有返回值
成功为true
3.阻塞等待
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
这个方法是没有返回值的,我们可以发现,在加锁的基础上,我们的这个方法调用了await()方法,也就是说,队列满的情况下,我们会一直等,程序会卡死
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
同理取出元素也是如此
4.超时等待
arrayBlockingQueue.offer("d",2, TimeUnit.SECONDS);
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
原理:
添加一个元素,如果队满,就等待参数规定的时间,如果还是满,就返回false
同理,取出也是如此
超出我们的时间取不出来就会爆出null,程序结束
arrayBlockingQueue.poll(2,TimeUnit.SECONDS);
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock()
}
}
SynchronousQueue同步队列
进去一个元素,必须等取出来之后,才能再往里面放一个元素
即最多只等放一个元素
put(),take()
public class SyschronousQueneDemo {
public static void main(String[] args) {
BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"放PUT 1");
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName()+"放PUT 2");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName()+"放PUT 3");
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
十.线程池
三大方法,七大参数,四种拒绝策略
池化技术
程序运行的本质:占用系统资源,===》优化CPU资源使用
创建or销毁(关闭)非常消耗资源
解决办法就是池化技术:eg 线程池,内存池,连接池,对象池
事先准备好一些资源,有人要用,就来这个池中拿,用完之后
好处
1.降低资源消耗
2.提高响应的速度
3.方便管理
线程复用,可以控制最大并发数,管理线程
三大方法
阿里巴巴开发手册规定
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明: Executors 返回的线程池对象 的弊端 如下
1 FixedThreadPool 和 SingleThread Pool
允许的请求队列长度为 Integer.MAX_VALUE,可 能会堆积大量的请求,从而导致 OOM。
2 CachedThreadPool 和 ScheduledThreadPool
允许的创建线程数量 为 Integer.MAX_VALUE 可能会创建大量的线程,从而导致 OOM。
1.单个线程池
Executors.newSingleThreadExecutor()
//单个线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
for (int i=0;i<10;i++)
{
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"OK");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
即使我们通过for调用再多的线程方法,始终用的都是线程池中的一个线程在工作
2.固定大小的线程池
Executors.newFixedThreadPool(5)
//创建一个固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
try {
for (int i=0;i<10;i++)
{
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"OK");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
我们创建线程池的时候,给定的参数就是线程池中线程存在数量的最大值
即使我们通过for循环创建调用多次,始终都是5个线程在工作
3.可伸缩的线程池
Executors.newCachedThreadPool();
// //可伸缩的线程池
ExecutorService executorService = Executors.newCachedThreadPool();
try {
for (int i=0;i<100;i++)
{
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"OK");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
字面意思,我们可以通过for来控制,for中调用线程次数多,那线程池中线程的个数就会多,线程池创建线程个数,跟for循环次数有关,但是不一定,因为还跟CPU有关,比如for是1000个,我们的线程不一定是1000,有个能100都不到
七大参数
三大方法的源码分析
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>());
}
我们发现这里的源码都是
new ThreadPoolExecutor
他的源码:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
//调用了this方法
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize:核心线程池大小
maximumPoolSize:最大核心线程池大小
keepAliveTime:超时多久没人调用就会释放
TimeUnit unit:超时单位
BlockingQueue workQueue:
阻塞队列
**ThreadFactory threadFactory:**线程工程,一般不用动
RejectedExecutionHandler handler:拒绝策略
看完这7个参数,我们回头看下我们的三大方法的源码
发现我们的前两个方法是基本相同的,固定大小的线程池方法,只是传递了一个参数,用来代替单个线程池中的那个1
而最后一个默认的核心线程数是0,但是最大核心线程数为
Integer.MAX_VALUE,约为21亿,这就是为什么消耗资源
手动创建线程池
keepAliveTime:超时多久没人调用就会释放,在这里的意思就是:345号窗口没人了,等了1个小时也没人来,那我就关了,只留我的核心线程
代码
package com.hkd.pool;
import java.util.concurrent.*;
/**
* @author 王庆华
* @version 1.0
* @date 2020/11/14 23:39
* @Description TODO
* @pojectname 线程相关
*/
public class Demo {
public static void main(String[] args) {
// Executors.newSingleThreadExecutor();
//自定义线程池
ExecutorService threadPoolExecutor = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
//new ThreadPoolExecutor.AbortPolicy()//银行满了,还有人进来,不处理这个人了,抛出异常
//new ThreadPoolExecutor.CallerRunsPolicy()//哪来的去哪里
// new ThreadPoolExecutor.DiscardPolicy()//队列满了,丢掉任务,不会抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy()//队列满了,尝试第一个线程竞争,如果竞争成功则会正常进行,失败,丢掉任务,也不会爆出异常
);
try {
for (int i=1;i<=9;i++)
{
threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+"OK");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPoolExecutor.shutdown();
}
}
}
四种拒绝策略
默认的拒绝策略
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
AbortPolicy()
银行满了,还有人进来,不处理这个人了,抛出异常
java.util.concurrent.RejectedExecutionException:线程池满了,报错
CallerRunsPolicy()
哪来的去哪里,这里我们是main函数,所以他会回去让main线程代理执行他这个任务
DiscardPolicy
队列满了,丢掉任务,不会抛出异常
DiscardOldestPolicy
队列满了,尝试第一个线程竞争,如果竞争成功则会正常进行,失败,丢掉任务,也不会爆出异常
最大承载数
是我们new LinkedBlockingDeque<>(3)这个阻塞队列的大小+maximumPoolSize
上面的例子就是3+5 = 8
手写的代码分析
如果我们的for中只是调用一个threadPoolExecutor.execute那么,线程池中就只有一个线程工作
i=3时
线程池中有两个线程在工作,也就是我们的核心线程池数corePoolSize
直到i=5,我们的线程池中还是只有两个线程在工作
i=6时,超出我们的阻塞队列和核心线程数之和,就要启动最大核心线程数中的线程了,这个时候,我们工作的线程数变成了3个
同理i=7时 工作的线程数变成了4个
i=8时,达到了我们最大核心线程数5个,
在大,就跟拒绝策略有关了,参照上面的4种拒绝服务
CPU密集型和IO密集型
我们最大核心线程数设置为多少合适呢?
我们在用ThreadPoolExecutor创建线程池的时候,我们的最大核心线程数规定有以下两种
CPU密集型
根据我们的电脑(服务器)中处理器是几核的,规定最大核心线程数就为几,保持我们的CPU效率最高不能写死,要动态获取核数
Runtime.getRuntime().availableProcessors()
IO密集型
定义的最大核心线程数 **>**程序中十分耗IO的线程数
一般我们按照2倍规定
十一.四大函数式接口
只有一个方法的接口
例如foreach(参数),他的参数就是一个消费者函数接口
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
Function函数型接口
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
有一个输入参数,有一个输出
只要是函数式接口,就可以用Lambda表达式简化
public static void main(String[] args) {
Function<String,String> function = (str)->{return str;};
System.out.println(function.apply("sfsa"));
}
Predicate断定型接口
只有一个输入参数,输出类型始终为boolean类型
public interface Predicate<T> {
boolean test(T t);
public static void main(String[] args) {
Predicate<String> predicate = (str)->{
return str.isEmpty();
};
System.out.println(predicate.test("dsda"));
}
Consumer消费型接口
消费型接口,只有输入,没有返回值
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
public static void main(String[] args) {
Consumer<String> consumer =(str)->{
System.out.println(str);
};
consumer.accept("sadsa");
}
打印字符串
Supplier供给型接口
没有参数,只有返回值
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
public static void main(String[] args) {
Supplier<Integer> supplier = ()->{
// System.out.println("get");
return 1024;
};
System.out.println(supplier.get());
}
十二.Strean流式计算
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-03fWcO0A-1605609859715)(C:\Users\王庆华\AppData\Roaming\Typora\typora-user-images\image-20201116161315974.png)]
我们用一到题来理解流式计算
题目要求
现有五个用户!筛选
- 1、ID必须是偶数
- 2、年龄必须大于23岁
- 3.用户名转为大写字母
- 4、用户名字母倒者排序
- 5、只输出一个用户!
public class Test {
public static void main(String[] args) {
User u1 = new User(1,"a",21);
User u2 = new User(2,"b",22);
User u3 = new User(3,"c",23);
User u4= new User(4,"d",24);
User u5 = new User(6,"e",25);
List<User> list = Arrays.asList(u1,u2,u3,u4,u5);
list.stream()
.filter(u->{return u.getId()%2==0;})//获得id为偶数
.filter(u->{return u.getAge()>23;})//年龄大于23的用户
.map(u->{return u.getName().toUpperCase();})//转换大写
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})//逆序
.limit(1)//分页
.forEach(System.out::println);
}
}
十三.ForkJoin
什么是ForkJoin?====》分支合并
ForkJoin在jdk1.7之后推出
ForkJoin特点
工作窃取
B执行完了回去偷A的任务,提高效率,当然这里维护的是双端队列
使用步骤
1.forkjoinpool通过它来执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kr1Q5Hp4-1605609859718)(C:\Users\王庆华\AppData\Roaming\Typora\typora-user-images\image-20201116163752230.png)]
2.计算任务
创建我们的ForkJoinTask
测试类
package com.hkd.forkjoin;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
/**
* @author 王庆华
* @version 1.0
* @date 2020/11/16 16:47
* @Description TODO
* @pojectname 线程相关
*/
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// test1();//499999999500000000求和时间为7200
test2();//求和时间为6129
test3();//500000000500000000求和时间为231
}
public static void test1(){
Long sum=0L;
long start = System.currentTimeMillis();
for (Long i=1L;i<10_0000_0000;i++)
{
sum+=i;
}
long end = System.currentTimeMillis();
System.out.println(sum+"求和时间为"+(end-start));
}
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> forkJoinDemo = new ForkJoinDemo(0L,10_0000_0000L);
// forkJoinPool.execute(forkJoinDemo);//执行 没有结果
ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoinDemo);//提交 有结果
submit.get();
long end = System.currentTimeMillis();
System.out.println("求和时间为"+(end-start));
}
public static void test3() {
long start = System.currentTimeMillis();
long sum;
sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println(sum + "求和时间为" + (end - start));
}
}
计算任务
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start;
private Long end;
//临界值
private Long temp = 10000L;
public ForkJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
//计算方法
@Override
protected Long compute() {
if ((end-start)<temp)
{
Long sum =0L;
for (Long i=start;i<=end;i++)
{
sum+=i;
}
return sum;
}else
{
long middle = (start+end)/2;
ForkJoinDemo forkJoin = new ForkJoinDemo(start,middle);
forkJoin.fork();//拆分任务,把任务亚入线程队列
//分支合并计算
ForkJoinDemo forkJoin2 = new ForkJoinDemo(middle + 1, end);
forkJoin2.fork();
return forkJoin.join()+forkJoin2.join();
}
}
}
十四.异步回调
类似于我们客户端与服务器的异步通信Ajax
没有返回值的异步回调
public static void main(String[] args) throws ExecutionException, InterruptedException {
//没有返回值的runAsync异步回调
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"runAsync=>void");
});
System.out.println("11111");
completableFuture.get();
}
我们虽然一开始就执行了异步回调,但是我们没有立马获取,而是走的主线程的输出,其实这个时候我们已经获得我们的异步回调中的信息了,只是没输出,如果我们的异步回调没有设置延迟的话,他在11111输出后就会立马输出
有返回值的异步回调
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
System.out.println("t=>" + t);
System.out.println("u=>" + u);
}).exceptionally((e) -> {
System.out.println(e.getMessage());
return 233;
}).get());
}
}
n这个参数是什么呢,我们在线程中,写一个错误语句来看
可以看出,我们的t返回的是详细的错误信息
whenComplete源码
public CompletableFuture<T> whenComplete(
BiConsumer<? super T, ? super Throwable> action) {
return uniWhenCompleteStage(null, action);
}
它调用了一个BiConsumer,
@FunctionalInterface
public interface BiConsumer<T, U> {
/**
* Performs this operation on the given arguments.
*
* @param t the first input argument
* @param u the second input argument
*/
void accept(T t, U u);
还是一个消费型接口,只不过这次传参是两个
十五.JMM
什么是JMM
JMM是一个java内存模型,是一个概念模型,约定
JMM同步的约定
1.线程解锁前,必须把共享变量立刻刷回主存
2.线程加锁前,必须读取主存中的最新值到工作内存中
3.加锁和解锁是同一把锁
八种操作
问题:
线程B修改了flag的值,但是A不能及时可见
内存交互操作有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操作之前,必须把此变量同步回主内存
我们那一个简单的例子
public class JMMDemo {
private static int num=0;
public static void main(String[] args) {
new Thread(()->{
while (num==0)
{}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num);
}
}
我们出现的问题就是,我们的线程一直在循环,但是我们的主线程已经修改了num=1,但是我们的另一个线程不能及时的读取最新的消息,导致程序一直在运行,怎么解决呢?
引入Volatile关键字
十六.Volatile
请你谈谈Volatile的理解
Volatile是java虚拟机提供轻量级的同步机制
1.保证可见性
上面的JMM例子说明我们的线程对主内存的变化是不知道的
修改:
private volatile static int num=0;
我们发现线程不在一直循环了,不加volatile会导致程序死循环
2.不保证原子性
原子性:不可分割
public class VDemo1 {
private volatile static int num =0;
public static void add(){
num++;
}
public static void main(String[] args) {
//理论上应该是2W
for (int i=1;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);
}
}
我们发现加volatile与不加都不是2W这个值,这也就说明,我们的线程有些还是被占用了
当然如果我们加lock和synchronzeid是可以解决这个问题,但是不加这个怎么解决呢
我们的add()中的num++并不是原子性操作
使用原子类来,解决原子性问题
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
num.getAndIncrement();//AtomicInteger+1方法并不是简单的+1方法 底层原理是CAS
}
原子类底层都直接跟操作系统挂钩
是在内存中修改值!
Unsafe是一个很特殊的存在,在CAS中详细介绍
3.禁止指令重排
什么是指令重排
计算机程序并不是按照我们写的程序来执行的
源代码–>编译器优化重排–>指令并行也可能重排–>内存系统重排—>执行
eg
int x = 1; //1
int y = 2; //2
x = x + 5; //3
y = x * x; //4
我们所期望的顺序是 1234
但是执行的时候可能是 2134 1324
但不会是 4123
可能造成的结果:abxy默认都是0
线程A | 线程B |
---|---|
x=a | y=b |
b=1 | a=2 |
正常的结果是 x=0; y=0但是由于指令重排
线程A | 线程B |
---|---|
b=1 | a=2 |
x=a | y=b |
指令重排结果就成了 x = 2; y =1
volatile可以避免指令重排
内存屏障。CPU指令:作用
1.保证特定的操作的执行顺序
2.保证某些变量的内存可见性(利用这些特性,volatile实现了可见性)
内存屏障那个地方用的多呢?=====>单例模式
十七.单例模式
饿汉式
public class Hungry {
//可能会浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
//构造器私有,别人没办法来new,保证唯一
private Hungry(){}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
匿名内部类
public class Holder {
private Holder(){}
public static Holder getInstance()
{
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
懒汉式
package com.hkd.single;
/**
* @author 王庆华
* @version 1.0
* @date 2020/11/16 20:11
* @Description 懒汉式模式
* @pojectname 线程相关
*/
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"OK");
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan == null)
{
lazyMan = new LazyMan();
}
return lazyMan;
}
//单线程确实OK
//多线程
public static void main(String[] args) {
for (int i =0;i<10;i++)
{
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
我们发现有的时候1个线程在运行,有的时候变多了,我们的解决办法就是双重检测锁模式的懒汉式单例
DCL懒汉式
public static LazyMan getInstance(){
if (lazyMan==null)
{
synchronized (LazyMan.class)
{
if (lazyMan == null)
{
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
问题:
lazyMan = new LazyMan();不是一个原子性操作
/**
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向这个空间
*
* 底层有可能就会发生指令重排
* 我们期望的123
* 有可能发生的132 A线程没有问题
* 但是如果有线程B,线程B就认为不是null了
* 直接走return
* 但是lazyMan还没有完成构造
*/
改进
使用volatile关键字
private volatile static LazyMan lazyMan;
反射破坏单例怎么解决
public static void main(String[] args) throws Exception {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//无视私有构造器
LazyMan lazyMan = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(lazyMan);
}
如果我们的单例没被破坏,那么新建的应该是一个对象
但是我们运行发现,不是同一个对象,说明我们的单例模式被破坏了
第一次改进
我们上边的代码是一个使用自己的私有构造器,一个是通过反射的构造器,解决办法
改进我们的私有构造器
private LazyMan() {
synchronized (LazyMan.class)
{
if (lazyMan!=null)
{
throw new RuntimeException("不要试图用反射破坏异常");
}
}
}
那么问题又来了,那要是两个对象都用反射来创建呢?
public static void main(String[] args) throws Exception {
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//无视私有构造器
LazyMan lazyMan = declaredConstructor.newInstance();
LazyMan lazyMan1 = declaredConstructor.newInstance();
System.out.println(lazyMan);
System.out.println(lazyMan1);
}
我们发现我们改进的私有构造器又不行了
第二次改进
利用我们的标志位
private LazyMan() {
synchronized (LazyMan.class)
{
if (flag == false)
{
flag = true;
}else {
throw new RuntimeException("不要试图使用反射来破坏");
}
}
}
但是我们又可以通过反射获取到私有字段,改变其可见性,在第二个对象创建前,再把其值改为false又可以破坏我们的单例模式了
public static void main(String[] args) throws Exception {
Field flag = LazyMan.class.getDeclaredField("flag");
flag.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//无视私有构造器
LazyMan lazyMan = declaredConstructor.newInstance();
flag.set(lazyMan,false);
LazyMan lazyMan1 = declaredConstructor.newInstance();
System.out.println(lazyMan);
System.out.println(lazyMan1);
}
怎么从根本解决呢?
源码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jzCYjPHJ-1605609859724)(C:\Users\王庆华\AppData\Roaming\Typora\typora-user-images\image-20201116204624852.png)]
枚举
枚举本身也是一个Class类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test
{
public static void main(String[] args) {
EnumSingle enumSingle1 = EnumSingle.INSTANCE;
EnumSingle enumSingle = EnumSingle.INSTANCE;
System.out.println(enumSingle1);
System.out.println(enumSingle);
}
}
结果一样,我们想破坏它这种单例模式
编译文件
package com.hkd.single;
public enum EnumSingle {
INSTANCE;
private EnumSingle() {
}
public EnumSingle getInstance() {
return INSTANCE;
}
}
我们可以看到,反编译文件中是有无参构造函数的
但是我们通过反射的无参构造来创建对象发现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AwO45kBF-1605609859725)(C:\Users\王庆华\AppData\Roaming\Typora\typora-user-images\image-20201116205318642.png)]
没有无参构造器??IDEA骗了我们?但是我们反编译也发现有一个无参构造器,他也骗了我们?
我们再去找个反编译的工具jad,反编译发现
那我们改变我们反射的方式
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
然后我们终于发现了
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.hkd.single.Test.main(EnumSingle.java:25)
终于得到了我们想要的错误
十八.深入理解CAS
什么是CAS
Unsafe类
atomicInteger.getAndIncrement();
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
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;
}
我们发现这是一个标准的锁,自旋锁
public class CasDemo {
//compareAndSet比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
atomicInteger.compareAndSet(2020,2021);
//如果我期望的值达到了 那么就更新
atomicInteger.getAndIncrement();
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2020, 2021));
}
}
比较并交换
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
缺点:
1.底层是自旋锁,耗时
2.一次只能保证一个共享变量的原子性
3.ABA问题
CAS:ABA问题
public class CasDemo {
//compareAndSet比较并交换
public static void main(String[] args) {
//对于我们平时写的SQL操作来说:乐观锁!!
//
AtomicInteger atomicInteger = new AtomicInteger(2020);
//========捣乱线程=====
atomicInteger.compareAndSet(2020,2021);
System.out.println(atomicInteger.get());
//=====我再改回去
atomicInteger.compareAndSet(2021,2020);
System.out.println(atomicInteger.get());
//========期望的线程========
atomicInteger.compareAndSet(2020, 6666);
System.out.println(atomicInteger.get());
}
}
我想知道谁动过了!!!!
那么就要引入知识:原子引用
十九.原子引用
对应乐观锁
package com.hkd.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @author 王庆华
* @version 1.0
* @date 2020/11/16 21:06
* @Description TODO
* @pojectname 线程相关
*/
public class CasDemo {
//compareAndSet比较并交换
public static void main(String[] args) {
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获得版本号
System.out.println("a获得的"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a2获得的版本号"+atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a3获得的版本号"+atomicStampedReference.getStamp());
},"a").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获得版本号
System.out.println("b获得的"+atomicStampedReference.getStamp());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 66, stamp, stamp + 1));
System.out.println("b2获得的"+atomicStampedReference.getStamp());
},"b").start();
}
}
原理跟我们的乐观锁原理相同
大坑
【强制】所有的相同类型的包装类对象之间值的比较,全部使用equals方法比较。 说明:对于Integer var = ? 在-128至127范围内的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。
这是阿里巴巴开发手册规定的
也就是我们的泛型写的是Integer,Integer在缓存池中有可能存在复用问题
//initialStamp时间戳:相当于版本号
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
二十.锁的理解
1.公平锁、非公平锁
公平锁:不可以插队,先来后到
非公平锁:可以插队,超队现象,人家等不及啊,就会插你队,默认的机制
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
传入一个true参数就可以改变我们的锁类型
2.可重入锁
递归锁
Synchronized
//Synchronized
public class Demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+"s s m");
call();
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"c a l l ");
}
}
正常情况下,我们A线程调用call 的时候,出了sms方法,按理说应该解锁(线程B就拿到锁了),但是无论怎么执行,A都要先执行完,也就是说,A线程调用完call方法才会释放锁
Lock
package com.hkd.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 王庆华
* @version 1.0
* @date 2020/11/16 22:05
* @Description TODO
* @pojectname 线程相关
*/
public class Demo2 {
public static void main(String[] args) {
Phone1 phone = new Phone1();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone1{
Lock lock = new ReentrantLock();
public void sms(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"s s m");
call();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"c a l l ");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
本质一样,但是细节不怎么一样
我们先进入的sms,加锁后,进call的时候有拿到了一把锁,解锁也是,先解了call 的锁,然后解锁sms的锁
3.自旋锁
spinlock
手写
public class SpinLockDemo {
//int 0
//Thread null
AtomicReference atomicReference = new AtomicReference();
//加锁
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"==>mylock");
//自旋锁
while (!atomicReference.compareAndSet(null,thread)){
}
}
//加锁
public void myUnLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"==>myUnlock");
//自旋锁解锁
atomicReference.compareAndSet(thread,null);
}
}
测试
public class TestSpinLock {
public static void main(String[] args) throws InterruptedException {
// ReentrantLock reentrantLock = new ReentrantLock();
// reentrantLock.lock();
// reentrantLock.unlock();
SpinLockDemo lock = new SpinLockDemo();
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.myUnLock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.myUnLock();
}
},"T2").start();
}
}
我们的t1t2同时拿到,但是必须等ti解锁后t2才能解锁
4.死锁
什么是死锁
死锁的例子
public class Deadlockdemo {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new MyThread(lockA,lockB),"A线程").start();
new Thread(new MyThread(lockB,lockA),"B线程").start();
}
}
class MyThread implements Runnable{
private String lockA;
private String lockB;
public MyThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA)
{
System.out.println(Thread.currentThread().getName()+"我现在的锁是===》"+lockA+"想要获得Get"+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB)
{
System.out.println(Thread.currentThread().getName()+"我现在的锁是===》"+lockB+"想要获得Get"+lockA);
}
}
}
}
解决问题
1.使用jps定位进程号 jps -l
2.使用jstack 进程号查看堆栈信息
ock();
try {
System.out.println(Thread.currentThread().getName()+"c a l l ");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
**本质一样,但是细节不怎么一样**
我们先进入的sms,加锁后,进call的时候有拿到了一把锁,解锁也是,先解了call 的锁,然后解锁sms的锁
## 3.自旋锁
**spinlock**
手写
```java
public class SpinLockDemo {
//int 0
//Thread null
AtomicReference atomicReference = new AtomicReference();
//加锁
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"==>mylock");
//自旋锁
while (!atomicReference.compareAndSet(null,thread)){
}
}
//加锁
public void myUnLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"==>myUnlock");
//自旋锁解锁
atomicReference.compareAndSet(thread,null);
}
}
测试
public class TestSpinLock {
public static void main(String[] args) throws InterruptedException {
// ReentrantLock reentrantLock = new ReentrantLock();
// reentrantLock.lock();
// reentrantLock.unlock();
SpinLockDemo lock = new SpinLockDemo();
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.myUnLock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.myUnLock();
}
},"T2").start();
}
}
我们的t1t2同时拿到,但是必须等ti解锁后t2才能解锁
4.死锁
什么是死锁
[外链图片转存中…(img-H1RSyuPM-1605609859732)]
死锁的例子
public class Deadlockdemo {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new MyThread(lockA,lockB),"A线程").start();
new Thread(new MyThread(lockB,lockA),"B线程").start();
}
}
class MyThread implements Runnable{
private String lockA;
private String lockB;
public MyThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA)
{
System.out.println(Thread.currentThread().getName()+"我现在的锁是===》"+lockA+"想要获得Get"+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB)
{
System.out.println(Thread.currentThread().getName()+"我现在的锁是===》"+lockB+"想要获得Get"+lockA);
}
}
}
}
[外链图片转存中…(img-UdR7Kegx-1605609859733)]
解决问题
1.使用jps定位进程号 jps -l
2.使用jstack 进程号查看堆栈信息
内容借鉴B站狂神说的教学视频哦,想学习的小伙伴可以关注下