狂神JUC笔记
Posted 在奋斗的大道
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了狂神JUC笔记相关的知识,希望对你有一定的参考价值。
目录
本文来源于:B站狂神JUC,视频地址:狂神B站
1、什么是JUC
JUC是 java util concurrent
java.util 是Java的一个工具包~
2、线程和进程
进程:一个程序,QQ.EXE Music.EXE;
一个进程可以包含多个线程,至少包含一个线程!
Java默认有几个线程?2个线程! main线程、GC线程
线程:开了一个进程Notepad++,写字,等待几分钟会进行自动保存(线程负责的)
对于Java而言:Thread、Runable、Callable进行开启线程的实现方式。
提问?JAVA真的可以开启线程吗? 开不了的!
public synchronized void start()
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try
start0();
started = true;
finally
try
if (!started)
group.threadStartFailed(this);
catch (Throwable ignore)
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
//这是一个C++底层,Java是没有权限操作底层硬件的
private native void start0();
Java是没有权限去开启线程、操作硬件的,这是一个native的一个本地方法,它调用的底层的C++代码。
并行、串行
并发: 多线程操作同一个资源。
- CPU 只有一核,模拟出来多条线程,天下武功,唯快不破。那么我们就可以使用CPU快速交替,来模拟多线程。
并行: 多个人一起行走
- CPU多核,多个线程可以同时执行。 我们可以使用线程池!
public class Test1
public static void main(String[] args)
//获取cpu的核数
System.out.println(Runtime.getRuntime().availableProcessors());
并发编程的本质:充分利用CPU的资源!
线程有几个状态?
线程的状态:6个状态
public enum State
/**
* Thread state for a thread which has not yet started.
*/
//运行
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
//运行
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* @link Object#wait() Object.wait.
*/
//阻塞
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>@link Object#wait() Object.wait with no timeout</li>
* <li>@link #join() Thread.join with no timeout</li>
* <li>@link LockSupport#park() LockSupport.park</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
//等待
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>@link #sleep Thread.sleep</li>
* <li>@link Object#wait(long) Object.wait with timeout</li>
* <li>@link #join(long) Thread.join with timeout</li>
* <li>@link LockSupport#parkNanos LockSupport.parkNanos</li>
* <li>@link LockSupport#parkUntil LockSupport.parkUntil</li>
* </ul>
*/
//超时等待
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
//终止
TERMINATED;
wait/sleep的区别
1、来自不同的类
wait => Object
sleep => Thread
一般情况企业开发中使用休眠是:
TimeUnit.DAYS.sleep(1); //休眠1天
TimeUnit.SECONDS.sleep(1); //休眠1s
2、关于锁的释放
wait 会释放锁;
sleep睡觉了,不会释放锁;
3、使用的范围是不同的
wait 必须在同步代码块中;
sleep 可以在任何地方睡;
4、是否需要捕获异常
wait是不需要捕获异常;
sleep必须要捕获异常;
3、Lock锁(重点)
传统的Synchronized
/**
* 真正的多线程开发
* 线程就是一个单独的资源类,没有任何的附属操作!
*/
public class SaleTicketDemo01
public static void main(String[] args)
//多线程操作
//并发:多线程操作同一个资源类,把资源类丢入线程
Ticket ticket = new Ticket();
//@FunctionalInterface 函数式接口 jdk1.8之后 lambda表达式
new Thread(()->
for(int i=0;i<40;i++)
ticket.sale();
,"A").start();
new Thread(()->
for(int i=0;i<40;i++)
ticket.sale();
,"B").start();
new Thread(()->
for(int i=0;i<40;i++)
ticket.sale();
,"C").start();
//资源类
//属性+方法
//oop
class Ticket
private int number=50;
//卖票的方式
// synchronized 本质:队列,锁
public synchronized void sale()
if(number>0)
System.out.println(Thread.currentThread().getName()+" 卖出了第"+number+" 张票,剩余:"+number+" 张票");
number--;
Lock接口
公平锁: 十分公平,必须先来后到~;
非公平锁: 十分不公平,可以插队;(默认为非公平锁)
public class SaleTicketDemo02
public static void main(String[] args)
//多线程操作
//并发:多线程操作同一个资源类,把资源类丢入线程
Ticket2 ticket = new Ticket2();
new Thread(()->for(int i=0;i<40;i++) ticket.sale(); ,"A").start();
new Thread(()->for(int i=0;i<40;i++) ticket.sale(); ,"B").start();
new Thread(()->for(int i=0;i<40;i++) ticket.sale(); ,"C").start();
//lock三部曲
//1、 Lock lock=new ReentrantLock();
//2、 lock.lock() 加锁
//3、 finally=> 解锁:lock.unlock();
class Ticket2
private int number=50;
Lock lock=new ReentrantLock();
//卖票的方式
// 使用Lock 锁
public void sale()
//加锁
lock.lock();
try
//业务代码
if(number>=0)
System.out.println(Thread.currentThread().getName()+" 卖出了第"+number+" 张票,剩余:"+number+" 张票");
number--;
catch (Exception e)
e.printStackTrace();
finally
//解锁
lock.unlock();
Synchronized 和 Lock区别
- Synchronized 内置的Java关键字,Lock是一个Java类
- Synchronized 无法判断获取锁的状态,Lock可以判断
- Synchronized 会自动释放锁,Lock必须要手动加锁和手动释放锁!可能会遇到死锁
- Synchronized 线程1(获得锁->阻塞)、线程2(等待);Lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。
- Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;
- Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;
4、生产者和消费者问题!
Synchronized版本
public class A
public static void main(String[] args)
Data data = new Data();
new Thread(()->for(int i=0;i<10;i++)
try
data.increment();
catch (InterruptedException e)
e.printStackTrace();
,"A").start();
new Thread(()->for(int i=0;i<10;i++)
try
data.decrement();
catch (InterruptedException e)
e.printStackTrace();
,"B").start();
class Data
//数字 资源类
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException
if(number!=0)
//等待操作
this.wait();
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程 我+1完毕了
this.notifyAll();
//-1
public synchronized void decrement() throws InterruptedException
if(number==0)
//等待操作
this.wait();
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程 我-1完毕了
this.notifyAll();
问题存在,A线程B线程,现在如果我有四个线程A B C D!
解决方案: if 改为while即可,防止虚假唤醒
Lock版本
通过Lock找到Condition
public class B
public static void main(String[] args)
Data2 data = new Data2();
new Thread(()->for(int i=0;i<10;i++)
data.increment();
,"A").start();
new Thread(()->for(int i=0;i<10;i++)
data.decrement();
,"B").start();
new Thread(()->for(int i=0;i<10;i++)
data.increment();
,"C").start();
new Thread(()->for(int i=0;i<10;i++)
data.decrement();
,"D").start();
class Data2
//数字 资源类
private int number = 0;
//lock锁
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//+1
public void increment()
lock.lock();
try
//业务
while (number!=0)
//等待操作
condition.await();
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程 我+1完毕了
condition.signalAll();
catch (Exception e)
e.printStackTrace();
finally
lock.unlock();
//-1
public void decrement()
lock.lock();
try
//业务
while (number==0)
//等待操作
condition.await();
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程 我+1完毕了
condition.signalAll();
catch (Exception e)
e.printStackTrace();
finally
lock.unlock();
Condition的优势:精准的通知和唤醒的线程
如果我们要指定的通知按照一定的顺序执行,如何实现?可以使用Condition来指定通知进程。
/**
* A 执行完 调用B
* B 执行完 调用C
* C 执行完 调用A
*/
public class C
public static void main(String[] args)
Data3 data3 = new Data3();
new Thread(()->
for(int i=0;i<10;i++)
data3.printA();
,"A").start();
new Thread(()->
for(int i=0;i<10;i++)
data3.printB();
,"B").start();
new Thread(()->
for(int i=0;i<10;i++)
data3.printC();
,"C").start();
class Data3
//资源类
private Lock lock=new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1; //1A 2B 3C
public void printA()
lock.lock();
try
//业务 判断 -> 执行 -> 通知
while(number!=1)
//等待
condition1.await();
//操作
System.out.println(Thread.currentThread().getName()+",AAAAA");
//唤醒指定的线程
number=2;
condition2.signal(); // 唤醒2
catch (Exception e)
e.printStackTrace();
finally
lock.unlock();
public void printB()
lock.lock();
try
//业务 判断 -> 执行 -> 通知
while (number!=2)
condition2.await();
System.out.println(Thread.currentThread().getName()+",BBBBB");
//唤醒3
number=3;
condition3.signal();
catch (Exception e)
e.printStackTrace();
finally
lock.unlock();
public void printC()
lock.lock();
try
//业务 判断 -> 执行 -> 通知
while(number!=3)
condition3.await();
System.out.println(Thread.currentThread().getName()+",CCCCC");
//唤醒1
number=1;
condition1.signal();
catch (Exception e)
e.printStackTrace();
finally
lock.unlock();
5、八锁现象
如何判断锁的是谁!锁到底锁的是谁?
锁会锁住:对象、Class
深刻理解我们的锁
- 问题1:
结果 :先发短信,再打电话。
- 问题2:让发短信延迟4秒,再次验证输出结果。
结果:还是先发短信,再打电话。
原因:因为synchronized 锁的对象是方法的调用!对于两个方法用的是同一个锁,谁先拿到谁先执行!另外一个则等待!
- 问题3:如果我们添加一个普通方法,那么先执行哪一个呢?
结果:先执行hello,然后再执行发短信!原因是hello是一个普通方法,不受synchronized锁的影响,但是我发现,如果我把发短信里面的延迟4秒去掉,那么就会顺序执行,先执行发短信然后再执行hello,原因应该是顺序执行的原因吧,不是太理解。
- 问题4:如果我们使用的是两个对象,一个调用发短信,一个调用打电话,那么整个顺序是怎么样的呢?
结果:先打电话,再发短信。原因:在发短信方法中延迟了4s,又因为synchronized锁的是对象,但是我们这使用的是两个对象,所以每个对象都有一把锁,所以不会造成锁的等待。正常执行
- 问题5,6:如果我们把synchronized的方法加上static变成静态方法!那么顺序又是怎么样的呢?
(1)我们先来使用一个对象调用两个方法!
结果:先发短信,后打电话
(2)如果我们使用两个对象调用两个方法!
结果:还是先发短信,后打电话
问题: 为什么加了static就始终前面一个对象先执行呢!为什么后面会等待呢?
原因:对于static静态方法来说,对于整个类Class来说只有一份,对于不同的对象使用的是同一份方法,相当于这个方法是属于这个类的,如果静态static方法使用synchronized锁定,那么这个synchronized锁会锁住整个类!不管多少个实例对象,对于静态的锁都只有一把锁,谁先拿到这个锁就先执行,其他的线程都需要等待!
- 问题7:如果我们使用一个静态同步方法、一个同步方法、一个对象调用顺序是什么?
结果:先打电话,后发短信。
原因:因为sendSms方法锁的是Phone4 Class类,call锁的是对象调用者Phone4 Class类实例对象。后面那个打电话不需要等待发短信,直接运行就可以了。
- 问题8:如果我们使用一个静态同步方法、一个同步方法、两个对象调用顺序是什么呢?
结果:先打电话,再发短信
原因:两把锁锁的不是同一个东西,所以后面的第二个对象不需要等待第一个对象的执行。
总结:
synchronized修饰普通方法锁的是类实例化对象。
synchronized修饰静态方法锁的是类模板。
6、集合不安全
List不安全
示例代码:
//java.util.ConcurrentModificationException 并发修改异常!
public class ListTest
public static void main(String[] args)
List<Object> arrayList = new ArrayList<>();
for(int i=1;i<=10;i++)
new Thread(()->
arrayList.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(arrayList);
,String.valueOf(i)).start();
错误信息:
结论:ArrayList 在并发情况下是不安全的!
解决方案1:ArrayList切换成Vector就是线程安全
解决方案2:使用Collections.synchronizedList(new ArrayList<>());
public class ListTest
public static void main(String[] args)
List<Object> arrayList = Collections.synchronizedList(new ArrayList<>());
for(int i=1;i<=10;i++)
new Thread(()->
arrayList.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(arrayList);
,String.valueOf(i)).start();
解决方案3:使用JUC中的包:List arrayList = new CopyOnWriteArrayList<>();
public class ListTest
public static void main(String[] args)
List<Object> arrayList = new CopyOnWriteArrayList<>();
for(int i=1;i<=10;i++)
new Thread(()->
arrayList.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(arrayList);
,String.valueOf(i)).start();
CopyOnWriteArrayList:写入时复制! COW 计算机程序设计领域的一种优化策略
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
CopyOnWriteArrayList比Vector厉害在哪里?
Vector底层是使用synchronized关键字来实现的:效率特别低下。
CopyOnWriteArrayList使用的是Lock锁,效率会更加高效!
Set 不安全
和List、Set同级的还有一个BlockingQueue 阻塞队列;
Set和List同理可得: 多线程情况下,普通的Set集合是线程不安全的;
解决方案还是两种:
- 使用Collections工具类的synchronized包装的Set类
- 使用CopyOnWriteArraySet 写入复制的JUC解决方案
//同理:java.util.ConcurrentModificationException
// 解决方案:
public class SetTest
public static void main(String[] args)
// Set<String> hashSet = Collections.synchronizedSet(new HashSet<>()); //解决方案1
Set<String> hashSet = new CopyOnWriteArraySet<>();//解决方案2
for (int i = 1; i < 100; i++)
new Thread(()->
hashSet.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(hashSet);
,String.valueOf(i)).start();
HashSet底层是什么?
HashSet底层就是一个HashMap;
public HashSet()
map = new HashMap<>();
//add 本质其实就是一个map的key,map的key是无法重复的,所以使用的就是map存储
//hashSet就是使用了hashmap key不能重复的原理
public boolean add(E e)
return map.put(e, PRESENT)==null;
//PRESENT是什么? 是一个常量 不会改变的常量 无用的占位
private static final Object PRESENT = new Object();
Map不安全
map基本操作:
//map 是这样用的吗? 不是,工作中不使用这个
//默认等价什么? new HashMap<>(16,0.75);
Map<String, String> map = new HashMap<>();
//加载因子、初始化容量
默认加载因子是0.75,默认的初始容量是16
同样的HashMap类也存在并发修改异常!
public static void main(String[] args)
//map 是这样用的吗? 不是,工作中不使用这个
//默认等价什么? new HashMap<>(16,0.75);
Map<String, String> map = new HashMap<>();
//加载因子、初始化容量
for (int i = 1; i < 100; i++)
new Thread(()->
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
,String.valueOf(i)).start();
结果:异常java.util.ConcurrentModificationException 并发修改异常。
解决方案:
- 使用Collections.synchronizedMap(new HashMap<>());处理;
- 使用ConcurrentHashMap进行并发处理
7、Callable
Callable 与Runnable 区别
- Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
- Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息
- 3、方法不同,Runnable.run()/Callablle.call()
Runnable 代码测试
传统线程编码方式:
public class CallableTest
public static void main(String[] args)
for (int i = 1; i < 10; i++)
new Thread(new MyThread()).start();
class MyThread implements Runnable
@Override
public void run()
System.out.println(Thread.currentThread().getName());
使用Callable进行多线程操作:、
Calleable 泛型T就是call运行方法的返回值类型;
问题:如何将Callable怎么放入到Thread里面呢?
源码分析:Thread.java 源码
通过上述Thread的构造函数,发现仅支持传入Runnable类型的参数。
解决思路:在Runnable里面有一个叫做FutureTask的实现类,可以将Runnable接口和Callable接口进行整合。
FutureTask构造函数可以接受Callable参数;
FutureTask 和Calleable 代码测试
public class CallableTest
public static void main(String[] args) throws ExecutionException, InterruptedException
for (int i = 1; i < 10; i++)
// new Thread(new Runnable()).start();
// new Thread(new FutureTask<>( Callable)).start();
MyThread thread= new MyThread();
//适配类:FutureTask
FutureTask<String> futureTask = new FutureTask<>(thread);
//放入Thread使用
new Thread(futureTask,String.valueOf(i)).start();
//获取返回值
String s = futureTask.get();
System.out.println("返回值:"+ s);
class MyThread implements Callable<String>
@Override
public String call() throws Exception
System.out.println("Call:"+Thread.currentThread().getName());
return "String"+Thread.currentThread().getName();
Calleable + FutureTask 主要重点:
8、 常用的辅助类
8.1 CountDownLatch
其实就是一个减法计数器,对于计数器归零之后再进行后面的操作,这是一个计数器!
**
* 计数器减法
*/
public class CountDownLatchDemo
public static void main(String[] args) throws InterruptedException
//总数是6
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6 ; i++)
new Thread(()->
System.out.println(Thread.currentThread().getName()+" Go out");
countDownLatch.countDown(); //每个线程都数量-1
,String.valueOf(i)).start();
countDownLatch.await(); //等待计数器归零 然后向下执行
System.out.println("close door");
主要方法:
- countDown 减一操作;
- await 等待计数器归零。
await等待计数器为0,就唤醒,再继续向下运行。
8.2 CyclickBarrier
其实就是一个加法计数器;
package com.zzg.juc;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo
public static void main(String[] args)
//主线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->
System.out.println("召唤神龙~");
);
for (int i = 1; i <= 7; i++)
//子线程
int finalI = i;
new Thread(()->
System.out.println(Thread.currentThread().getName()+" 收集了第 "+ finalI+" 颗龙珠");
try
cyclicBarrier.await(); //加法计数 等待
catch (InterruptedException e)
e.printStackTrace();
catch (BrokenBarrierException e)
e.printStackTrace();
).start();
8.3 Semaphore
Semaphore:信号量
功能示例:抢车位-3个车位 6辆车;
public class SemaphoreDemo
public static void main(String[] args)
//停车位为3个
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++)
int finalI = i;
new Thread(()->
try
semaphore.acquire(); //得到
//抢到车位
System.out.println(Thread.currentThread().getName()+" 抢到了车位"+ finalI +"");
TimeUnit.SECONDS.sleep(2); //停车2s
System.out.println(Thread.currentThread().getName()+" 离开车位");
catch (InterruptedException e)
e.printStackTrace();
finally
semaphore.release();//释放
,String.valueOf(i)).start();
原理:
semaphore.acquire()获得资源,如果资源已经使用完了,就等待资源释放后再进行使用!
semaphore.release()释放,会将当前的信号量释放+1,然后唤醒等待的线程!
作用: 多个共享资源互斥的使用! 并发限流,控制最大的线程数!
9、读写锁
功能要求:在多线程环境下,实现全局缓存,别有写入操作、读取操作。
不加锁情况
package com.ogj.rw;
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.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo
public static void main(String[] args)
MyCache_ReadWriteLock mycache = new MyCache_ReadWriteLock();
//开启5个线程 写入数据
for (int i = 1; i <=5 ; i++)
int finalI = i;
new Thread(()->
mycache.put(String.valueOf(finalI),String.valueOf(finalI));
).start();
//开启10个线程去读取数据
for (int i = 1; i <=10 ; i++)
int finalI = i;
new Thread(()->
String o = mycache.get(String.valueOf(finalI));
).start();
class MyCache_ReadWriteLock
private volatile Map<String,String> map=new HashMap<>();
//使用读写锁
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
//普通锁
private Lock lock=new ReentrantLock();
public void put(String key,String value)
//写入
System.out.println(Thread.currentThread().getName()+" 线程 开始写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName()+" 线程 写入OK");
public String get(String key)
//得到
System.out.println(Thread.currentThread().getName()+" 线程 开始读取");
String o = map.get(key);
System.out.println(Thread.currentThread().getName()+" 线程 读取OK");
return o;
运行效果:
Thread-0 线程 开始写入
Thread-4 线程 开始写入 # 插入了其他的线程进行写入
Thread-4 线程 写入OK
Thread-3 线程 开始写入
Thread-1 线程 开始写入
Thread-2 线程 开始写入
Thread-1 线程 写入OK
Thread-3 线程 写入OK
Thread-0 线程 写入OK # 对于这种情况会出现 数据不一致等情况
Thread-2 线程 写入OK
Thread-5 线程 开始读取
Thread-6 线程 开始读取
Thread-6 线程 读取OK
Thread-7 线程 开始读取
Thread-7 线程 读取OK
Thread-5 线程 读取OK
Thread-8 线程 开始读取
Thread-8 线程 读取OK
Thread-9 线程 开始读取
Thread-9 线程 读取OK
Thread-10 线程 开始读取
Thread-11 线程 开始读取
Thread-12 线程 开始读取
Thread-12 线程 读取OK
Thread-10 线程 读取OK
Thread-14 线程 开始读取
Thread-13 线程 开始读取
Thread-13 线程 读取OK
Thread-11 线程 读取OK
Thread-14 线程 读取OK
Process finished with exit code 0
解决方案一:采用synchronized这种重量锁和轻量锁 lock去保证数据的可靠。
本次我们采用更细粒度的锁:ReadWriteLock 读写锁来保证。
package com.ogj.rw;
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.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo
public static void main(String[] args)
MyCache_ReadWriteLock mycache = new MyCache_ReadWriteLock();
//开启5个线程 写入数据
for (int i = 1; i <=5 ; i++)
int finalI = i;
new Thread(()->
mycache.put(String.valueOf(finalI),String.valueOf(finalI));
).start();
//开启10个线程去读取数据
for (int i = 1; i <=10 ; i++)
int finalI = i;
new Thread(()->
String o = mycache.get(String.valueOf(finalI));
).start();
class MyCache_ReadWriteLock
private volatile Map<String,String> map=new HashMap<>();
//使用读写锁
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
//普通锁
private Lock lock=new ReentrantLock();
public void put(String key,String value)
//加锁
readWriteLock.writeLock().lock();
try
//写入
//业务流程
System.out.println(Thread.currentThread().getName()+" 线程 开始写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName()+" 线程 写入OK");
catch (Exception e)
e.printStackTrace();
finally
readWriteLock.writeLock().unlock(); //解锁
public String get(String key)
//加锁
String o="";
readWriteLock.readLock().lock();
try
//得到
System.out.println(Thread.currentThread().getName()+" 线程 开始读取");
o = map.get(key);
System.out.println(Thread.currentThread().getName()+" 线程 读取OK");
catch (Exception e)
e.printStackTrace();
finally
readWriteLock.readLock().unlock();
return o;
运行效果:
Thread-0 线程 开始写入
Thread-0 线程 写入OK
Thread-1 线程 开始写入
Thread-1 线程 写入OK
Thread-2 线程 开始写入
Thread-2 线程 写入OK
Thread-3 线程 开始写入
Thread-3 线程 写入OK
Thread-4 线程 开始写入
Thread-4 线程 写入OK
# 以上 整个过程没有再出现错乱的情况,对于读取,我们运行多个线程同时读取,
# 因为这样不会造成数据不一致问题,也能在一定程度上提高效率
Thread-9 线程 开始读取
Thread-9 线程 读取OK
Thread-10 线程 开始读取
Thread-5 线程 开始读取
Thread-11 线程 开始读取
Thread-11 线程 读取OK
Thread-10 线程 读取OK
Thread-7 线程 开始读取
Thread-7 线程 读取OK
Thread-6 线程 开始读取
Thread-5 线程 读取OK
Thread-14 线程 开始读取
Thread-8 线程 开始读取
Thread-14 线程 读取OK
Thread-6 线程 读取OK
Thread-13 线程 开始读取
Thread-12 线程 开始读取
Thread-13 线程 读取OK
Thread-8 线程 读取OK
Thread-12 线程 读取OK
10、阻塞队列
阻塞队列jdk1.8文档解释:
BlockingQueue
blockingQueue 是Collection的一个子类;
什么情况我们会使用 阻塞队列呢?
多线程并发处理、线程池!
整个阻塞队列的家族如下:Queue以下实现的有Deque、AbstaractQueue、BlockingQueue;
BlockingQueue以下有Link链表实现的阻塞队列、也有Array数组实现的阻塞队列。
如何使用阻塞队列呢?
操作:添加、移除
但是实际我们要学的有:四组API
方式 | 抛出异常 | 不会抛出异常,有返回值 | 阻塞 等待 | 超时 等待 |
---|---|---|---|---|
添加 | add | offer | put | offer(timenum,timeUnit) |
移除 | remove | poll | take | poll(timenum,timeUnit) |
判断队列首 | element | peek | - | - |
/**
* 抛出异常
*/
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"));
//抛出异常:java.lang.IllegalStateException: Queue full
// System.out.println(blockingQueue.add("d"));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//如果多移除一个
//这也会造成 java.util.NoSuchElementException 抛出异常
System.out.println(blockingQueue.remove());
=======================================================================================
/**
* 不抛出异常,有返回值
*/
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"));
//添加 一个不能添加的元素 使用offer只会返回false 不会抛出异常
System.out.println(blockingQueue.offer("d"));
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//弹出 如果没有元素 只会返回null 不会抛出异常
System.out.println(blockingQueue.poll());
=======================================================================================
/**
* 等待 一直阻塞
*/
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");
System.out.println("开始等待");
blockingQueue.offer("d",2, TimeUnit.SECONDS); //超时时间2s 等待如果超过2s就结束等待
System.out.println("结束等待");
System.out.println("===========取值==================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println("开始等待");
blockingQueue.poll(2,TimeUnit.SECONDS); //超过两秒 我们就不要等待了
System.out.println("结束等待");
SynchronousQueue同步队列
同步队列 没有容量,也可以视为容量为1的队列;
进去一个元素,必须等待取出来之后,才能再往里面放入一个元素;
put方法 和 take方法;
Synchronized 和 其他的BlockingQueue 不一样 它不存储元素;
put了一个元素,就必须从里面先take出来,否则不能再put进去值!
并且SynchronousQueue 的take是使用了lock锁保证线程安全的。
/**
* 同步队列
*/
public class SynchronousQueueDemo
public static void main(String[] args)
BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();
//研究一下 如果判断这是一个同步队列
//使用两个进程
// 一个进程 放进去
// 一个进程 拿出来
new Thread(()->
try
System.out.println(Thread.currentThread().getName()+" Put 1");
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName()+" Put 2");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName()+" Put 3");
synchronousQueue.put("3");
catch (InterruptedException e)
e.printStackTrace();
,"T1").start();
new Thread(()->
try
System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take());
// TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take());
// TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take());
catch (InterruptedException e)
e.printStackTrace();
,"T2").start();
11、线程池(重点)
线程池:三大方法、7大参数、4种拒绝策略
池化技术
程序的运行,本质:占用系统的资源!我们需要去优化资源的使用 ===> 池化技术
线程池、JDBC的连接池、内存池、对象池 等等。。。。
资源的创建、销毁十分消耗资源
池化技术:事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,以此来提高效率。
线程池的好处:
1、降低资源的消耗;
2、提高响应的速度;
3、方便管理;
线程复用、可以控制最大并发数、管理线程;
线程池:三大方法
- ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
- ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
- ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的
//工具类 Executors 三大方法;
public class Demo01
public static void main(String[] args)
ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的
//线程池用完必须要关闭线程池
try
for (int i = 1; i <=100 ; i++)
//通过线程池创建线程
threadPool.execute(()->
System.out.println(Thread.currentThread().getName()+ " ok");
);
catch (Exception e)
e.printStackTrace();
finally
threadPool.shutdown();
线程池:7大参数
源码分析
public static ExecutorService newSingleThreadExecutor()
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
public static ExecutorService newFixedThreadPool(int nThreads)
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
public static ExecutorService newCachedThreadPool()
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
本质:三种方法都是开启的ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大的线程池大小
long keepAliveTime, //超时了没有人调用就会释放
TimeUnit unit, //超时单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂 创建线程的 一般不用动
RejectedExecutionHandler handler //拒绝策略
)
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
阿里巴巴的Java操作手册中明确说明:对于Integer.MAX_VALU
以上是关于狂神JUC笔记的主要内容,如果未能解决你的问题,请参考以下文章