JavaJUC学习篇
什么是JUC
java.util 工具包、包、分类
线程和进程
进程:一个程序,比如qq,酷我音乐啥的一个进程往往包含一个多个线程。
线程:java默认两个线程一个main,一个GC
java线程实现的方式:
继承Thread,实现Runnable接口,实现callable
并行和并发
并行:一个cpu处理多个任务
并发:是指多个处理器或者是多核的处理器同时处理多个不同的任务。
线程的状态:
创建 –》 就绪 –》 运行 –》阻塞 –>死亡
(我们通常分为五种,但是有些情况下分为7种,加的两种为等待和超时等待)
wait和sleep的区别
wait来自Object sleep来自Thread
其次:
wait会释放锁 sleep不会
使用范围不一样:
wait必须放到同步块中
sleep没有要求
Lock和Synchonized
传统的synchonized锁
synchonized(){
}
括号里边一般两种
1对象
2类名.class
还有 private synchonized void name(){}
Lock接口
ReentrantLock: 可重用锁
readLock: 读锁
writeLock:写锁
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.xh.juc.day02;
/**
* @Title
* @Description
* @author 罗小黑
* 线程之间的通信问题,常用的是生产者和消费者问题。 等待唤醒,通知唤醒。
* 线程交替执行 A B操作同一个变量 num = 0;
* A num + 1
* B num - 1
* @return
* @date 2022/10/2 16:29
*/
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) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
}
}
// 生产者消费者: 判断等待 ,业务 , 通知
class Date{
//数字资源锁
private int number = 0;
public synchronized void increment() throws InterruptedException {
if(number != 0){
//等待
this.wait();
}
//通知其他线程我加1完毕了
number ++ ;
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四个线程的情况下出现问题
public class B {
public static void main(String[] args) {
Date date = new Date();
//创建线程A进行加
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
date.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
date.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
date.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
date.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"D").start();
}
}
A=>1
C=>0
B=>1
A=>2
B=>3
C=>2
C=>1
C=>0
B=>1
A=>2
B=>3
C=>2
B=>3
A=>4
D=>3
D=>2
D=>1
D=>0
A=>1
D=>0
以上原因是由于线程产生了虚假唤醒,导致出现问题。
解决办法,我们将if改成while判断
class Date1{
//数字资源锁
private int number = 0;
public synchronized void increment() throws InterruptedException {
while(number != 0){//if改成while解决线程虚拟唤醒
//等待
this.wait();
}
//通知其他线程我加1完毕了
number ++ ;
System.out.println(Thread.currentThread().getName() + "=>" +number);
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
while(number == 0){//改成while解决线程虚拟唤醒
//等待
this.wait();
}
number --;
//通知其他线程我-1完毕了
System.out.println(Thread.currentThread().getName() + "=>" +number);
this.notifyAll();
}
}
Lock解决生产者和消费者问题JUC版本的
基本实现
public class C {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"D").start();
}
}
class Data{
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//加一操作
/* condition.await();//等待相当于wait
condition.signalAll();//唤醒全部*/
public void increment () throws InterruptedException {
try {
lock.lock();
while(number != 0){
condition.await();
}
number ++;
System.out.println(Thread.currentThread().getName()+"=>"+ number);
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
//减一操作
public void decrement () throws InterruptedException {
try {
lock.lock();
while (number == 0) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() +"=>"+ number);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
任何一个新的技术绝对不是仅仅覆盖了一起的技术,一定会在原来的技术上进行相应的拓展
Condtion实现精准唤醒
public class D {
public static void main(String[] args) {
Data2 data2 = new Data2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data2.printA();
}
},"A").start();;
new Thread(()->{
for (int i = 0; i < 10; i++) {
data2.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data2.printC();
}
},"C").start();;
}
}
class Data2{
private int number = 1; //1A 2B 3C
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
public void printA(){
lock.lock();
try{
//业务,判断 ->执行 ->通知
while(number != 1){
//等待
condition.await();
}
System.out.println(Thread.currentThread().getName()+ "==>AA" );
number = 2;
condition1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try{
//业务,判断 ->执行 ->通知
while ( number != 2){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+ "==>BB");
number = 3;
condition2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try{
//业务,判断 ->执行 ->通知
while (number != 3){
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "==>CC");
number =1;
condition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
什么是锁,如何判断锁对象:
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone =new Phone();
//并不是谁先谁后,而是由于锁的存在,谁先拿到谁执行
new Thread(()->{
try {
phone.sendSms();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"A").start();
TimeUnit.SECONDS.sleep(1);//休眠一秒,比较谁快
new Thread(()->{
phone.sendTel();
},"B").start();
}
}
class Phone{
public synchronized void sendSms() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
System.out.println("发短信");
}
public synchronized void sendTel(){
System.out.println("打电话");
}
}
public class Test2 {
public static void main(String[] args) throws InterruptedException {
//创建两个不同的对象,每个对象都 持有一把锁,故而有两把锁
Phone2 phone2 =new Phone2();
Phone phone = new Phone();
//并不是谁先谁后,而是由于锁的存在,谁先拿到谁执行
new Thread(()->{
try {
//存在锁 phone2.sendSms();
phone.sendSms();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"A").start();
TimeUnit.SECONDS.sleep(1);//休眠一秒,比较谁快
new Thread(()->{
// phone2.hello();
phone2.sendTel();
},"B").start();
}
}
class Phone2{
//synchronized锁的对象时方法的调用者
public synchronized void sendSms() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
System.out.println("发短信");
}
public synchronized void sendTel(){
System.out.println("打电话");
}
//没有加锁,普通方法,故而没有收到锁的限制
public void hello(){
System.out.println("说hello");
}
}
public class Test3 {
public static void main(String[] args) throws InterruptedException {
//加了static保证锁住类,而不是之前的对象了
Phone3 phone3 =new Phone3();
Phone phone = new Phone();
new Thread(()->{
try {
//存在锁 phone2.sendSms();
phone.sendSms();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"A").start();
TimeUnit.SECONDS.sleep(1);//休眠一秒,比较谁快
new Thread(()->{
// phone2.hello();
phone3.sendTel();
},"B").start();
}
}
class Phone3{
//synchronized锁的对象时方法的调用者
//static类一加载就有了 此时锁的是类而不是一开始的对象了锁的是Phone3.class
public static synchronized void sendSms() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
System.out.println("发短信");
}
public static synchronized void sendTel(){
System.out.println("打电话");
}
//没有加锁,普通方法,故而没有收到锁的限制
public void hello(){
System.out.println("说hello");
}
}
import java.util.concurrent.TimeUnit;
public class Test4 {
public static void main(String[] args) {
Phone4 phone4 = new Phone4();
Phone2 phone2 = new Phone2();
new Thread(()->{
try {
phone4.sendSms();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
new Thread(()->{
phone2.sendTel();
}).start();
}
}
class Phone4{
public static synchronized void sendSms() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
new this 具体的一个手机
static Class 唯一的一个模板
集合类是不安全的
List
//java.util.ConcurrentModificationException 并发修改异常
public class ListTest {
public static void main(String[] args) {
//并发下ArrayList是不安全的。 java.util.ConcurrentModificationException
/*
解决办法,使用Vector可以保证不报错
List<String> list = new Vector<>();
解决方法二,使用Collection将ArrayList转变成安全的,使用锁机制
List<String> list = Collections.synchronizedList(new ArrayList<>());
解决办法三:使用JUC下的CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>(); //JUC里边自带的解决办法
*/
//copyOnWrite写入时复制 COW计算程序领域的一种优化策略
//多个线程调用的时候,list,读取的时候固定的,写入(覆盖)
//写入时候避免覆盖,造成数据问题
// copyOnWrite 比vector nb在哪?
// List<String> list = new ArrayList<>();
List<String> list = new CopyOnWriteArrayList<>(); //JUC里边自带的解决办法
for (int i = 1; i <= 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
Set的不安全
/**
* @Title java.util.ConcurrentModificationException
* @Description 同理可得还是出现并发修改异常
* @author 罗小黑
* @date 2022/10/5 14:22
*/
public class SetTest {
public static void main(String[] args) {
//解决办法,还是如以前一样
// Set<String> set = new HashSet<>();
//方法一使用Collection.synchronizedSet方法解决
// Set<String> set = Collections.synchronizedSet(new HashSet<>());
//方法二使用JUC下的Copy...
//Set<String > set = new CopyOnWriteArraySet<>();
Set<String > set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}
HashSet的底层就是HashMap
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(); //不可变的值
HashMap
回顾hashMap的 简单用法加载因子为0.75
//ConcurrentModificationException存在并发修改异常
public class HashMapTest {
public static void main(String[] args) {
//map 是这样子用的吗?不是,工作中不用HashMap(16,0.75)
//默认等价干什么?
ConcurrentModificationException存在并发修改异常
//Map<String ,Object> map = new HashMap<>();
//解决办法一,还是使用 Map<String ,String> map = Collections.synchronizedMap(new HashMap<>());
//Map<String ,String> map = Collections.synchronizedMap(new HashMap<>());
//解决办法二
Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
Callable重点讲解
- 有返回值
- 可以抛出异常
- 方法不同,run()/call()
代码测试
public class CallabelTest {
//两者对比,看到这里实现的Runnable接口没有返回值
//new Thread (new Runnable()).start();
//new Thread (new FutureTask<V>()).start();
//new Thread (new FutureTask<V>(Callable)).start();
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Thread().start(); //怎么启动Callable
MyThread1 thread = new MyThread1();
FutureTask futureTask = new FutureTask(thread);
// 适配类
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start(); //结果会被缓存,效率会高
Integer o = (Integer)futureTask.get();//获取callbale的返回结果
System.out.println(o);
}
}
//实现Runnable接口
class MyThread implements Runnable{
@Override
public void run() {
}
}
//实现Callable接口
class MyThread1 implements Callable<Integer>{
//对比发现,这里实现的Callable接口是存在返回值的
@Override
public Integer call() throws Exception {
System.out.println("Call()");
return 123;
}
}
细节:
- 存在缓存
- 结果可能需要等待,会阻塞
常用的辅助类(必须掌握)
CountDownLatch
//计数器
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//总数是6,必须要执行任务的时候,再使用
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread( ()->{
System.out.println(Thread.currentThread().getName() + " \t GO OUT");
countDownLatch.countDown(); //数量减1
},String.valueOf(i)).start();
}
countDownLatch.await(); //等待计数器归零,让向下执行
//保证数据全部执行之后才将程序结束
//countDownLatch.countDown(); // -1
System.out.println("关门");
}
}
减法计数器:
原理是数量减一:
countDownLatch.countDown(); // -1
countDownLatch.await(); //等待计数器归零,让向下执行
每次有线程调用countDown()数量1,假设计数器变为0,countDownLatch.await就会被唤醒,继续执行!
CycliBarrier: 加法计数器
public class CycliBarrierDemo {
public static void main(String[] args) {
//如果这里的数值没有在for循环中体现的话,就不能结束事件,也就会一直运行,占用程序运行
CyclicBarrier cyclicBarrier = new CyclicBarrier(6,()->{
System.out.println("事件完成");
});
//加法计数器
for (int i = 1; i < 7; i++) {
final int temp = i;
//lambda能操作到i吗 --不能直接操作到
new Thread( ()->{
System.out.println(Thread.currentThread().getName() + "收集"+ temp + "个龙珠"); //我们发现是操作不到i的
try {
cyclicBarrier.await(); //等待
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
}).start();
}
}
}
Semaphore : 信号量
public class SemaphoreDemo {
public static void main(String[] args) {
//线程数量;停车位,限流
Semaphore semaphore = new Semaphore(6);
for (int i = 0; i < 6; i++) {
new Thread(()->{
//acquire()得到
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+ "抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+ "离开车位");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
semaphore.release();
}
//release()释放
},String.valueOf(i)).start();
}
}
}
原理:
semaphore.acquire();获得,假如已经满了,等待,等待被释放
semaphore.release();释放,会将当前的信号量释放 +1 ,然后唤醒等待的线程。
读写锁
ReadWriteLock:读可以被多个线程读,但写的时候只能是一个线程写
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCacheLock myCache = new MyCacheLock();
for (int i = 0; i < 5; i++) {
final int temp = i; //lamada表达式中不能直接读取i,只能通过这样的转换
//只做写入的操作
new Thread(()->{
myCache.put(temp + "",temp +"");
},String.valueOf(i)).start();
}
//只做读取的操作
for (int i = 0; i < 5; i++) {
final int temp = i; //lamada表达式中不能直接读取i,只能通过这样的转换
//只做写入的操作
new Thread(()->{
myCache.get(temp + "");
},String.valueOf(i)).start();
}
}
}
//未加锁导致,我在写入1的时候由于线程影响可能会产生1没写入完成,
// 二也进行写入了
class MyCache{
private volatile Map<String,Object> map = new HashMap<>();
//这个是一个存的过程
public void put(String key , Object obj){
System.out.println(Thread.currentThread().getName()+ "写入"+ key);
map.put(key,obj);
System.out.println(Thread.currentThread().getName()+"写入成功");
}
//这个是一个取的过程
public void get(String key){
System.out.println(Thread.currentThread().getName()+ "读取"+ key);
Object obj = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取成功");
}
}
class MyCacheLock{
private volatile Map<String,Object> map = new HashMap<>();
//private Lock lock = new ReentrantLock(); //普通锁
//读写锁,存写入的时候,只希望同时只有一个线程写
private ReadWriteLock lock = new ReentrantReadWriteLock();
//这个是一个存的过程
public void put(String key , Object obj){
//这个时候就是进行枷锁了
lock.writeLock().lock();
try{
System.out.println(Thread.currentThread().getName()+ "写入"+ key);
map.put(key,obj);
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 obj = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取成功");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.readLock().unlock();
}
}
}
阻塞队列
队列(FIFO)
写入的时候:如果队列是满的,那么久必须阻塞等待。
取出来的时候:如果队列是空的,那么必须阻塞等待生产。
BlockingQueue
在线程下,我们使用的时候需要注意阻塞队列是多线程下并发处理,线程池处理。
学会使用队列
添加,移除
四组API(熟练掌握并使用 )
方式 | 抛出异常 | 不会抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer(,) |
移除 | remove | poll | take | poll(,) |
判断队列首位 | element | peek | – | – |
- 抛出异常
```java
public class BlockingQueueDemo {
/* Collections
List
Set
*/
public static void main(String[] args) {
test1();
}
/*
抛出异常
*/
public static void test1(){
//队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
/*由于我定义的是3个值,故而如果添加四个值的话导致保持
队列满了,这就说明和我们创建一个数组一样,定于一个大小,超出这个大小就好报错
抛出异常 Queue full*/
// System.out.println(blockingQueue.add("d"));
//讲队列的所有添加值全部移除,使得队列为空
// 一次只能移除一个
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
/*
次数队列为空,我们可以发现将会产生问题
java.util.NoSuchElementException
*/
System.out.println(blockingQueue.remove());
}
}
- 不会抛出异常
/*
不抛出异常
*/
public static void test2(){
//设置队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
//不会抛出异常
System.out.println(blockingQueue.offer("d"));//false
System.out.println(blockingQueue.peek()); //判断队首元素
System.out.println("===========================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll()); //null
}
- 阻塞 , 等待
/*
等待,阻塞(一直阻塞)
*/
public static void test3() throws InterruptedException {
//老样子设置队列大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
//一直阻塞
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// blockingQueue.put("d");//队列没有位置
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
//会导致一直阻塞
// System.out.println(blockingQueue.take());
}
- 超时等待
/*
超时等待
*/
public static void test4() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
//超时等待啊,超过两秒钟就退出
blockingQueue.offer("d",2, TimeUnit.SECONDS);
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
blockingQueue.poll(2,TimeUnit.SECONDS);//超过2秒钟就退出
}
SynchronousQueue
/*
同步队列
和其他的是不一样的,SynchronousQueue不存储元素
put了一个元素,必须从里边先take出来,否则不能在put进去值
*/
public class SynchronousQueueDemo {
public static void main(String[] args) {
//同步队列
SynchronousQueue 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) {
throw new RuntimeException(e);
}
},"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) {
throw new RuntimeException(e);
}
},"T2").start();
}
}
线程池(重点)
池化技术
程序的运行,本质:占用系统的资源,优化资源的使用!=>池化技术
线程池,连接池,内存池,对象池。。。 创建,销毁,十分浪费资源
池化技术,事先准备好一些资源,有人要用,就可以来我这里拿,用完之后还给我
线程池的好处
- 降低资源的消耗
- 提高响应的速度
- 方便管理
线程复用,可以控制最大的并发数,管理线程
/*
Executor 工具类,里边有三大方法
*/
public class Demo1 {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newSingleThreadExecutor();//创建一个单例的线程池
ExecutorService threadPool = Executors.newCachedThreadPool();//创建一个缓存的线程池,遇强则强,遇弱则弱
//ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定的线程大小
//TODO 线程使用完毕是要关闭的
try {
for (int i = 0; i < 10; i++) {
final int temp = i;
//new Thread(()->{}).start();//常规的创建线程的方式
//现在线程池的创建方式 threadPool.execute(()->{});
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+ "=>" + "OK");
});
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
threadPool.shutdown();
}
}
}
7大参数
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, //最大21亿
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
//我们发现都是调用 ThreadPoolExecutor
public ThreadPoolExecutor(
int corePoolSize, //创建线程创大小
int maximumPoolSize, //最大核心线程的大小
long keepAliveTime, //超时了,没人调用就好释放
TimeUnit unit, //超时单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂,创建线程的,一般不用动
RejectedExecutionHandler handler //拒绝策略) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
/*
Executor 工具类,里边有三大方法
七大参数
四种拒绝策略
new ThreadPoolExecutor.AbortPolicy() //银行满了,但还有人进去
new ThreadPoolExecutor.CallerRunsPolicy() //哪来的去哪里
new ThreadPoolExecutor.DiscardPolicy() //队列满了不会抛出异常,会丢到任务
new ThreadPoolExecutor.DiscardOldestPolicy()//队列满了,尝试和最早的去竞争
*/
public class Demo1 {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newSingleThreadExecutor();//创建一个单例的线程池
// ExecutorService threadPool = Executors.newCachedThreadPool();//创建一个缓存的线程池,遇强则强,遇弱则弱
//ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定的线程大小
/*
七大参数 ,自定义线程池,一般是工作中使用,因为使用工具了类会出现问题
*/
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy()//队列满了,尝试和最早的去竞争
);
//TODO 线程使用完毕是要关闭的
try {
for (int i = 0; i < 10; i++) {
final int temp = i;
//new Thread(()->{}).start();//常规的创建线程的方式
//现在线程池的创建方式 threadPool.execute(()->{});
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+ "=>" + "OK");
});
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
threadPool.shutdown();
}
}
}
线程池大小如何去设置
CPU密集型和IO密集型(调优)
/*
最大线程数应该如何定义?
IO密集型 十分占用资源 程序 , 15个类型任务 判断你的程序中判断程序中占用IO的线程
CPU密集型 ,几核就是几,可以保持CPU的效率最高
*/
//输出CPU的核心数
System.out.println(Runtime.getRuntime().availableProcessors());
四大函数接口(必需掌握)
新时代的程序员需要掌握的lambda表达式,链式编程,函数式接口,Stream流式计算
函数式接口:只有一个方法的接口
比如:Runnable
简化开发模型,在新版本中大量使用
比如foreach
/*
* @Title 函数式接口
* @Description Function
* 有一个输入参数,有一个输出
* 只要是 函数式接口,那么我们都可以用lambda 表达式简化
*
* @author 罗小黑
* @param null
* @return
* @date 2022/10/7 18:38
*/
public class FunctionDemo {
public static void main(String[] args) {
Function function = new Function<String,String>(){
//工具类,输出输入的值
@Override
public String apply(String o) {
return o;
}
};
Function function1 = (str)->{ return str;};
System.out.println(function.apply("asd"));
System.out.println(function1.apply("add"));
}
}
//断定性接口;有一个输入参数,返回值只能是布尔值
public class PredicateDemo {
public static void main(String[] args) {
Predicate predicate = new Predicate<String >(){
//判断字符串是否为空
@Override
public boolean test(String o) {
return o.isEmpty();
}
};
//注意一下,这里泛型需要定义一下
Predicate<String> predicate1 = (str)->{return str.isEmpty();};
System.out.println(predicate.test("aad"));
System.out.println(predicate1.test(""));
}
}
/*
消费型接口
只有输入,没有返回值
*/
public class ConsumerDemo {
public static void main(String[] args) {
Consumer consumer = new Consumer<String>() {
@Override
public void accept(String o) {
System.out.println(o);
}
};
Consumer<String> consumer1 = (str)->{
System.out.println(str);
};
consumer.accept("你好");
consumer1.accept("我喜欢你");
}
}
//供给型函数接口,没有参数,只有返回值
public class SupplierDemo {
public static void main(String[] args) {
Supplier supplier = new Supplier<String>() {
@Override
public String get() {
return "你好";
}
};
Supplier supplier1 = ()->{return "我还是喜欢你";};
System.out.println(supplier.get());
System.out.println(supplier1.get());
}
}
老程序员,泛型,反射,枚举都需要会
新程序员,lambda表达式,函数式接口,Stream流式计算
什么是流式计算
大数据:存储+计算
集合,MySql本质上就是存储东西
计算都应该交给流来处理
/*
流式计算
现有一个题目:要求一分钟内完成此题,只能用一行代码实现
现有五个用户,
1.ID必须为偶数
2.年龄必须要大于23岁
3.用户名必须转换为大写
4.用户名字倒叙排序
5.只输出一个用户
*/
public class Test {
public static void main(String[] args) {
User u1 = new User(1,12,"a");
User u2 = new User(2,25,"b");
User u3 = new User(3,23,"c");
User u4 = new User(4,15,"d");
User u5 = new User(5,16,"e");
//使用List集合存储,将集合类对象转换
List<User> list = Arrays.asList(u1,u2,u3,u4,u5);
System.out.println(list);
//计算交给Steam流
list.stream()
.filter(u->{return u.getId()%2 ==0;})//过滤id为偶数的
.filter(u->{return u.getAge() > 16;}) //过滤年龄小于16的
.map(u->{return u.getName().toUpperCase();}) //通过map函数式接口将名字转换为大写
.sorted((uu1,uu2) -> {return uu1.compareTo(uu2);})//排序并比较
.limit(1)//只显示一条数据
.forEach(System.out::println);//将他遍历出来
}
}
ForkJoin
ForkJoin在JDK1.7中,并行执行任务,提交效率,一般通用于大数据量!
大数据:Map Reduce(把大任务拆分为小任务)
ForkJoin特点
测试
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// test1();
//test2();
test3(); //贼强,速度很快一般大型的时候使用
}
//普通程序员
// Sum=时间4099
public static void test1(){
Long start = System.currentTimeMillis();
Long sum = 0L;
for (long i = 1L; i <= 10_0000_0000; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("Sum="+sum+"时间\t" + (end -start));
}
//中级程序员
//Sum=时间11302
public static void test2() throws ExecutionException, InterruptedException {
Long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo(1L,10_0000_0000L);
forkJoinPool.execute(task);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);//提交任务
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("Sum="+"时间" + (end -start));
}
//并行流 时间241
public static void test3(){
Long start = System.currentTimeMillis();
Long sum = LongStream.rangeClosed(0L,10_0000_0000L).parallel().reduce(0,Long::sum);
Long end = System.currentTimeMillis();
System.out.println("Sum="+sum + "\n时间" + (end -start));
}
}
异步回调
Future设计初衷,对将来某个事件结果进行建模
/*
异步调用ajax技术,一般上ajax使用原理就异步调用
异步执行:
成功回调
失败回调
*/
public class FutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//发送一个请求 ,没有返回值的异步回调
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "RunAsny=>Void");
});
System.out.println("111");
completableFuture.get();//获取执行结果
System.out.println("=================================");
//有返回值的异步回调
//有返回值的是supplyAsync异步回调
//ajax,成功和失败的回调
//返回的是错误的信息。
CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + "supplyAsync=>Integer");
int i = 4/0;
return 1024 ;
});
completableFuture1.whenComplete((t,u)->{
System.out.println( " --> " +t); //正确的返回结果
System.out.println( " -- >" + u); //错误信息
}).exceptionally((e)->{
System.out.println(e.getMessage());
return 444; //可以通过这个标识码获取错误的返回结果
}).get();
}
}
JVM初步理解
谈谈你对Volatile的理解
volatile是javaJVM提供的轻量级的同步机制
- 保证可见性
- 不保证原子性
- 静止指令重排
JMM:java内存模型,不存在的东西,就是一个概念!约定
关于JMM的一些同步的约定
- 线程解锁前,必须把共享变量立刻 刷回主存。
- 线程加锁前,必须读取主存中的最新值到工作内存中。
- 加锁和解锁是同一把锁。
线程工作内存,主内存
jvm的8种操作:
> 1. read - > load
> 2. user -> assign
> 3. write ->store
> 4. lock -> unlock
内存交互操作有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操作之前,必须把此变量同步回主内存
Volatile
/*
在这个例子中我们发现,当执行线程的时候,num =0是一开始的时候,之后我们将
改成num = 1,但是程序输出1却没有终止
这里原因是,我们更改了num的值,但是线程B知道而线程A却不知道
也就是main线程知道,但是Thread线程却是不知道
*/
public class VolatileDemo {
private static int num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread( ()->{
while (num == 0){
}
}).start();
TimeUnit.SECONDS.sleep(1);
num =1;
System.out.println(num);
}
}
验证特性
- 保证可见性
/*
在这个例子中我们发现,当执行线程的时候,num =0是一开始的时候,之后我们将
改成num = 1,但是程序输出1却没有终止
这里原因是,我们更改了num的值,但是线程B知道而线程A却不知道
也就是main线程知道,但是Thread线程却是不知道
*/
public class VolatileDemo {
//不加volatile就会陷入无限循环
private volatile static int num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread( ()->{
while (num == 0){
}
}).start();
TimeUnit.SECONDS.sleep(1);
num =1;
System.out.println(num);
}
}
- 不保证原子性:不可分割
原子性: 线程A在执行任务的时候,不能被打扰到,也不能被分割,要么同时成功要么同时失败。
//不保证原子性
public class VolatileDemo2 {
//这个时候我们可以发现,volatile无法保证原子性
private volatile static int num = 0;
//synchronized可保证为结果为20000
public static void add(){
num ++; // 不是原子性
}
public static void main(String[] args) {
//理论上num 为20000
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
/*
java中默认两个线程,第一个main,第二个GC线程
*/
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
//不保证原子性
public class VolatileDemo2 {
//这个时候我们可以发现,volatile无法保证原子性
private volatile static int num = 0;
//synchronized可保证为结果为20000
//我们此时发现,lock也可以保证结果为20000
private static Lock lock = new ReentrantLock();
public static void add(){
lock.lock();
try{
num ++;
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
//理论上num 为20000
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
/*
java中默认两个线程,第一个main,第二个GC线程
*/
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
如果不加Lock和synochronized如何保证原子性?
使用原子类解决原子性
//不保证原子性
public class VolatileDemo2 {
//这个时候我们可以发现,volatile无法保证原子性
// private volatile static int num = 0;
private volatile static AtomicInteger num = new AtomicInteger();
//synchronized可保证为结果为20000
//我们此时发现,lock也可以保证结果为20000
private static Lock lock = new ReentrantLock();
public static void add(){
/* lock.lock();
try{
num ++;
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}*/
num.getAndIncrement(); //AtomicInteger +1方法
}
public static void main(String[] args) {
//理论上num 为20000
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
/*
java中默认两个线程,第一个main,第二个GC线程
*/
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
这些类的底层都是和操作系统挂钩,在内存中修改中,Unsafe是一个特殊的存在。
指令重排
什么是指令重排:你写的程序,计算机并不是按照你写的程序那样去执行的。
源代码 – – 编译器里边的优化的重排 —— 指令并行也可能会重排 – 内存系统也会重排—–执行
int x = 1; //1
int y = 2;//2
x = x+ 5;// 3
y = x +x; // 4
//我们希望的顺序是 1234 可能是 1234 2134 1324 但不可能是 4123
可能造成影响的结果: a b x y 这四个值 默认都是 0
线程A | 线程B |
---|---|
x =a | y =b |
b =1 | a = 2 |
正常的结果 x = 0; y = 0;
volatile可以避免指令重排
内存屏障,CPU指令,作用:
- 保证特定的操作执行顺序
- 保证某些变量的内存可见性(利用这些特性Volatile实现可见性)
volatile可保证可见性,不能保证原子性,由于内存屏障,可以避免指令重排的现象产生。
内存屏障使用最多的场景是单例模式
单例模式
//饿汉式单例模式
public class SingleDemo {
//饿汉式单例这些数据都没有使用,但是被创建的时候还是占用很多内存
private byte[] d1 = new byte[1024* 1024];
private byte[] d2 = new byte[1024* 1024];
private byte[] d3 = new byte[1024* 1024];
private byte[] d4 = new byte[1024* 1024];
//无参构造 只要是单例,我们都要创建一个私有化构造
private SingleDemo(){
}
//单例模式的调用方式
private static SingleDemo singleDemo = new SingleDemo();
public static SingleDemo getInstance(){
return singleDemo;
}
}
/*
* @Title 懒汉式单例模式
* @Description
* @author 罗小黑
* @param null
* @return
* @date 2022/10/9 0:04
*/
public class LazyDemo {
//不说二话直接整上构造器,只要是单例模式,构造器私有
private LazyDemo(){
System.out.println(Thread.currentThread().getName() + "\t OK");
}
//先创建一个lazy对象,为保证不会产生指令重排,我们需要在这加入volatile
private volatile static LazyDemo lazyDemo;
/*
* @Title 双重检测模式下的懒汉式单例,DCL懒汉式
* @Description
* @author 罗小黑
* @return com.xh.juc.day06.Single.LazyDemo
* @date 2022/10/9 0:15
*/
//重点是getInstance在单例模式中我们直接使用的是getInstance()
public static LazyDemo getInstance(){
//解决办法之一最常用的加锁。
if(lazyDemo == null){
synchronized (LazyDemo.class){
lazyDemo = new LazyDemo(); //不是一个原子性操作
/*
有三步:
1. 分配内存空间
2. 执行构造方法,初始化对象
3.把这个对象指向这个空间
这个时候可能会产生指令重排,会出现故障
*/
}
//如果传如的对象为空,那么我们就需要将对爱心给他赋予
// lazyDemo = new LazyDemo();
}
return lazyDemo;
}
//问题,单线程下没问题,但是多线性下出现问题.
// 我们发现在多线程下会产生不一样的结果,而且也不会创建10个线程
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyDemo.getInstance();
},String.valueOf(i)).start();
}
}
}
// 静态内部类
public class Holder {
private Holder(){
}
public static Holder getInstace(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
//单例不安全,反射
//枚举
// enum 是一个什么? 本身也是一个Class类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
// NoSuchMethodException: com.kuang.single.EnumSingle.<init>()
System.out.println(instance1);
System.out.println(instance2);
}
}
CAS
public class CASDemo {
//CAS compareAndSet : 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2022);
//期望,更新
// public final boolean compareAndSet(int expect,int update);
//如果我期望的值达到了,那么就更新,否则不更新 CAS是CPU的调度
//java 无法操作内存,java可以调用C++ native C++可以操作内存,java的后门,可以操作这个类(Unsafe)调度
System.out.println(atomicInteger.compareAndSet(2022, 2123));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}
CAS:比较当前工作内存中的值和主内存的值,如果这个值是期望的,那么执行操作,如果不是就一直循环
缺点:
- 循环会耗时
- 一次性只能保证一个共享变量的原子性
- ABA问题
CAS:ABA问题(狸猫换太子)
public class CASDemo {
//CAS compareAndSet : 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2022);
//期望,更新
// public final boolean compareAndSet(int expect,int update);
//如果我期望的值达到了,那么就更新,否则不更新 CAS是CPU的调度
//java 无法操作内存,java可以调用C++ native C++可以操作内存,java的后门,可以操作这个类(Unsafe)调度
//(A)
System.out.println(atomicInteger.compareAndSet(2022, 2123));
System.out.println(atomicInteger.get());
// B
System.out.println(atomicInteger.compareAndSet(2123, 2022));
System.out.println(atomicInteger.get());
//A
System.out.println(atomicInteger.compareAndSet(2022, 2123));
System.out.println(atomicInteger.get());
}
}
原子引用(ABA问题)
//乐观锁和悲观锁
public class CASDemo2 {
public static void main(String[] args) {
//注意如果泛型是包装类,需要注意引用类型和范围Integer的范围是-128 到 127
//正常的应用的时候,我们使用的都是对象,而不是直接使用Integer的等,
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(1,1);
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获取版本号
System.out.println( Thread.currentThread().getName()+ " " + atomicStampedReference.getStamp());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(atomicStampedReference.compareAndSet(
1, 5,
atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 1));
System.out.println( Thread.currentThread().getName()+ " -A2- " + atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(
5, 6,
atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 1));
System.out.println( Thread.currentThread().getName()+ " -A3- " + atomicStampedReference.getStamp());
},"A").start();
//比较AB两个线程的版本号
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获取版本号
System.out.println( Thread.currentThread().getName()+ " " + atomicStampedReference.getStamp());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(atomicStampedReference.compareAndSet(2020, 6666,
stamp, stamp + 1));
System.out.println( Thread.currentThread().getName()+ " -B2- " + atomicStampedReference.getStamp());
},"B").start();
}
}
锁机制的了解
公平锁
公平锁:非常公平,不能插队,必须先来先到
非公平锁:java自带默认非公平锁,可以插队
//java源码
public ReentrantLock() {
//我们发现,这里默认就是不公平锁
sync = new NonfairSync();
}
```java
public class 公平锁 {
//创建一个公平锁
private Lock lock = new ReentrantLock();
//synchronized
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()+"发短信");
call();
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"打电话");
}
}
public class LockDemo {
private Lock lock = new ReentrantLock();
public static void main(String[] args) {
Phone2 phone2 = new Phone2();
new Thread(()->{
phone2.sms();
},"A").start();
new Thread(()->{
phone2.sms();
},"B").start();
System.out.println("=====================");
Test te = new Test();
new Thread(()->{
te.sms();
},"C").start();
new Thread(()->{
te.sms();
},"D").start();
}
}
class Phone2{
private Lock lock = new ReentrantLock();
public void sms(){
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+"发消息");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
call();
}
public void call(){
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+ "打电话");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
class Test{
public void sms(){
System.out.println(Thread.currentThread().getName()+"发消息");
call();
}
public void call(){
System.out.println(Thread.currentThread().getName()+ "打电话");
}
}
可重入锁
可重入锁是
某个线程已经获得某个锁,可以再次获取锁而不会出现死锁
。
再次获取锁的时候会判断当前线程是否是已经加锁的线程,如果是对锁的次数+1,释放锁的时候加了几次锁,就需要释放几次锁。
代码中的锁的递归只是锁的一种表现及证明形式,除了这种形式外,还有另一种表现形式。同一个线程在没有释放锁的情况下多次调用一个加锁方法,如果成功,则也说明是可重入锁。
自旋锁
/*
自旋锁:
我们自己定义一个自旋锁
*/
public class 自旋锁 {
AtomicReference<Thread> atomicStampedReference = new AtomicReference<>();
/*
//加锁
所谓自旋锁
*/
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "=> MY Lock");
//自旋开始
while (!atomicStampedReference.compareAndSet(null,thread)){
}
}
/*
解锁
*/
public void myUnLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "=> MY UnLock");
atomicStampedReference.compareAndSet(thread,null);
}
}
public class Test_Self {
public static void main(String[] args) {
/*
一般情况下使用的方式
ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
reentrantLock.unlock();
*/
/*
使用我们自定义下的方式
我们使用的是CAS比较的也就是compareAndSet
*/
自旋锁 t = new 自旋锁();
new Thread(()->{
t.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
t.myUnLock();
}
},"A").start();
new Thread(()->{
t.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
t.myUnLock();
}
},"B").start();
}
}
悲观锁
总是以悲观的角度来看问题,当用户拿到锁之后,认为用户会修改
乐观锁
总是以乐观的方式看待问题,当用户每次拿到锁后,都不会对数据进行修改
死锁
两个线程强制一个资源造成的两个线程处于等待状态,在不经过人的干预的情况之下一直会保持这种状态,我们称之为死锁。
如何去避免死锁(避免死锁产生的条件)
如何去判断线程产生死锁问题
public class 死锁{
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new MyTest(lockA,lockB),"A").start();
new Thread(new MyTest(lockB,lockA),"B").start();
}
}
class MyTest implements Runnable{
private String lockA;
private String lockB;
public MyTest(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName() + "Lock A " + "=> get " + lockB);
//我们让线程A休眠2秒
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName() + "Lock B " + lockB + "=> get " + lockA);
//我们让线程A休眠2秒
}
}
}
}
1. 使用 jps -l 定位进程号
2. 使用 jstack 进程号 查看进程的信息 ,找到死锁问题
**PS E:\IDEAProgram\juc-learning> jstack 2400**
2022-10-09 21:13:37
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.261-b12 mixed mode):
"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x000001f454e10800 nid=0x31ec wait
ing on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"B" #13 prio=5 os_prio=0 tid=0x000001f470d10800 nid=0x3690 waiting for moni
tor entry [0x000000870e7ff000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.xh.juc.day07.锁.MyTest.run(死锁.java:40)
- waiting to lock <0x0000000775b9f8a0> (a java.lang.String)
- locked <0x0000000775b9f8d8> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
"A" #12 prio=5 os_prio=0 tid=0x000001f470d03000 nid=0x39e0 waiting for moni
tor entry [0x000000870e6ff000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.xh.juc.day07.锁.MyTest.run(死锁.java:40)
- waiting to lock <0x0000000775b9f8d8> (a java.lang.String)
- locked <0x0000000775b9f8a0> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000001f470ba4000 nid=0x3
ea0 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x000001f470af0000 nid
=0x3f94 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000001f470aef000 nid=
0x3168 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000001f470ade000 nid=
0xd1c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000001f470add000 nid=
0x1d30 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000001f470adb000 nid=
0x268c runnable [0x000000870dffe000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116
)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x0000000775c8d1c0> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x0000000775c8d1c0> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.
java:49)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000001f46edb9800 nid=0xd
64 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000001f46ee1e000 nid=0
x10e8 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000001f454eb7000 nid=0x2ee0 in
Object.wait() [0x000000870dcfe000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000775a08ee0> (a java.lang.ref.ReferenceQueue$L
ock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x0000000775a08ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000001f454eb0000 nid=
0x35e0 in Object.wait() [0x000000870dbff000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000775a06c00> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x0000000775a06c00> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x000001f454ead800 nid=0x1f50 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x000001f454e26000 nid=0x3f00
runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x000001f454e28800 nid=0x3eb0
runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000001f454e29800 nid=0x3e84
runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000001f454e2d000 nid=0x3fb8
runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000001f454e32000 nid=0x3ca0
runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x000001f454e32800 nid=0x3df8
runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x000001f454e35800 nid=0x3630
runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x000001f454e36800 nid=0x8ec
runnable
"VM Periodic Task Thread" os_prio=2 tid=0x000001f470be6000 nid=0x21f4 waiti
ng on condition
JNI global references: 12
Found one Java-level deadlock:
=============================
"B":
waiting to lock monitor 0x000001f454eb3c68 (object 0x0000000775b9f8a0, a
java.lang.String),
which is held by "A"
"A":
waiting to lock monitor 0x000001f454eb65a8 (object 0x0000000775b9f8d8, a
java.lang.String),
which is held by "B"
Java stack information for the threads listed above:
===================================================
"B":
at com.xh.juc.day07.???.MyTest.run(死锁.java:40)
- waiting to lock <0x0000000775b9f8a0> (a java.lang.String)
- locked <0x0000000775b9f8d8> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
"A":
at com.xh.juc.day07.锁.MyTest.run(死锁.java:40)
- waiting to lock <0x0000000775b9f8d8> (a java.lang.String)
- locked <0x0000000775b9f8a0> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
如何检测死锁:
1. 查看日志
2. 堆栈信息
轻量锁
① 当一个线程要进入同步块时,首先会创建一个Lock Record(锁记录)对象,该对象包含Displaced Mark Word和Owner。
② 锁记录对象会拷贝对象头中的Mark Word,即Displaced Mark Word。
③ 拷贝成功后,锁记录将owner指向对象头,然后尝试通过cas将对象头的Mark Word更改为指向Lock
Record的指针,并且将对象Mark Word的锁标志位设置为“00”。④ 如果更新成功,则表示该对象处于轻量级锁定状态。如果失败,那么首先检测是否是可重入,如果可重入则进入执行。
⑤ 如果不可重入,则膨胀为重量锁。(有的文章说会进行自旋,有的说不进行自旋只要存在竞争就膨胀为重量锁,美团说当只有一个等待线程时,该线程自旋。当超过一定次数或者超过一个自旋线程时直接膨胀为重量锁)。
⑥ 释放锁时,使用cas对象头的Mark Word恢复,如果cas成功,则解锁成功。如果cas失败,则进入重量锁解锁流程。
重量锁
JDK1.6之前,锁没被优化,synchronized使用的是重量锁。重量锁需要操作系统帮忙,所以需要进行用户态到内核态的切换,同时还会带来线程上下文切换。
原理
Monitor对象分为3部分组成:Owner、EntryList和WaitSet
① JVM会创建一个Monitor对象,然后将锁对象的对象头中MarkWord改变成指向Monitor对象的指针。
② 当其中一个线程抢占到锁后,Monitor的Owner会置为该线程id,只能有一个。
③ 其余没抢到的,会被放置到EntryList中阻塞。
④ 进入WAITING状态的线程,会被放在WaitSet中。
排他锁
排他锁(EXclusive Lock),又称X锁、独占锁、写锁。针对行锁。
当有事务对数据加写锁后,其他事务不能再对锁定的数据加任何锁,又因为InnoDB对select语句默认不加锁,所以其他事务除了不能写操作外,照样是允许读的(尽管不允许加读锁)。
📢主要为了在事务进行写操作时,不允许其他事务修改。
偏向锁
轻量锁每次重入和退出时还是会执行cas操作,并且要创建锁记录对象。如果在大多数情况下,锁总是由一个线程多次获得,不存在多线程竞争,就可以使用偏向锁优化。
① 第一次使用CAS将线程ID设置到对象的Mark Word。
② 以后该线程进入同步块时,不需要CAS进行加锁,只会往当前线程的栈中添加一条Displaced Mark Word为空的Lock
Record中,用来统计重入的次数。