浅谈JUC的常用类
JUC就是java.util.concurrent…包下的类
回顾多线程
Java默认有几个线程? 2 个 mian、GC
Java 真的可以开启线程吗? 开不了,点击源码得知:本地方法,底层的C++ ,Java 无法直接操作硬件
线程有几个状态
public enum State {
NEW,//新生
RUNNABLE,//运行
BLOCKED,//阻塞
WAITING,//等待,死等
TIMED_WAITING,//超时等待
TERMINATED;//终止
}
Synchronized 和 Lock 区别
1、Synchronized 内置的Java关键字, Lock 是一个Java类
2、Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
3、Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
4、Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下
去;
5、Synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以 判断锁,非公平(可以
自己设置);
6、Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!
代码演示:
package com.fs.demo;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo01 {
public static void main(String[] args) {
//创建资源类
ThreeOne threeOne = new ThreeOne();
//创建三个线程,循环调用number--方法
new Thread(()->{
for (int i = 0; i < 40; i++) threeOne.test();
},"B").start();
new Thread(()->{
for (int i = 0; i < 40; i++) threeOne.test();
},"A").start();
new Thread(()->{
for (int i = 0; i < 40; i++) threeOne.test();
},"C").start();
}
}
//资源类
class ThreeOne{
int number = 30;
//买票方法
//添加synchronized关键字解决买票安全问题:队列,锁,悲观锁
//Synchronized 会自动释放锁,
// public synchronized void test(){
// //如果大于0就卖
// if (number>0){
// System.out.println("线程:"+Thread.currentThread().getName()+",正在卖第:"+(number--)+",剩余票:"+number);
// }
// }
/*
不使用块结构锁就失去了使用 synchronized 方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。
*/
//创建JUC锁,
Lock lock = new ReentrantLock();
public synchronized void test(){
//加锁
lock.lock();
try {
//如果大于0就卖
if (number>0){
System.out.println("线程:"+Thread.currentThread().getName()+",正在卖第:"+(number--)+",剩余票:"+number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//lock 必须要手动释放锁!如果不释放锁,死锁
//释放锁,一定要释放锁,否则会死锁
lock.unlock();
}
}
/*
Synchronized 和 Lock 区别
1、Synchronized 内置的Java关键字, Lock 是一个Java类
2、Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
3、Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
4、Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下
去;
5、Synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以 判断锁,非公平(可以
自己设置);
6、Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!
*/
}
生产者和消费者问题 Synchronized 版
package com.fs.demo02;
/**
* 生产者和消费者问题 Synchronized 版
*/
public class SynchronizedWhile {
/*
我们使用2个线程的时候,调用++--方法,看似没有任何问题
当我们线程多了后.使用if就会出现虚假唤醒的情况
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。
当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。
对于某一个参数的版本,实现中断和虚假唤醒是可能的,而且此方法应始终在循环中使用:
synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
}
此方法只应由作为此对象监视器的所有者的线程来调用。有关线程能够成为监视器所有者的方法的描述,请参阅 notify 方法。
*/
public static void main(String[] args) {
//创建资源类
DataTest dataTest = new DataTest();
//创建一个线程调用++方法
new Thread(()->{
for (int i = 0; i < 30; i++) {
try {
dataTest.testIncrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
//创建一个线程调用--方法
new Thread(()->{
for (int i = 0; i < 30; i++) {
try {
dataTest.testDecrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
//使用if,我们两个线程暂且看不出问题,我们添加CD线程试试 就会发现资源不安全了
//A->1
//C->2
//A->3
//创建一个线程调用++方法
new Thread(()->{
for (int i = 0; i < 30; i++) {
try {
dataTest.testIncrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
//创建一个线程调用--方法
new Thread(()->{
for (int i = 0; i < 30; i++) {
try {
dataTest.testDecrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//资源类
class DataTest{
int num = 0;
//这个方法让num +1
public synchronized void testIncrement() throws InterruptedException {
//判断num == 0吗
//根据API得知,使用While解决线程的等待唤醒问题
while (num != 0){
// if (num != 0){
//为0,就等待
this.wait();
}
//让num++
num++;
System.out.println(Thread.currentThread().getName()+"->"+num);
//唤醒正在等待的所有线程
this.notifyAll();
}
//这个方法让num -1
public synchronized void testDecrement() throws InterruptedException {
//判断num == 0吗
while (num == 0){
// if (num == 0){
//为0,就等待,
this.wait();
}
//让num--
num--;
System.out.println(Thread.currentThread().getName()+"->"+num);
//唤醒正在等待的其他线程
this.notifyAll();
}
}
java.util.concurrent.locks.Condition;
lock锁与Condition监视器指定唤醒
package com.fs.demo02;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
lock锁与Condition监视器指定唤醒
*/
public class ConditionDemo {
public static void main(String[] args) {
//创建资源
TestCondition testCondition = new TestCondition();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
testCondition.Test01();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
testCondition.Test02();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
testCondition.Test03();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
}
}
class TestCondition{
//创建一个lock
Lock lock = new ReentrantLock();
//创建3个监视器,用于指定唤醒某个线程
private Condition condition01 = lock.newCondition();
private Condition condition02 = lock.newCondition();
private Condition condition03 = lock.newCondition();
//定义一个变量来决定当前线程应该谁执行
int num = 0;
//创建一个方法
public void Test01() throws InterruptedException {
//获得锁
lock.lock();
//判断当前num == 0吗.等于就=1,不等就等待
if (num!=0){
//就让condition01线程等待
condition01.await();
}
//否则就让num++
System.out.println(Thread.currentThread().getName()+"condition01,执行了"+num);
num = 1;
//唤醒condition02,实现精准唤醒
condition02.signal();
//释放锁
lock.unlock();
}
//创建一个方法
public void Test02() throws InterruptedException {
//获得锁
lock.lock();
//判断当前num == 1吗.等于就=2,不等就等待
if (num!=1){
//就让condition02线程等待
condition02.await();
}
//否则就让num++
System.out.println(Thread.currentThread().getName()+"condition01,执行了"+num);
num = 2;
//唤醒condition03,实现精准唤醒
condition03.signal();
//释放锁
lock.unlock();
}
//创建一个方法
public void Test03() throws InterruptedException {
//获得锁
lock.lock();
//判断当前num == 2吗.等于就=0,不等就等待
if (num!=2){
//就让condition03线程等待
condition03.await();
}
//否则就让num =0
System.out.println(Thread.currentThread().getName()+"condition01,执行了"+num);
num = 0;
//唤醒condition03,实现精准唤醒
condition01.signal();
//释放锁
lock.unlock();
}
}
关于锁的8个问题
代码(注释为详细解释)
1、标准情况下,两个线程先打印 是先执行One 还是Two
2、One延迟4秒,两个线程先打印 是先执行One 还是Two
package com.fs.demo03;
import java.util.concurrent.TimeUnit;
/*
8锁
8锁,就是关于锁的8个问题
1、标准情况下,两个线程先打印 是先执行One 还是Two
2、One延迟4秒,两个线程先打印 是先执行One 还是Two
*/
public class EightLockOne {
public static void main(String[] args) {
//创建Test对象
Test test = new Test();
//开启两个线程
new Thread(()->{
try {
test.One();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
//让main线程到这里睡1秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
test.Two();
},"B").start();
}
/*
1、标准情况下,两个线程先打印 是先执行One 还是Two
结论:
One,执行了
Two,执行了
因为我们使用的同一把锁对象,test这个对象,两个方法都被同一把锁对象锁住
2、sendSms延迟4秒,两个线程先打印 是先执行One 还是Two
结论:
One,执行了
Two,执行了
因为我们使用的是同一把锁test对象,当One执行的时候获得了锁,没有执行完synchronized是不会自动释放锁的
所以two要等到one释放了test对象锁后,在拿到锁,然后在执行
*/
}
class Test{
//创建两个方法,便于测试,使用synchronized
public synchronized void One() throws InterruptedException {
//2、sendSms延迟4秒,两个线程先打印 是先执行One 还是Two
TimeUnit.SECONDS.sleep(4);
System.out.println("One,执行了");
}
public synchronized void Two(){
System.out.println("Two,执行了");
}
}
3、 增加了一个普通方法后! 是先执行One 还是Two
4、 两个对象,两个同步方法, 是先执行One 还是Two
package com.fs.demo03;
import java.util.concurrent.TimeUnit;
/*
8锁
8锁,就是关于锁的8个问题
3、 增加了一个普通方法后! 是先执行One 还是Two
4、 两个对象,两个同步方法, 是先执行One 还是Two
*/
public class EightLockTwo {
public static void main(String[] args) {
//创建Test对象
Test02 test = new Test02();
//创建Test对象2来解释:两个对象,两个同步方法, 是先执行One 还是Two
Test02 test02 = new Test02();
//开启两个线程
new Thread(()->{
try {
test.One();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
//让main线程到这里睡1秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//使用test02来调用two
new Thread(()->{
test02.Two();
},"B").start();
//创建一个线程,执行普通方法
// new Thread(()->{
// test.method();
// },"C").start();
}
/*
3、 增加了一个普通方法后! 是先执行One 还是Two 还是普通方法
结论:
method,执行了
One,执行了
Two,执行了
因为我们的普通方法没有被synchronized,不是同步方法,不受锁的影响,执行也不需要锁
4、 两个对象,两个同步方法, 是先执行One 还是Two
结论:
Two,执行了
One,执行了
因为虽然我们方法都使用了synchronized,但是我们test锁对象不是一个,自己有自己的锁,不需要争取锁,各自是各自的锁,
所以,因为我们one sleep了4秒,two有自己的锁,所以正常执行,所以先输出
*/
}
class Test02{
//创建两个方法,便于测试,使用synchronized
public synchronized void One() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("One,执行了");
}
public synchronized void Two(){
System.out.println("Two,执行了");
}
//3 增加了一个普通方法后! 是先执行One 还是Two
public void method(){
System.out.println("method,执行了");
}
}
5、增加两个静态的同步方法 是先执行One 还是Two
6、两个对象!增加两个静态的同步方法,是先执行One 还是Two
package com.fs.demo03;
import java.util.concurrent.TimeUnit;
/*
8锁
8锁,就是关于锁的8个问题
5、增加两个静态的同步方法 是先执行One 还是Two
6、两个对象!增加两个静态的同步方法,是先执行One 还是Two
*/
public class EightLockThree {
public static void main(String[] args) {
//创建Test对象
Test03 test = new Test03();
Test03 test02 = new Test03();
//开启两个线程
new Thread(()->{
try {
test.One();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
//让main线程到这里睡1秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// new Thread(()->{
// test.Two();
// },"B").start();
//两个对象!增加两个静态的同步方法,是先执行One 还是Two
new Thread(()->{
test02.Two();
},"B").start();
}
/*
5、增加两个静态的同步方法 是先执行One 还是Two
结论:
One,执行了
Two,执行了
因为我们的锁对象有2种,一种是对象锁,一种是Class锁.
而我们在方法上添加synchronized使用的是对象锁,当我们添加了static synchronized就为Class锁
而我们这里两个方法都加了Class,就是使用的Test03.Class锁,Class只会存在一个,所以我们这里使用的始终是同一把锁
6、两个对象!增加两个静态的同步方法,是先执行One 还是Two
结论:
One,执行了
Two,执行了
还是这样,为什么呢?是因为,我们这里方法添加了static synchronized,使用的就是Class锁,虽然我们是两个test03对象分别
调用了One Two ,但是内存中Class 对象只有一个,实际使用的还是一把锁,一把锁,就需要等待one释放锁,two获得锁
*/
}
//Test03唯一的一个 Class 对象
class Test03{
//创建两个方法,便于测试,使用synchronized
public static synchronized void One() throws InterruptedException {
//2、sendSms延迟4秒,两个线程先打印 是先执行One 还是Two
TimeUnit.SECONDS.sleep(4);
System.out.println("One,执行了");
}
public static synchronized void Two(){
System.out.println("Two,执行了");
}
}
7,1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 One?Two?
8,1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 One?Two?
package com.fs.demo03;
import java.util.concurrent.TimeUnit;
/*
8锁
8锁,就是关于锁的8个问题
7,1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 One?Two?
8,1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 One?Two?
*/
public class EightLockFour {
public static void main(String[] args) {
//创建Test对象
Test04 test = new Test04();
Test04 test02 = new Test04();
//开启两个线程
new Thread(()->{
try {
test.One();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
//让main线程到这里睡1秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//1个静态的同步方法,1个普通的同步方法 ,两个对象
new Thread(()->{
test02.Two();
},"B").start();
}
/*
7,1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 One?Two?
结论:
Two,执行了
One,执行了
因为one方法是class锁,two是对象锁,锁不一样,所以互相不影响
8,1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 One?Two?
结论:
Two,执行了
One,执行了
同理可得,因为one是class锁,而two是对象锁
*/
}
class Test04{
//创建两个方法,便于测试,使用synchronized
//静态同步方法
public static synchronized void One() throws InterruptedException {
//2、sendSms延迟4秒,两个线程先打印 是先执行One 还是Two
TimeUnit.SECONDS.sleep(4);
System.out.println("One,执行了");
}
public synchronized void Two(){
System.out.println("Two,执行了");
}
}
集合不安全
List不安全
并发下 ArrayList 不安全的吗???
package com.fs.demo04;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/*
List集合不安全
*/
public class CopyOnWriteArrayListDemo {
public static void main(String[] args) {
//创建一个ArrayList,会出现ConcurrentModificationException,并发修改异常
// List<String> list = new ArrayList<>();
//解决方案一
// List<String> list = new Vector<>();
//解决方案2,使用集合工具类
// List<String> list = Collections.synchronizedList(new ArrayList<>());
//解决方案3,使用JUC工具类package java.util.concurrent;下的CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>();
//创建10个线程去向list集合中添加元素
for (int i = 1; i <= 20; i++) {
new Thread(()->{
//向集合中添加元素
list.add("线程:"+Thread.currentThread().getName()+":"+UUID.randomUUID().toString().substring(0,5));
System.out.println(list.toString()+new Date());
},String.valueOf(i)).start();
}
}
/*
结论:
java.util.ConcurrentModificationException
会出现并发修改异常
解决方案:
* 1、List<String> list = new Vector<>();
* 2、List<String> list = Collections.synchronizedList(new ArrayList<>());
* 3、List<String> list = new CopyOnWriteArrayList<>();
解析:
* 1、List<String> list = new Vector<>();
从源码中查看:
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
Vector.add()方法是使用synchronized同步锁解决的线程安全问题的,是阻塞的,效率低
* 2、List<String> list = Collections.synchronizedList(new ArrayList<>());
public static <T> List<T> synchronizedList(List<T> list)返回由指定列表支持的同步(线程安全)列表。
返回的是一个线程安全的集合,源码:
static class SynchronizedList<E>
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
看出也是通过synchronized同步锁实现线程安全功能
* 3、List<String> list = new CopyOnWriteArrayList<>();
这是package java.util.concurrent;下的类
由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();//释放锁
}
} \
这个add方法使用的是lock锁实现的,相比 synchronized 性能要高,所以开发中要使用list存在并发的时候,推荐使用这个JUC下的这个类CopyOnWriteArrayList
*/
}
Set不安全
hashSet 底层是什么?
public HashSet() {
map = new HashMap<>();
}
// add set 本质就是 map key是无法重复的!
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object(); // 不变得值!
解决代码
package com.fs.demo04;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
/*
set并发不安全
*/
public class SetDemo {
public static void main(String[] args) {
//创建一个HashSet,java.util.ConcurrentModificationException,冰法修改异常,因为HashSet线程不安全,
// Set<String> set = new HashSet<>();
//解决方案1:使用集合工具类中的synchronizedSet方法
// Set<String> set = Collections.synchronizedSet(new HashSet<>());
//解决方案2:使用JUC包下的java.util.concurrent Class CopyOnWriteArraySet<E>
Set<String> set = new CopyOnWriteArraySet<>();
//创建20个线程来想set中添加数据
for (int i = 1; i <= 20; i++) {
new Thread(()->{
//每个线程向set中添加值
set.add("线程:"+Thread.currentThread().getName()+":"+ UUID.randomUUID().toString().substring(0,5));
//输出一下
System.out.println(set);
},String.valueOf(i)).start();
}
}
/*
Set<String> set = new HashSet<>();线程不安全
由源码得知,HashSet是HashMap
构造方法:就是new HashMap<>();
public HashSet() {
map = new HashMap<>();
}
//HashMap源码add方法得知:set的值就是Map的K,而Map的Value是一个常量,private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
解决方案:
解决方案1.使用集合工具类中的synchronizedSet方法
1. Set<String> set = Collections.synchronizedSet(new HashSet<>());
源码:
public boolean add(E e) {
synchronized (mutex) {return c.add(e);}
得知,是使用的synchronized来解决线程安全的
解决方案2:使用JUC包下的java.util.concurrent Class CopyOnWriteArraySet<E>
Set<String> set = new CopyOnWriteArraySet<>();
由源码得:
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
CopyOnWriteArraySet的无参构造实际上是new CopyOnWriteArrayList<E>();
所以Add方法最终调用了CopyOnWriteArrayList的addIfAbsent(),方法体中是使用的lock锁实现的线程安全
*/
}
Map不安全
// map 是这样用的吗? 不是,工作中不用 HashMap
// 默认等价于什么? new HashMap<>(16,0.75);
// Map<String, String> map = new HashMap<>();
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
使用JUC的ConcurrentHashMap解决Map的不安全
package com.fs.demo04;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/*
HashMap是线程不安全的
// map 是这样用的吗? 不是,工作中不用 HashMap
// 默认等价于什么? new HashMap<>(16,0.75);
// Map<String, String> map = new HashMap<>();
// 研究ConcurrentHashMap的原理
*/
public class HashMapDemo {
public static void main(String[] args) {
//创建一个HashMap,java.util.ConcurrentModificationException,并发下会出现并发修改异常,所以,是不安全的
// Map<String, String> map = new HashMap<>();
//解决方法1:使用集合工具类,获取线程安全的Map
// Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
//解决方案2:使用JUC工具包中的ConcurrentHashMap
Map<String, String> map = new ConcurrentHashMap<>();
//创建20个线程put map集合
for (int i = 1; i <= 20; i++) {
new Thread(()->{
//put K为线程名,V为UUID生成的前5位
map.put("线程:"+Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
//输出一下
System.out.println(map);
},String.valueOf(i)).start();
}
}
/*
创建一个HashMap,java.util.ConcurrentModificationException,并发下会出现并发修改异常,所以,是不安全的
Map<String, String> map = new HashMap<>();
解决方案:
//解决方法1:使用集合工具类,获取线程安全的Map
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
源码得知:使用了,synchronized
public V put(K key, V value) {
synchronized (mutex) {return m.put(key, value);}
}
//解决方案2:使用JUC工具包中的ConcurrentHashMap
Map<String, String> map = new ConcurrentHashMap<>();
源码中put方法调用putVal(),这个方法中使用的synchronized来解决线程安全的
public V put(K key, V value) {
return putVal(key, value, false);
}
putVal(){
synchronized
}
*/
}
Callable
java.util.concurrent
Interface Callable
@FunctionalInterface
public interface Callable
返回结果并可能引发异常的任务。 实现者定义一个没有参数的单一方法,称为call 。
Callable接口类似于Runnable ,因为它们都是为其实例可能由另一个线程执行的类设计的。 然而,A Runnable不返回结果,也不能抛出被检查的异常。
该Executors类包含的实用方法,从其他普通形式转换为Callable类。
package com.fs.demo05;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/*
java.util.concurrent
Interface Callable<V>
@FunctionalInterface
public interface Callable<V>
返回结果并可能引发异常的任务。 实现者定义一个没有参数的单一方法,称为call 。
Callable接口类似于Runnable ,因为它们都是为其实例可能由另一个线程执行的类设计的。 然而,A Runnable不返回结果,也不能抛出被检查的异常。
该Executors类包含的实用方法,从其他普通形式转换为Callable类。
*/
public class CallableDemo {
//Thread Runnable FutureTask Callable 之间的关系
// new Thread(new Runnable()).start();
// new Thread(new FutureTask<V>()).start();
// new Thread(new FutureTask<V>( Callable )).start();
public static void main(String[] args) {
//我们使用Runnable跑的线程是没有返回值的
//创建一个线程,我们发现 public Thread(Runnable target){}传递的接口都是Runnable,怎么办
//怎么办
//怎么办?
/*
查看API文档:我发现Runnable有一个实现类:Class FutureTask<V> 这个实现类有一个构造方法
FutureTask(Callable<V> callable) 创建一个 FutureTask ,它将在运行时执行给定的 Callable 。
这样就知道了 new Thread(new FutureTask<V>( Callable )).start();
V - 该FutureTask的 get方法返回的结果类型
*/
// new Thread(new FutureTask<String>(new TestB())).start();
//运行一下发现控制台输出:Callable的call()被执行了,所以线程是执行了,那么返回值怎么拿?
//查看API文档得:public V get()throws InterruptedException,ExecutionException说明从接口Future复制 等待计算完成,然后检索其结果。
//创建一个FutureTask
FutureTask<String> futureTask = new FutureTask<String>(new TestB());
//开启一个线程
new Thread(futureTask).start();
try {
//get获取值打印输出一下
String result = futureTask.get();//这个get 方法可能会产生阻塞!把他放到最后 或者使用异步通信来处理!
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//那么问题来了,我多线程调用futureTask,会打印几次:Callable的call()被执行了
new Thread(futureTask).start();
new Thread(futureTask).start();//只会执行一次,原因是结果会被缓存,效率高
}
}
//一般我们开启多线程,会传递这个接口
class TestA implements Runnable{
//这个接口的run方法是没有返回值的,若我们想我们执行的线程有一个返回值需要使用什么? 那就是Callable接口
@Override
public void run() {
System.out.println("Runnable的run方法,我没有返回值,呜呜呜");
}
}
//创建一个类,实现Callable<V> 由源码得知,V是方法的返回值类
class TestB implements Callable<String>{
//源码:V call() throws Exception;
@Override
public String call() throws Exception {
System.out.println("Callable的call()被执行了");
return "Callable的call()是有返回值的,我现在返回给我的调用者了";
}
}
JUC常用的辅助类
CountDownLatch
public class CountDownLatch
extends Object
允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。
A CountDownLatch用给定的计数初始化。 await方法阻塞,直到由于countDown()方法的调用而导致当前计数达到零,之后所有等待线程被释放,并且任何后续的await 调用立即返回。 这是一个一次性的现象 - 计数无法重置。
countDownLatch.countDown(); // 数量-1
countDownLatch.await(); // 等待计数器归零,然后再向下执行
每次有线程调用 countDown() 数量-1,假设计数器变为0,countDownLatch.await() 就会被唤醒,继续
执行!
package com.fs.demo06;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class CountDownLatchDemo {
/*
CountDownLatch一个有用的属性是,它不要求调用countDown线程等待计数到达零之前继续,它只是阻止任何线程通过await ,直到所有线程可以通过。
示例用法:这是一组类,其中一组工作线程使用两个倒计时锁存器:
第一个是启动信号,防止任何工作人员进入,直到驾驶员准备好继续前进;
第二个是完成信号,允许司机等到所有的工作人员完成。
*/
public static void main(String[] args) {
//创建一个CountDownLatch总数是5,必须要执行任务的时候,再使用!
CountDownLatch countDownLatch = new CountDownLatch(5);
new Thread(()->{
//执行5次
for (int i = 1; i <= 5; i++) {
System.out.println("线程执行中"+i);
//sleep一下
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();//减少锁存器的计数,如果计数达到零,释放所有等待的线程。
}
}).start();
try {
// 等待计数器归零,然后再向下执行
countDownLatch.await();//导致当前线程等到锁存器计数到零,除非线程是 interrupted 。
} catch (InterruptedException e) {
e.printStackTrace();
}
//上面的线程不执行完,这个不会输出,因为使用了countDownLatch,要countDownLatch减到0 的时候才会唤醒其他线程
System.out.println("mian线程执行");
}
}
CyclicBarrier
public class CyclicBarrier
extends Object
允许一组线程全部等待彼此达到共同屏障点的同步辅助。 循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。 屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。
A CyclicBarrier支持一个可选的Runnable命令,每个屏障点运行一次,在派对中的最后一个线程到达之后,但在任何线程释放之前。 在任何一方继续进行之前,此屏障操作对更新共享状态很有用。
package com.fs.demo06;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
public class CyclicBarrierDemo {
public static void main(String[] args) {
//使用七龙珠案列说明这个CyclicBarrier
//创建这个类,
/*
public CyclicBarrier(int parties, Runnable barrierAction){}
发现这个类需要连个参数
parties:当前线程的到达索引,其中索引 getParties() - 1表示第一个到达,零表示最后到达异常
barrierAction:当给定数量的线程(线程)等待时,它将跳闸,当屏障跳闸时执行给定的屏障动作,由最后一个进入屏障的线程执行。
意思是什么呢?
意思就是假如我们给定int = 7 ,每次线程执行调用一次await()方法,parties值就会-1,当值为0的时候,就会执行barrierAction这个线程
*/
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("集齐七龙珠,召唤神龙~");
});
//创建7个线程,每个线程调用一次await()表示收集到一个龙珠
for (int i = 1; i <= 7; i++) {
//由于内部类不能访问外部类的变量,使用final提升作用域
final int j = i;
new Thread(()->{
System.out.println("线程:"+Thread.currentThread().getName()+",收集到:"+j+"颗龙珠");
//sleep一下
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
int await = cyclicBarrier.await();//调用await()
System.out.println(await);//输出一下当前parties
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
/*
运行结果
线程:2,收集到:2颗龙珠
线程:3,收集到:3颗龙珠
线程:1,收集到:1颗龙珠
线程:4,收集到:4颗龙珠
线程:5,收集到:5颗龙珠
线程:7,收集到:7颗龙珠
线程:6,收集到:6颗龙珠
集齐七龙珠,召唤神龙~
0
4
5
6
1
3
2
*/
}
Semaphore
public class Semaphore
extends Object
implements Serializable
一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它。 每个release()添加许可证,潜在地释放阻塞获取方。 但是,没有使用实际的许可证对象; Semaphore只保留可用数量的计数,并相应地执行。
package com.fs.demo06;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/*
public class Semaphore extends Object implements Serializable
一个计数信号量。 在概念上,信号量维持一组许可证。
如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它。
每个release()添加许可证,潜在地释放阻塞获取方。
但是,没有使用实际的许可证对象; Semaphore只保留可用数量的计数,并相应地执行。
*/
public class SemaphoreDemo {
public static void main(String[] args) {
//Semaphore(int permits)创建一个 Semaphore与给定数量的许可证和非公平公平设置。
//permits参数:限流,设置为3,就指定一次只能执行3个线程
Semaphore semaphore = new Semaphore(3);
//作用: 多个共享资源互斥的使用!并发限流,控制最大的线程数!
//创建9个线程
for (int i = 1; i <= 9; i++) {
new Thread(()->{
//acquire()获得,假设如果已经满了,等待,等待被释放为止!
//从该信号量获取许可证,阻止直到可用,或线程为 interrupted 。
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();//释放
}
//release()释放,会将当前的信号量释放 + 1,然后唤醒等待的线程!
//释放许可证,将其返回到信号量。
},String.valueOf(i)).start();
}
}
}
读写锁
ReadWriteLock
public interface ReadWriteLock
A ReadWriteLock维护一对关联的locks ,一个用于只读操作,一个用于写入。 read lock可以由多个阅读器线程同时进行,只要没有作者。 write lock是独家的。
所有ReadWriteLock实现必须保证的存储器同步效应writeLock操作(如在指定Lock接口)也保持相对于所述相关联的readLock 。 也就是说,一个线程成功获取读锁定将会看到在之前发布的写锁定所做的所有更新。
package com.fs.demo07;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/*
读写锁
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
//创建出普通读写测试类
/*
testReadWrite没有进行读写加锁执行的是有问题的,某个线程在执行写的时候,其余线程也同时进行写,数据会出现异常,所以是有线程安全问题的
*/
// TestReadWrite testReadWrite = new TestReadWrite();
//创建TestReadWriteLock测试类
/*
从运行结果看出,我们加上了ReentrantReadWriteLock这个锁,在进行写的时候,是一个一个线程的执行写的操作,而读就是多个线程同时去读
*/
TestReadWriteLock testReadWrite = new TestReadWriteLock();
//创建10个线程来模拟10个线程进行写的操作
for (int i = 1; i <= 10; i++) {
//匿名内部类访问外部资源,资源要被final修饰
final String test = String.valueOf(i);
new Thread(()->{
//进行写
testReadWrite.testWrite(test,test);
},String.valueOf(i)).start();
}
//创建10个线程来模拟10个线程进行读的操作
for (int i = 1; i <= 10; i++) {
//匿名内部类访问外部资源,资源要被final修饰
final String test = String.valueOf(i);
new Thread(()->{
//进行写
testReadWrite.testRead(test);
},String.valueOf(i)).start();
}
}
}
//创建一个普通的读写
class TestReadWrite{
/*
volatile 的特性
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)
禁止进行指令重排序。(实现有序性)
volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。
关于volatile 原子性可以理解为把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步.
*/
//创建一个HashMap来模拟读写
private volatile Map<String, String> map = new HashMap<>();
public void testWrite(String k,String v){
System.out.println("线程:"+Thread.currentThread().getName()+",写入中");
//对map进行put模拟写操作
map.put(k,v);
System.out.println("线程:"+Thread.currentThread().getName()+",写入OK");
}
public void testRead(String k){
System.out.println("线程:"+Thread.currentThread().getName()+",读取中");
//对map,get,模拟读
String s = map.get(k);
System.out.println("线程:"+Thread.currentThread().getName()+",读取OK,V为:"+s);
}
}
//创建一个读写锁
class TestReadWriteLock{
/*
volatile 的特性
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)
禁止进行指令重排序。(实现有序性)
volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。
关于volatile 原子性可以理解为把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步.
*/
//创建一个HashMap来模拟读写
private volatile Map<String, String> map = new HashMap<>();
//创建一个java.util.concurrent.locks Class ReentrantReadWriteLock
//ReadWriteLock维护一对关联的locks ,一个用于只读操作,一个用于写入。 read lock可以由多个阅读器线程同时进行,只要没有写入者。 write lock是独家的。
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 存,写入的时候,只希望同时只有一个线程写
public void testWrite(String k,String v){
//通过readWriteLock的Lock Lock writeLock()返回用于写入的锁
readWriteLock.writeLock().lock();
try {
System.out.println("线程:"+Thread.currentThread().getName()+",写入中");
//对map进行put模拟写操作
map.put(k,v);
System.out.println("线程:"+Thread.currentThread().getName()+",写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁
readWriteLock.writeLock().unlock();
}
}
//取,读,所有人都可以读!
public void testRead(String k){
通过readWriteLock的Lock readLock()返回用于阅读的锁。
readWriteLock.readLock().lock();
try {
System.out.println("线程:"+Thread.currentThread().getName()+",读取中");
//对map,get,模拟读
String s = map.get(k);
System.out.println("线程:"+Thread.currentThread().getName()+",读取OK,V为:"+s);
} catch (Exception e) {
e.printStackTrace();
}finally {
//释放锁
readWriteLock.readLock().unlock();
}
}
}
阻塞队列
BlockingQueue 阻塞队列
阻塞队列的4种方法
package com.fs.demo08;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
/*
阻塞队列
四组API
方式 抛出异常 有返回值,不抛出异常 阻塞,等待 超时等待
添加 add offer() put() offer(,,)
移除 remove poll() take() poll(,)
检测队首元素 element peek - -
*/
public class BlockingQueueDemo {
public static void main(String[] args) {
//测试抛出异常
// testOne();
//测试有返回值,不抛出异常
// testTwo();
//测试等待,阻塞(一直阻塞)
// try {
// testThree();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//测试等待,阻塞(等待超时)
try {
testFour();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//测试抛出异常
public static void testOne(){
//创建一个阻塞队列,指定队列最大添加3个
BlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println("========入队列=======");
//blockingQueue.add() 返回值是布尔值
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//我们添加3个是没有任何问题的,当我们在添加就会报错
// System.out.println(blockingQueue.add("d"));//报错Queue full 队列满
System.out.println("========出队列=======");
//查看首元素
System.out.println("队首:"+blockingQueue.element());//a
//因为是队列的原因,先进先出,所以出来的顺序是abc
System.out.println(blockingQueue.remove());
System.out.println("队首:"+blockingQueue.element());//b 因为上面a队列出了,所以b就成首队列了
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//当我们出完后,在从队列中去,肯定是没有的,所以会报错
// System.out.println(blockingQueue.remove());//NoSuchElementException 没有这样的元素异常
}
//测试有返回值,不抛出异常
public static void testTwo(){
//创建一个阻塞队列,指定队列最大添加3个
BlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println("========入队列=======");
//blockingQueue.offer() 返回值是布尔值
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
//我们添加3个是没有任何问题的,offer()在入队的时候,队列满,返回false
System.out.println(blockingQueue.offer("d"));//不会报错 返回false
System.out.println("========出队列=======");
System.out.println("队首:"+blockingQueue.peek());//a
//因为是队列的原因,先进先出,所以出来的顺序是abc
System.out.println(blockingQueue.poll());
System.out.println("队首:"+blockingQueue.peek());//b
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//当我们出完后,在从队列中去,肯定是没有的,不会报错,只是取不出值,返回null
System.out.println(blockingQueue.poll());//不会报错
}
//测试等待,阻塞(一直阻塞)
public static void testThree() throws InterruptedException {
//创建一个阻塞队列,指定队列最大添加3个
BlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println("========入队列=======");
//blockingQueue.put() 没有返回值
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//我们添加3个是没有任何问题的,put()在入队的时候,队列满,就会一直等待,阻塞线程就不会往下执行
// blockingQueue.put("d");//不会报错
System.out.println("========出队列=======");
System.out.println("队首:"+blockingQueue.peek());//a
//因为是队列的原因,先进先出,所以出来的顺序是abc
System.out.println(blockingQueue.take());
System.out.println("队首:"+blockingQueue.peek());//b
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
//当我们出完后,在从队列中去,肯定是没有的,不会报错,线程会阻塞,会一直等待队列中是否有数据可以取出,
System.out.println(blockingQueue.take());//不会报错
}
//测试等待,阻塞(等待超时)
public static void testFour() throws InterruptedException {
//创建一个阻塞队列,指定队列最大添加3个
BlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println("========入队列=======");
//blockingQueue.offer() 返回值是布尔值
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
//我们添加3个是没有任何问题的,offer(,,)这重载方法,第一个参数为入队的数据,第二个参数为超时时间,第三个参数是时间规则是秒,还分
// 在入队的时候,队列满,阻塞线程,等待设置的时间,超时就退出
System.out.println(blockingQueue.offer("d",2, TimeUnit.SECONDS));//不会报错 返回false
System.out.println("========出队列=======");
System.out.println("队首:"+blockingQueue.peek());//a
//因为是队列的原因,先进先出,所以出来的顺序是abc
System.out.println(blockingQueue.poll());
System.out.println("队首:"+blockingQueue.peek());//b
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//当我们出完后,在从队列中去,肯定是没有的,不会报错,只是取不出值,poll(,)重载方法,指定时间,超时如果拿不到值就退出
System.out.println(blockingQueue.poll(1,TimeUnit.SECONDS));//不会报错,返回null
}
}
SynchronousQueue 同步队列
public class SynchronousQueue
extends AbstractQueue
implements BlockingQueue, Serializable
A blocking queue其中每个插入操作必须等待另一个线程相应的删除操作,反之亦然。 同步队列没有任何内部容量,甚至没有一个容量。
package com.fs.demo08;
import java.util.AbstractQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
/*
* 同步队列
* 和其他的BlockingQueue 不一样, SynchronousQueue 不存储元素
* put了一个元素,必须从里面先take取出来,否则不能在put进去值!
*
* A blocking queue其中每个插入操作必须等待另一个线程相应的删除操作,反之亦然。
* 同步队列没有任何内部容量,甚至没有一个容量。
* 你不能peek在同步队列,因为一个元素,当您尝试删除它才存在; 您无法插入元素(使用任何方法),除非另有线程正在尝试删除它
*/
public class SynchronousQueueDemo {
public static void main(String[] args) {
//创建一个同步队列,同步队列每次只能入队一个,就好比某些特殊服务,每次只能服务一人,服务后出去,才能服务下一个
/*
线程:A正在入队a
线程:B正在出队,出队元素为:a
线程:A正在入队b
线程:B正在出队,出队元素为:b
线程:A正在入队c
线程:B正在出队,出队元素为:c
由打印结果就能看出
*/
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
//创建2和线,一个用于入队
new Thread(()->{
//向同步队列中入队元素
try {
System.out.println("线程:"+Thread.currentThread().getName()+"正在入队a");
blockingQueue.put("a");//若这里入队后,没有线程来take()出列,那么线程就会阻塞,必须要出列后才能继续put
System.out.println("线程:"+Thread.currentThread().getName()+"正在入队b");
blockingQueue.put("b");
System.out.println("线程:"+Thread.currentThread().getName()+"正在入队c");
blockingQueue.put("c");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
//出队
new Thread(()->{
//向同步队列中入队元素
try {
System.out.println("线程:"+Thread.currentThread().getName()+"正在出队,出队元素为:"+blockingQueue.take());
System.out.println("线程:"+Thread.currentThread().getName()+"正在出队,出队元素为:"+blockingQueue.take());
System.out.println("线程:"+Thread.currentThread().getName()+"正在出队,出队元素为:"+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
线程池
线程池的好处:
1、降低资源的消耗
2、提高响应的速度
3、方便管理。
线程复用、可以控制最大并发数、管理线程
使用Executors 工具类、3大方法获取线程池(工作中不推荐使用,原因在代码中)
package com.fs.demo09;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
线程池:三大方法、7大参数、4种拒绝策略
下面测试线程池的三大创建方法 Executors 工具类
线程池的好处:线程复用、可以控制最大并发数、管理线程
*/
public class ThreadPoolDemo {
public static void main(String[] args) {
// Executors 工具类、3大方法(这个是不推荐使用的,阿里巴巴开发手册写明,因为:请求队列长度为:Integer.MAX_VALUE 大约21亿 会导致OOM,内存溢出)
//创建一个单线程
//public static ExecutorService newSingleThreadExecutor()创建一个使用从无界队列运行的单个工作线程的执行程序。
/*
打印结果发现,执行的10次代码,都是使用的同一个线程
*/
// ExecutorService executorService = Executors.newSingleThreadExecutor();
//Executors.newFixedThreadPool(nThreads);创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程 nThreads - 池中的线程数
/*
发现我们指定的5,打印输出的就只有5个线程来执行任务
*/
// ExecutorService executorService = Executors.newFixedThreadPool(5);
/*
public static ExecutorService newCachedThreadPool()
创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。
这些池通常会提高执行许多短暂异步任务的程序的性能。 调用execute将重用以前构造的线程(如果可用)。
如果没有可用的线程,将创建一个新的线程并将其添加到该池中。 未使用六十秒的线程将被终止并从缓存中删除。
因此,长时间保持闲置的池将不会消耗任何资源。
就是创建一个由场景需求自动变大变小的线程池
*/
//打印发现,使用这个创建的线程池,每次执行的线程个数不一样
ExecutorService executorService = Executors.newCachedThreadPool();
//使用线程线程池
try {
for (int i = 1; i <= 10; i++) {
final int tmp = i;
//void execute(Runnable command)在将来的某个时间执行给定的命令。
// 该命令可以在一个新线程,一个合并的线程中或在调用线程中执行,由Executor实现。
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + "->" + tmp);
});
}
} finally {
//切记要关闭线程池void shutdown()启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。 如果已经关闭,调用没有额外的作用。
//不关闭,线程一直运行
executorService.shutdown();
}
}
}
使用ThreadPoolExecutor(七大参数,四中策略)创建线程池
package com.fs.demo09;
import java.util.concurrent.*;
/*
线程池的七大参数,4种拒绝策略
*/
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
//由于 Executors.new...这样的方法获取的线程池是工具类自己设定的参数,请求队列是Integer.MAX_VALUE,这么大,会导致OOM,内存溢出
//这三个大对象实际源码也是new了ThreadPoolExecutor
//所以我们一般使用线程池就直接使用ThreadPoolExecutor(7大参数)
/*
七大参数
public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
int maximumPoolSize,//最大线程池大小
long keepAliveTime,//超时没有调用释放时间
TimeUnit unit,//超时单位
BlockingQueue<Runnable> workQueue,//阻塞队列
ThreadFactory threadFactory,//线程工厂
RejectedExecutionHandler handler//拒绝策略) {}
四大策略
* new ThreadPoolExecutor.AbortPolicy() // 队列满了,还有人进来,不处理这个人的,抛出异常
* new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里!那个线程丢进来的,就交给拿个线程执行
* new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常!
* new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会抛出异常!
*/
//注意maximumPoolSize这个参数一般不会写死,会通过一个方法获取当前系统的cpu线程大小而定
System.out.println("本机最大线程数为:"+Runtime.getRuntime().availableProcessors());
//创建一个自定义线程池,设置7大参数
ExecutorService executorService = new ThreadPoolExecutor(
2,
Runtime.getRuntime().availableProcessors(),//设置最大线程数为本机的cpu线程数
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
//最大承载:maximumPoolSize+workQueue 最大线程加上等待队列数,我们这就是8+3 = 11
//AbortPolicy()超过了最大承载 RejectedExecutionException 拒绝执行异常
//CallerRunsPolicy()超过了最大承载 不会报错,谁给调用的这个线程,就让这个线程执行,这里是main,所以输出结果:线程:main,执行了:9
//
try {
//开启多个线程
for (int i = 1; i <= 200; i++) {
final int tmp = i;
executorService.execute(()->{
System.out.println("线程:"+Thread.currentThread().getName()+",执行了:"+tmp);
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//切记记得在finally中关闭线程池
executorService.shutdown();
}
}
}
四大函数式接口
四大函数式接口:java.util.function包下
代码解释
package com.fs.demo10;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
/*
四大函数式接口:java.util.function包下
新时代的程序员:lambda表达式、链式编程、函数式接口、Stream流式计算
*/
public class FunctionalInterfaceDemo {
public static void main(String[] args) {
//测试java.util.function Interface Function<T,R>
// FunctionTest();
//测试java.util.function Interface Predicate<T>
// PredicateTest();
//测试java.util.function Interface Supplier<T>
// SupplierTest();
//测试java.util.function Interface Consumer<T>
ConsumerTest();
}
/*
java.util.function Interface Function<T,R>
public interface Function<T,R>
表示接受一个参数并产生结果的函数。
源码:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);//T为方法传递的参数,R为方法的返回值
}
*/
public static void FunctionTest(){
//使用 匿名内部类
// Function<String, String> function = new Function<String, String>() {
// @Override
// public String apply(String str) {
// return str;
// }
// };
//使用Lambda表达式
Function<String, String> function = (str)->{return str;};
//输出调用一下
System.out.println(function.apply("HelloWorld"));
}
/*
@FunctionalInterface public interface Predicate<T>
表示一个参数的谓词(布尔值函数)
//源码:传递一个参数,对参数进行评估,返回一个boolean值
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
*/
public static void PredicateTest(){
//使用 匿名内部类
// Predicate<String> predicate = new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return s.isEmpty();
// }
// };
//使用Lambda表达式
Predicate<String> predicate = (str)->{return str.isEmpty();};
//输出调用一下
System.out.println(predicate.test("HelloWorld"));//false
}
/*
@FunctionalInterface public interface Supplier<T>
代表结果供应商。没有要求每次调用供应商时都会返回新的或不同的结果。
源码: 泛型什么类型,get返回什么
@FunctionalInterface
public interface Supplier<T> {
T get();
}
*/
public static void SupplierTest(){
//使用 匿名内部类
// Supplier<String> supplier = new Supplier<String>() {
// @Override
// public String get() {
// return "HelloWorld";
// }
// };
//使用Lambda表达式
Supplier<String> supplier = ()->{return "HelloWorld";};
//输出调用一下
System.out.println(supplier.get());//HelloWorld
}
/*
@FunctionalInterface public interface Consumer<T>
表示接受单个输入参数并且不返回结果的操作。 与大多数其他功能界面不同, Consumer预计将通过副作用进行操作。
源码:给定一个泛型,accept给定一个参数,但是消费了,不会有任何返回
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
*/
public static void ConsumerTest(){
//使用 匿名内部类
// Consumer<String> consumer = new Consumer<String>() {
// @Override
// public void accept(String str) {
// System.out.println(str);
// }
// };
//使用Lambda表达式
Consumer<String> consumer = (str)-> System.out.println(str);
//输出调用一下
consumer.accept("HelloWorld");//HelloWorld
}
}
什么是Stream流式计算
大数据:存储 + 计算
集合、MySQL 本质就是存储东西的;
计算都应该交给流来操作!
简单案列提交Stream流的好处
package com.fs.demo10;
import java.util.Arrays;
import java.util.List;
/*
什么是Stream流式计算
大数据:存储 + 计算
集合、MySQL 本质就是存储东西的;
计算都应该交给流来操作!
*/
public class StreamDemo {
/*
* 题目要求:一分钟内完成此题,只能用一行代码实现!
* 现在有5个用户!筛选:
* 1、ID 必须是偶数
* 2、年龄必须大于23岁
* 3、用户名转为大写字母
* 4、用户名字母倒着排序
* 5、只输出一个用户!
*/
public static void main(String[] args) {
//创建3个user
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);
//将3个user存放在list集合中
List<User> users = Arrays.asList(u1, u2, u3, u4, u5);
//将users转成Stream流
users.stream()
.filter((user)->{return user.getId()%2==0;})//ID 必须是偶数
.filter((user)->{return user.getAge()>23;})//年龄必须大于23岁
.map((user)->{return user.getName().toUpperCase();})//用户名转为大写字母
.sorted((name1,name2)->{return name2.compareTo(name1);})//用户名字母倒着排序
.limit(1)//只输出一个用户!
.forEach((user)-> System.out.println(user));//输出
}
}
ForkJoin
什么是 ForkJoin
ForkJoin 在 JDK 1.7 , 并行执行任务!提高效率。大数据量!
大数据:Map Reduce (把大任务拆分为小任务)
Future 设计的初衷: 对将来的某个事件的结果进行建模
ForkJoin 特点:工作窃取
这个里面维护的都是双端队列
任务:执行1到1亿的数据叠加
package com.fs.demo11;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
/*
什么是 ForkJoin
ForkJoin 在 JDK 1.7 , 并行执行任务!提高效率。大数据量!
大数据:Map Reduce (把大任务拆分为小任务)
ForkJoin 特点:工作窃取 这个里面维护的都是双端队列 ,
假如有2个AB线程执行两个任务,B线程执行完毕后,发现A线程没有执行完毕,因为是双端队列,就去A线程队列的尾部去拿去A未完成的任务去帮A完成
*/
public class ForkJoinAndStreamDemo {
/*
任务:执行1到1亿的数据叠加
*/
private static Long start; // 1
private static Long end; // 1000000000
public static void main(String[] args) throws ExecutionException, InterruptedException {
start = 1L;
end = 10_0000_0000L;
long a = System.currentTimeMillis();
//普通方法:执行的时间:4408
// Long aLong = commonSolutionMethod();
//使用ForkJoin来计算:需要自己调优
// Long aLong = ForkJoinMethod();
//使用Stream流:执行的时间:532(推荐使用,高效率)
Long aLong = StreamMethod();
System.out.println("执行的结果:" + aLong);
long b = System.currentTimeMillis();
System.out.println("执行的时间:" + (b - a));
}
/*
普通方法,循环执行
执行的结果:500000000500000000
执行的时间:4408
*/
public static Long commonSolutionMethod() {
//定义一个tmp
long tmp = 0L;
for (Long i = start; i <= end; i++) {
tmp += i;
}
return tmp;
}
/*
使用ForkJoin来提高效率
* // 如何使用 forkjoin
* // 1、forkjoinPool 通过它来执行
* // 2、计算任务 forkjoinPool.execute(ForkJoinTask task)
* // 3. 计算类要继承 ForkJoinTask,我们吧我们这个ForkJoinClass类继承一下ForkJoinTask的子类RecursiveTask
*/
public static Long ForkJoinMethod() throws ExecutionException, InterruptedException {
//创建forkJoinPool,来执行我们自己定义的计算类
ForkJoinPool forkJoinPool = new ForkJoinPool();
//通过我们自定义的计算类,创建ForkJoinTask
ForkJoinTask<Long> task = new ForkJoinClass(0L, 10_0000_0000L);
//使用ForkJoinPool的submit(task);方法来执行我们自定义的计算类
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
//获得计算类执行的结果
return submit.get();
}
/*
使用Stream流来快速计算
*/
public static Long StreamMethod(){
Stream并行流 () (]
return LongStream.rangeClosed(0L, 10_0000_0000L).sum();
}
}
ForkJoin继承RecursiveTask类重写compute()计算方法
package com.fs.demo11;
import java.util.concurrent.RecursiveTask;
public class ForkJoinClass extends RecursiveTask<Long> {
private Long start; // 1
private Long end; // 1000000000
// 临界值,高于这个值就会触发forkjoin 递归,线程队列来计算
private Long temp = 10000L;
public ForkJoinClass() {
}
/*
继承RecursiveTask类重写compute()计算方法
*/
@Override
protected Long compute() {
//对参数进行判断,若参数没有达到就使用普通计算方法
if ((end-start)<temp){
//定义一个tmp
long tmp = 0L;
for (Long i = start; i <= end; i++) {
tmp+=i;
}
return tmp;
}else {//递归
//说明计算的数量大于临界值,启用forkjoin线程队列来计算
//计算任务拆分
long middle = (start + end) / 2; // 中间值
//创建两个forkjoinClass来执行,第一个执行拆分前面的
ForkJoinClass forkJoinClass = new ForkJoinClass(start, middle);
forkJoinClass.fork();// 拆分任务,把任务压入线程队列
//第二个创建拆分后面的
ForkJoinClass forkJoinClass1 = new ForkJoinClass(start + 1, middle);
forkJoinClass1.fork();
//返回两个ForkJoin执行的值
return forkJoinClass.join()+forkJoinClass1.join();
}
}
public ForkJoinClass(Long start, Long end) {
this.start = start;
this.end = end;
}
}
得知结论,使用Stream流最快,但是使用ForkJoin更加灵活,需要自己调整临界值,来让计算更加高效
JMM
JMM : Java内存模型,不存在的东西,概念!约定!
关于JMM的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷回主存。
2、线程加锁前,必须读取主存中的最新值到工作内存中!
3、加锁和解锁是同一把锁
线程 工作内存 、主内存
内存交互操作
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
不允许一个线程将没有assign的数据从工作内存同步回主内存
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
对一个变量进行unlock操作之前,必须把此变量同步回主内存
JMM对这八种操作规则和对volatile的一些特殊规则就能确定哪里操作是线程安全,哪些操作是线程不安全的了。但是这些规则实在复杂,很难在实践中直接分析。所以一般我们也不会通过上述规则进行分析。更多的时候,使用java的happen-before规则来进行分析。
请你谈谈你对 Volatile 的理解
Volatile 是 Java 虚拟机提供轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排
可见性代码演示
package com.fs.demo12;
import java.util.concurrent.TimeUnit;
public class VolatileDemo {
//定义一个变量
private volatile static int num = 0;
public static void main(String[] args) throws InterruptedException {
//创建一个线程来监视变量num
new Thread(()->{
//循环判断,因为num不可见,就会出现死循环,当添加了volatile就可以解决
while (num == 0){
}
}).start();
//main线程暂停1秒
TimeUnit.SECONDS.sleep(3);
//main线程将num进行修改
num = 1;
System.out.println("main线程修改的num:"+num);
}
}
/*
执行结果为打应很多次0,因为是我们num是其他线程不可见的,当main线程进行了修改,其余线程拿到的还是 原来的,
所以我们需要添加volatile关键字,来解决可见性,相当于一旦进行修改,会通知其余线程
*/
不保证原子性代码演示(使用synchronized 或者lock解决)
package com.fs.demo12;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
Volatile,不能解决原子性
*/
public class VolatileAtomDemo {
//Volatile,不能解决原子性
private volatile static int num = 0;
//使用synchronized解决
public synchronized static void add(){
num++;
}
public static void main(String[] args) {
//创建多个线程来让我们的num++
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
// num++;
add();
}
}).start();
}
//理论上执行完毕后,这个num为2万,但实际不是,因为线程不安全,使用volatile不能解决原子性,所以使用
//判断线程执行完毕后在
while (Thread.activeCount()>2){ // main gc
//public static void yield()对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。 调度程序可以自由地忽略这个提示。
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
不保证原子性代码演示(使用java.util.concurrent.atomic原子类解决)
package com.fs.demo12;
import java.util.concurrent.atomic.AtomicInteger;
/*
不使用synchronized类和lock锁来解决原子安全问题
java.util.concurrent.atomic
Class AtomicInteger
使用原子类
*/
public class VolatileAtomPojoDemo {
//Volatile,不能解决原子性
private static AtomicInteger num = new AtomicInteger();
//使用原子类的+1方法解决
public static void add(){
// num++; // 不是一个原子性操作
//是原子类加1,使用原子类,解决 原子性问题
num.incrementAndGet();
/*
点击源码得:最终调用这样的方法:原子类原理为CAS,比较并交换
//这些类的底层都直接和操作系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在!
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 static void main(String[] args) {
//创建多个线程来让我们的num++
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
add();
}
}).start();
}
//理论上执行完毕后,这个num为2万,但实际不是,因为线程不安全,使用volatile不能解决原子性,所以使用
//判断线程执行完毕后在
while (Thread.activeCount()>2){ // main gc
//public static void yield()对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。 调度程序可以自由地忽略这个提示。
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
浅谈指令重排
什么是 指令重排:你写的程序,计算机并不是按照你写的那样去执行的。
源代码–>编译器优化的重排–> 指令并行也可能会重排–> 内存系统也会重排—> 执行
处理器在进行指令重排的时候,考虑:数据之间的依赖性!
int x = 1; // 1
int y = 2; // 2
x = x + 5; // 3
y = x * x; // 4
我们所期望的:1234 但是可能执行的时候回变成 2134 1324
可不可能是 4123!
volatile可以避免指令重排:
内存屏障。CPU指令。作用:
1、保证特定的操作的执行顺序!
2、可以保证某些变量的内存可见性 (利用这些特性volatile实现了可见性)
Volatile 是可以保持 可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
单例模式不安全
我们使用构造方法私有化来创建单例模式是不安全的,可以通过反射来创建对象,就不保证单例了,所以不安全
饿汉
package com.fs.demo13;
/*
饿汉式
*/
public class HungryManDemo {
// 可能会浪费空间,下面假如很多依耐资源,懒汉式因为一会直接加载到内存,导致空间浪费
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];
//单列模式首先构造方法私有
private HungryManDemo(){
}
//创建对象初始化一个对象final,表示这个对象只有 一个
private final static HungryManDemo hungryManDemo = new HungryManDemo();
//创建一个方法来返回对象
public static HungryManDemo getInstance(){
return hungryManDemo;
}
//main方法测试
public static void main(String[] args) {
//由于构造方法私有了,所以我们需要通过getInstance获取对象
//我们获取多然后比较一下,看看是不是同一个对象 次试试
HungryManDemo instance = HungryManDemo.getInstance();
HungryManDemo instance02 = HungryManDemo.getInstance();
//输出一下
System.out.println(instance == instance02);
}
}
懒汉
通过下面的代码,解释了单例模式的不安全,可以通过反射来创建对象
package com.fs.demo13;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
/*
DCL 懒汉式
*/
public class LazybonesDemo {
//为了防止有人使用反射拿到无参构造,我们这里定一个字段来做标志
private static boolean xiaofu = false;
//构造方法私有
private LazybonesDemo(){
//加上class锁,class锁只有一把
synchronized (LazybonesDemo.class) {
if (!xiaofu) {
xiaofu = true;
} else {
throw new RuntimeException("请勿尝试使用反射来创建多个对象~~~");
}
}
}
private volatile static LazybonesDemo lazybonesDemo;
//懒汉式,需要才给
public static LazybonesDemo getInstance(){
if (lazybonesDemo ==null) {
//双重锁模式
synchronized (LazybonesDemo.class) {
if (lazybonesDemo == null) {
lazybonesDemo = new LazybonesDemo();
}
}
}
return lazybonesDemo;
}
//main测试
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, NoSuchFieldException {
// LazybonesDemo instance = LazybonesDemo.getInstance();
// LazybonesDemo instance1 = LazybonesDemo.getInstance();
// System.out.println(instance == instance1);//true
//这种单例不安全,因为可以通过反射创建对象,我们使用一个flag来标记这个对象是否被创建
Constructor<LazybonesDemo> declaredConstructor = LazybonesDemo.class.getDeclaredConstructor(null);
//暴力反射
declaredConstructor.setAccessible(true);
//获取对象
LazybonesDemo lazybonesDemo = declaredConstructor.newInstance();
// System.out.println(lazybonesDemo == instance);//false,这样就说明我们的对象并不是单例,可以通过反射创建对象
//反射通过无参构造获取2个对象的时候由于flag改变后,就会抛出异常
// LazybonesDemo lazybonesDemo1 = declaredConstructor.newInstance();
//那么就通过反射来改变字段
Field xiaofu = LazybonesDemo.class.getDeclaredField("xiaofu");
xiaofu.setAccessible(true);
//改变字段flag为false
xiaofu.set(lazybonesDemo,false);
//从新通过反射获取
LazybonesDemo lazybonesDemo1 = declaredConstructor.newInstance();
System.out.println(lazybonesDemo);
System.out.println(lazybonesDemo1);
//所以,道高一尺,魔高一丈,所以单例不安全.解决方案,使用枚举
}
}
解决方案
使用枚举类,报错IllegalArgumentException:不能反射地创建枚举对象
package com.fs.demo13;
import java.lang.reflect.Constructor;
/*
单例不安全,可以通过反射来创建对象,解决方案使用枚举,
报错IllegalArgumentException:不能反射地创建枚举对象
什么是枚举?
我们学习过单例模式,即一个类只有一个实例。而枚举其实就是多例,一个类有多个实例,但实例的个数不是无穷的,是有限个数的。
*/
public enum EnumDemo {
//定义枚举项
INSTANCE;
//其实枚举类和正常的类一样,可以有实例变量,实例方法,静态方法等等,只不过它的实例个数是有限的,不能再创建实例而已。
private String name;
//枚举类也可以有构造器,构造器默认都是private修饰,而且只能是private。因为枚举类的实例不能让外界来创建!
private EnumDemo() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//向外提供方法获取对象
public static EnumDemo getInstance() {
return INSTANCE;
}
}
//测试类
class TestEnum {
public static void main(String[] args) throws Exception{
//获取对象
EnumDemo instance = EnumDemo.getInstance();
//反射获取对象
Constructor<EnumDemo> declaredConstructor = EnumDemo.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumDemo enumDemo = declaredConstructor.newInstance();//报错IllegalArgumentException:不能反射地创建枚举对象
//解决了,通过反射创建对象
System.out.println(instance == enumDemo);
instance.setName("xiaofu");
System.out.println(instance.getName());
}
}
浅谈CAS
什么是 CAS
CAS compareAndSet : 比较并交换!
java.util.concurrent.atomic包下的类,大量使用了unsafe类中的方法 CAS 比较并交换
Unsafe 类 中的CAS方法是直接操作内存的,效率很高
CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就
一直循环!
缺点:
1、 循环会耗时
2、一次性只能保证一个共享变量的原子性
3、ABA问题
package com.fs.demo14;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;
/*
什么是 CAS
CAS compareAndSet : 比较并交换!
java.util.concurrent.atomic包下的类,大量使用了unsafe类中的方法 CAS 比较并交换
Unsafe 类 中的CAS方法是直接操作内存的,效率很高
CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就
一直循环!
缺点:
1、 循环会耗时
2、一次性只能保证一个共享变量的原子性
3、ABA问题
*/
public class CASDemo {
public static void main(String[] args) {
//创建一个原子类,点击原子类,发现源码中方法大量调用unsafe类中的方法,
// 而这类中的方法compareAndSwap***方法,这就是比较并交换
AtomicInteger atomicInteger = new AtomicInteger(1);
//boolean compareAndSet(int expect, int update) 如果当前值 ==为预期值,则将该值原子设置为给定的更新值。
//如果预期的值为1,我们将其变为2
System.out.println(atomicInteger.compareAndSet(1, 2));//true
//输出一下
System.out.println(atomicInteger.get());//2
//如果预期值为2,就将其变为1
System.out.println(atomicInteger.compareAndSet(2, 1));
System.out.println(atomicInteger.get());//1
}
}
解决ABA问题(思想乐观锁)
CAS : ABA 问题(狸猫换太子)
假设我们有2条线程对同一个变量进行CAS
num = 1;
A线程:cas(1,2)
B线程:cas(1,3) 然后又执行 cas(3,1)
我们线程B将num其实是做了改变的,但是我们A是不知道的,这就是ABA问题
解决ABA问题
java.util.concurrent.atomic
Class AtomicStampedReference
引入原子引用! 对应的思想:乐观锁!version = 1
package com.fs.demo14;
import com.fs.demo10.User;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
/*
CAS : ABA 问题(狸猫换太子)
假设我们有2条线程对同一个变量进行CAS
num = 1;
A线程:cas(1,2)
B线程:cas(1,3) 然后又执行 cas(3,1)
我们线程B将num其实是做了改变的,但是我们A是不知道的,这就是ABA问题
解决ABA问题
java.util.concurrent.atomic
Class AtomicStampedReference<V>
引入原子引用! 对应的思想:乐观锁!version = 1
*/
public class ABADemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(1);
//=====B====
System.out.println(atomicInteger.compareAndSet(1, 3));
System.out.println(atomicInteger.compareAndSet(3, 1));
//=====A====我们这里执行的时候,其实数据已经被B玩了一遍了,
System.out.println(atomicInteger.compareAndSet(1, 2));
System.out.println(atomicInteger.get());
}
}
//使用
//java.util.concurrent.atomic
//Class AtomicStampedReference<V>
class ABAOKDemo{
private static User user = new User(1,"xiaofu",2);
//创建一个AtomicStampedReference维护对象引用以及整数“印记”,可以原子更新。这个对象只有有参构造
//AtomicStampedReference(V initialRef, int initialStamp)initialRef 维护的对象 initialStamp印记,类似于乐观锁的version
private static AtomicStampedReference<User> atomicStampedReference = new AtomicStampedReference(user,1);
public static void main(String[] args) {
User user2 = new User(2,"xiaohua",3);
User user3 = new User(3,"xiaowang",3);
//获取版本,注意为了演示,我这里是死的,才会出现
int stamp = atomicStampedReference.getStamp();//1
//====B====
//compareAndSet(参数解释)
/*
* @param expectedReference引用的期望值
* @param newReference是引用的新值
* @param expectedStamp戳记的期望值
* @param newStamp为邮票的新值
*/
//我期望的是user,是的话就变成user3,期望的version 是期望的就version+1
System.out.println(atomicStampedReference.compareAndSet(ABAOKDemo.user, user3,stamp, atomicStampedReference.getStamp() + 1));//true
//=====A====
//由于版本号是1,我是B线程对version+1了,我们这里就执行不成功
System.out.println(atomicStampedReference.compareAndSet(user, user2, stamp, atomicStampedReference.getStamp() + 1));//false
User reference = atomicStampedReference.getReference();
System.out.println(reference);//这里就是A修改的,说明B么有修改成功,解决了cas的ABA问题
}
}
公平锁,非公平锁,可重入锁,自旋锁,死锁
下面的代码可能有错误
1、公平锁、非公平锁
公平锁: 非常公平, 不能够插队,必须先来后到!
非公平锁:非常不公平,可以插队 (默认都是非公平)
2、可重入锁
可重入锁(递归锁)
3,自旋锁
4,死锁
可重入锁
package com.fs.demo15;
import java.util.concurrent.TimeUnit;
/*
1、公平锁、非公平锁
公平锁: 非常公平, 不能够插队,必须先来后到!
非公平锁:非常不公平,可以插队 (默认都是非公平)
2、可重入锁
可重入锁(递归锁)
*/
public class Demo01 {
//可重入锁
public static void main(String[] args) throws InterruptedException {
ReentrantLockTest reentrantLockTest = new ReentrantLockTest();
//创建一个线程来执行测试
new Thread(()->{reentrantLockTest.testA();},"A").start();
//我们来延迟一下
TimeUnit.SECONDS.sleep(3);
new Thread(()->{reentrantLockTest.testA();},"B").start();
}
}
//测试可重入锁synchronized实现
class ReentrantLockTest{
public synchronized void testA(){
System.out.println(Thread.currentThread().getName()+",您已进入第一道门");
//调用testB拿到testB需要的锁,相当于我们这里拿到了2把锁,
testB();//一把tesaA需要的锁,一把testB需要的锁
System.out.println(Thread.currentThread().getName()+",您已退出第一道门");
}
public synchronized void testB(){
System.out.println(Thread.currentThread().getName()+",您已进入第二道门");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+",您已退出第二道门");
}
}
自旋锁
package com.fs.demo15;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/*
自旋锁
我们来自定义一个锁测试
*/
public class spinlockDemo {
//测试自旋
public static void main(String[] args) {
//创建自旋锁对象
SpinlockTest spinlockTest = new SpinlockTest();
//创建两个线程
new Thread(()->{
//加锁
spinlockTest.myLock();
try {
System.out.println(Thread.currentThread().getName()+"执行了");
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
spinlockTest.myUnLock();
}
},"A").start();
new Thread(()->{
//加锁
spinlockTest.myLock();
try {
System.out.println(Thread.currentThread().getName()+"执行了");
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
spinlockTest.myUnLock();
}
},"B").start();
}
}
class SpinlockTest{
//创建一个原子引用类
AtomicReference<Thread> atomicReference = new AtomicReference<>();
//加锁
public void myLock(){
//static Thread currentThread() 返回对当前正在执行的线程对象的引用。
Thread thread = Thread.currentThread();
System.out.println("线程:"+thread.getName()+"加锁");
//自旋锁,期望当前引用为null,我们就给他thread,执行为false就自旋,否则就出旋
while (!atomicReference.compareAndSet(null, thread)){
}
}
//解锁
public void myUnLock(){
Thread thread = Thread.currentThread();
System.out.println("线程:"+thread.getName()+"解锁");
atomicReference.compareAndSet(thread,null);
}
}
死锁
package com.fs.demo15;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
死锁
A拿A锁不释放--->想去拿B锁
B拿B锁不释放--->想去拿A锁
*/
public class DeadLockDemo {
public static void main(String[] args) {
//创建DeadLockTest
DeadLockTest deadLockTest = new DeadLockTest();
//创建2个线程
new Thread(()->{deadLockTest.TestA();},"A").start();
new Thread(()->{deadLockTest.TestB();},"B").start();
//会出现死锁现象,当工作中遇见死锁,我们是很难通过代码来查询的
//就使用java的几个命令来查看堆栈信息
/*
1、使用 jps -l 定位进程号
2、使用 jstack 进程号 找到死锁问题
*/
/*
上面的命令我们在cmd中查看到了出现死锁的代码
Found one Java-level deadlock:
=============================
"B":
waiting for ownable synchronizer 0x0000000770c849e8, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "A"
"A":
waiting for ownable synchronizer 0x0000000770c84a18, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "B"
Java stack information for the threads listed above:
===================================================
"B":
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x0000000770c849e8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at com.fs.demo15.DeadLockTest.TestB(DeadLockDemo.java:58)
at com.fs.demo15.DeadLockDemo.lambda$main$1(DeadLockDemo.java:20)
at com.fs.demo15.DeadLockDemo$$Lambda$2/1831932724.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"A":
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x0000000770c84a18> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at com.fs.demo15.DeadLockTest.TestA(DeadLockDemo.java:48)
at com.fs.demo15.DeadLockDemo.lambda$main$0(DeadLockDemo.java:18)
at com.fs.demo15.DeadLockDemo$$Lambda$1/1096979270.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
*/
}
}
//测试死锁
class DeadLockTest {
private Lock lockA = new ReentrantLock();
private Lock lockB = new ReentrantLock();
public void TestA(){
//获得a锁
lockA.lock();
System.out.println("线程:"+Thread.currentThread().getName()+"拿到LockA锁了");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:"+Thread.currentThread().getName()+"尝试获取LockB锁");
lockB.lock();
//解锁
lockA.unlock();
}
public void TestB(){
lockB.lock();
System.out.println("线程:"+Thread.currentThread().getName()+"拿到LockB锁了");
System.out.println("线程:"+Thread.currentThread().getName()+"尝试获取LockA锁");
lockA.lock();
//解锁
lockB.unlock();
}
}
当代码出现死锁,我们无法定位死锁位子,使用命令查看
1、使用 jps -l 定位进程号
2、使用 jstack 进程号 找到死锁问题
上面的代码中有方法