引言
今天是星期一,翘了课在寝室玩农药,正当我要五杀的时候,学姐给我打了个电话,于是我的五杀没了。
“学弟,今天面试官问我了不了解JUC,没答出来怎么办?”学姐哭着问道
“面试官怎么说的啊?”我把自己痛失五杀的愤怒强行压制下去,毕竟这个是在我大一的时候对我照顾有加的学姐,无论是正常的校园生活和不正常的校园生活,要我舒舒服服的混到了大三,是、人要有一颗感恩的心,所以我还是忍住没有和她发脾气。
“面试官说日后再说”,学姐说道
“这样的吗?这个面试官可真不是什么好东西?”
“为什么这么说啊”?学姐不解的说道。
“没事学姐,我给你写一篇JUC的文章,你有时间看看就好了”
什么是 JUC
JUC就是 java.util 下的工具包、包、分类等。
“在我们学习JUC之前,先去回顾一下线程方面的知识,学姐你要是不会的话可以子去看一下别的博主的博客,我以后有时间会写”,我说道
“好的学弟”
普通的线程
- Thread
- Runnable 没有返回值、效率相比入 Callable 相对较低!
- Callable 有返回值!
线程和进程
我之前就有写过一篇关于线程和进程的文章,可以先看看这篇文章
- 进程:一个程序,QQ.exe Music.exe 程序的集合;
- 一个进程往往可以包含多个线程,至少包含一个
- Java默认有2个线程? mian、GC
- 线程:开了一个进程 Typora,写字,自动保存(线程负责的)
- 对于Java而言提供了:Thread、Runnable、Callable操作线程。
并发、并行
并发
当我们有若干个线程需要运行的时候,由于cpu处理速度很快,他会把若干个线程快速交替,就是多线程同时操作资源
并行
假如我们的电脑是36核的,36个cpu同时运行
可以用这个代码来检查一下自己电脑是几核的,我的是16核
public class Test1 {
public static void main(String[] args) {
// 获取cpu的核数
// CPU 密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
// 如果电脑是8核,则结果输出8
}
}
注:
并发编程的本质就是充分利用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 区别
第一:两者来自不同的父类
- wait => Object
- sleep => Thread
第二:两者释放锁不一样
- wait 会释放锁
- sleep 睡觉了,抱着锁睡觉,不会释放!
第三:两者使用的范围是不同的
- wait 必须在同步代码块中使用
- sleep 可以再任何地方睡眠
Synchronized锁
传统 Synchronized锁:一个简单的卖票例子
package com.znb;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test01 {
public static void main(String[] args) {
//并发:多个线程同时操作一个资源类,把资源类丢入线程
Ticket ticket = new Ticket();
// @FunctionalInterface 函数式接口,jdk1.8 lambada表达式
new Thread(() -> {
for (int i = 1; i < 50; i++) {
ticket.sale();
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i < 50; i++) {
ticket.sale();
}
}, "B").start();
new Thread(() -> {
for (int i = 1; i < 50; 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() + "卖出了" +
(50-(--number)) + "张票,剩余:" + number + "张票");
}
}
}
Lock锁
公平锁(FairLock)
十分公平,分先来后到,不准插队
非公平锁 (UnFairLock)
不公平,可以插队
一般都是默认非公平锁,因为有时候如果一个线程的延时是10分钟,另一个线程没有延时,如果延时十分钟的线程先到,那么后一个线程就要等十分钟。
将上面的卖票例子用lock锁 替换synchronized:
package com.znb;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test01 {
public static void main(String[] args) {
Ticket ticket=new Ticket();
new Thread(()->{
for (int i = 0; i < 30; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 30; i++) {
ticket.sale();
}},"B").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}},"C").start();
}
}
class Ticket{
private int number =100;
Lock lock=new ReentrantLock();
public synchronized void sale(){
if (number>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"张票子");
}
}
}
Synchronized 和 Lock 区别:
- Synchronized 内置的Java关键字, Lock 是一个Java类
- Synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以判断锁,非公平(可以自己设置)
- Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
- Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,就会死锁
- Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码
- Synchronized 线程 1(获得锁,如果线程1阻塞)、线程2(等待);Lock锁就不一定会等待下去;
生产者和消费者问题
非JUC版
可以参考一下我之前的博客
package com.znb.pc;
public class Test01 {
public static void main(String[] args) {
A a=new A();
new Thread(()->{
for (int i = 0; i < 100; i++) {
try {
a.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
try {
a.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
try {
a.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
try {
a.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class A{
private int sum=0;
public synchronized void incr() throws InterruptedException {
while(sum!=0){
this.wait();
}
sum++;
System.out.println(Thread.currentThread().getName()+"==》"+sum);
this.notifyAll();
}
public synchronized void decr() throws InterruptedException {
while (sum==0){
this.wait();
}
sum--;
System.out.println(Thread.currentThread().getName()+"==》"+sum);
this.notifyAll();
}
}
这里我们通过线程A唤醒B,B唤醒C以此类推,并且这里我们用的是while,因为如果我们用的是if,并且线程大于2个,那么就会存在虚假唤醒,出现错误
使用while的结果
使用if的结果
我们可以看见,出现了-169,-170这样错误的数据,所以这里记得用while
JUC版
这里引进一个新东西,叫Condition
代码实现
package com.znb.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test02 {
public static void main(String[] args) {
B a=new B();
new Thread(()->{
for (int i = 0; i < 100; i++) {
try {
a.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
try {
a.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
try {
a.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
try {
a.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class B{
private int sum=0;
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
public void incr() throws InterruptedException {
lock.lock();
try {
while(sum!=0){
condition.await();
}
sum++;
System.out.println(Thread.currentThread().getName()+"==》"+sum);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decr() throws InterruptedException {
lock.lock();
try {
while (sum==0){
condition.await();
}
sum--;
System.out.println(Thread.currentThread().getName()+"==》"+sum);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
当我们使用Condition 的时候,我们唤醒线程和要线程等待就不是用什么 wait,sleep ,notifyAll这些东西了,我们需要使用 await 等待,signalAll唤醒
但是细心的我们发现,我们线程的顺序是A->B->C->D,但是最后输出的答案并不是这样,那要怎么办呢?没关系JDK源码的作者也想到了这个问题。
package com.znb.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test03 {
public static void main(String[] args) {
C c=new C();
new Thread(()->{
for (int i = 0; i < 10; i++) {
c.printf1();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
c.printf2();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
c.printf3();
}
},"C").start();
}
}
class C{
private Lock lock=new ReentrantLock();
private Condition condition1=lock.newCondition();
private Condition condition2=lock.newCondition();
private Condition condition3=lock.newCondition();
private int sum=1;
public void printf1(){
lock.lock();
try {
while (sum!=1){
condition1.await();
}
System.out.println("你好");
sum=2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printf2(){
lock.lock();
try {
while (sum!=2){
condition2.await();
}
System.out.println("我是帅哥");
sum=3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printf3(){
lock.lock();
try {
while (sum!=3){
condition3.await();
}
System.out.println("你觉得呢");
sum=1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
8锁现象
8锁,就是关于锁的8个问题
package com.znb.eightlock;
import java.util.concurrent.TimeUnit;
public class Test01 {
public static void main(String[] args) {
Phone phone = new Phone();
// 锁的存在
new Thread(()->{
phone.sendSms();
},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
// synchronized 锁的对象是方法的调用者!、
// 两个方法用的是同一个对象调用(同一个锁),谁先拿到锁谁执行!
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);// 抱着锁睡眠
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
package com.znb.eightlock;
import java.util.concurrent.TimeUnit;
/**
* 3、 增加了一个普通方法后!先执行发短信还是Hello?// 普通方法
* 4、 两个对象,两个同步方法, 发短信还是 打电话? // 打电话
*/
public class Test02 {
public static void main(String[] args) {
// 两个对象,两个调用者,两把锁!
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
//锁的存在
new Thread(()->{
phone1.sendSms();
},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
new Thread(()->{
phone2.hello();
},"C").start();
}
}
class Phone2{
// synchronized 锁的对象是方法的调用者!
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
// 这里没有锁!不是同步方法,不受锁的影响
public void hello(){
System.out.println("hello");
}
}
// 先执行打电话,接着执行hello,最后执行发短信
package com.znb.eightlock;
import java.util.concurrent.TimeUnit;
public class Test03 {
public static void main(String[] args) {
// 两个对象的Class类模板只有一个,static,锁的是Class
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
//锁的存在
new Thread(()->{
phone1.sendSms();
},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
// Phone3唯一的一个 Class 对象
class Phone3{
// synchronized 锁的对象是方法的调用者!
// static 静态方法
// 类一加载就有了!锁的是Class
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
package com.znb.eightlock;
import java.util.concurrent.TimeUnit;
public class Test04 {
public static void main(String[] args) {
// 两个对象的Class类模板只有一个,static,锁的是Class
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
//锁的存在
new Thread(()->{
phone1.sendSms();
},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
// Phone3唯一的一个 Class 对象
class Phone4{
// 静态的同步方法 锁的是 Class 类模板
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 普通的同步方法 锁的调用者(对象),二者锁的对象不同,所以不需要等待
public synchronized void call(){
System.out.println("打电话");
}
}
// 7/8 两种情况下,都是先执行打电话,后执行发短信,因为二者锁的对象不同,
// 静态同步方法锁的是Class类模板,普通同步方法锁的是实例化的对象,
// 所以不用等待前者解锁后 后者才能执行,而是两者并行执行,因为发短信休眠4s
// 所以打电话先执行。
总结
每个对象以及每个class文件都会有自己的锁(实质是标志位),如果调用的方法是非静态方法,那么jvm会锁住调用的对象,但是如果是静态方法则会锁住class的锁,不同对象都会有自己的锁,他们之间互不干扰,而不同对象又共享 class 的锁。
集合类不安全
List
我们平时做一些学生项目的时候,例如个人博客,List应该用的很多,但是List的线程并不安全,我们来看一下代码
package com.znb.unsafe;
import java.util.*;
import java.util.function.Consumer;
public class ListTest01 {
public static void main(String[] args) {
List<String> list= new ArrayList<>();
for (int i = 1; i <= 100; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
注意,如果这里没报错可能是你电脑太好, 把循环数变大看看比方说1000,10000,要是这样还没有报错那你的电脑是真的好
所以我们这里引入三种解决方法
- 方案1、List list = new Vector<>();
- 方案2、List list =Collections.synchronizedList(new ArrayList<>());
- 方案3、List list = new CopyOnWriteArrayList<>();
package com.znb.unsafe;
import java.util.*;
import java.util.function.Consumer;
public class ListTest01 {
public static void main(String[] args) {
List<String> list= Collections.synchronizedList(new ArrayList<>());
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
Set、Hash 等在并发多线程条件下,不能实现数据共享,多个线程同时调用一个set对象时候就会出现并发修改异常ConcurrentModificationException
package com.znb.unsafe;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
public class SetTest01 {
public static void main(String[] args) {
Set set=new HashSet();
for (int i = 1; i <= 100; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
注意,如果这里没报错可能是你电脑太好, 把循环数变大看看比方说1000,10000,要是这样还没有报错那你的电脑是真的好
所以我们这里引入两种种解决方法
- 方案一:Set set =Collections.synchronizedSet(new HashSet<>());
- 方案二:Set set=new CopyOnWriteArraySet();
package com.znb.unsafe;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
public class SetTest01 {
public static void main(String[] args) {
Set set=new CopyOnWriteArraySet();
for (int i = 1; i <= 100; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
Map
我之前写了很长一篇关于hashmap的文章,可以先看一下
JDK1.8HashMap底层实现原理
package com.znb.unsafe;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class MapTest01 {
public static void main(String[] args) {
Map map=new ConcurrentHashMap();
for (int i = 1; i <= 100; i++) {
new Thread(()->{
map.put(UUID.randomUUID().toString().substring(0,5),"A");
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
Callable
Callable 和 Runable 对比:
- Callable 是 java.util 包下 concurrent 下的接口,有返回值,可以抛出被检查的异常
- Runable 是 java.lang 包下的接口,没有返回值,不可以抛出被检查的异常
- 二者调用的方法不同,run()/ call()
同样的 Lock 和 Synchronized 二者的区别,前者是java.util 下的接口 后者是 java.lang 下的关键字。
package com.znb.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// new Thread(new Runnable()).start();// 启动Runnable
// new Thread(new FutureTask<V>()).start();
// new Thread(new FutureTask<V>( Callable )).start();
new Thread().start(); // 怎么启动Callable?
// new 一个MyThread实例
MyThread thread = new MyThread();
// MyThread实例放入FutureTask
FutureTask futureTask = new FutureTask(thread); // 适配类
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start(); // call()方法结果会被缓存,提高效率,因此只打印1个call
// 这个get 方法可能会产生阻塞!把他放到最后
Integer o = (Integer) futureTask.get();
// 或者使用异步通信来处理!
System.out.println(o);// 1024
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() {
System.out.println("call()"); // A,B两个线程会打印几个call?(1个)
// 耗时的操作
return 1024;
}
}
//class MyThread implements Runnable {
//
// @Override
// public void run() {
// System.out.println("run()"); // 会打印几个run
// }
//}
细节:
1、有缓存
2、结果可能需要等待,会阻塞!
CountDownLatch
减法计数器: 实现调用几次线程后 再触发某一个任务
package com.znb.jucutils;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() +" Go out");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println("Close Door");
}
}
CyclicBarrier
减法计数器: 实现调用几次线程后 再触发某一个任务
package com.znb.jucutils;
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++) {
final int temp = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()
+"收集"+temp+"个女朋友");
try {
cyclicBarrier.await(); // 等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Semaphore
限流/抢车位!6车—3个停车位置
package com.znb.jucutils;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
// 线程数量:停车位! 限流!、
// 如果已有3个线程执行(3个车位已满),则其他线程需要等待‘车位’释放后,才能执行!
Semaphore semaphore = new Semaphore(3);
for (int i = 1; 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) {
e.printStackTrace();
} finally {
semaphore.release(); // release() 释放
}
},String.valueOf(i)).start();
}
}
}
读写锁 ReadWriteLock
ReadWriteLock 读可以被很多线程读,写只可以被一个线程写
package com.znb.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 myCache = new MyCache();
MyCacheLock myCacheLock = new MyCacheLock();
// 写入
for (int i = 1; i <= 5 ; i++) {
final int temp = i;
new Thread(()->{
myCacheLock.put(temp+"",temp+"");
},String.valueOf(i)).start();
}
// 读取
for (int i = 1; i <= 5 ; i++) {
final int temp = i;
new Thread(()->{
myCacheLock.get(temp+"");
},String.valueOf(i)).start();
}
}
}
/**
* 自定义缓存
* 加锁的
*/
class MyCacheLock{
private volatile Map<String,Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new
ReentrantReadWriteLock();
public void put(String key,Object value){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()
+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()
+"写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()
+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()
+"读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
class MyCache{
private volatile Map<String,Object> map = new HashMap<>();
// 存,写
public void put(String key,Object value){
System.out.println(Thread.currentThread().getName()
+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()
+"写入OK");
}
// 取,读
public void get(String key){
System.out.println(Thread.currentThread().getName()
+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()
+"读取OK");
}
}
- 独占锁(写锁) 一次只能被一个线程占有
- 共享锁(读锁) 多个线程可以同时占有
- ReadWriteLock
- 读-读 可以共存!
- 读-写 不能共存!
- 写-写 不能共存!
阻塞队列
BlockingQueue
BlockingQueue 不是新的东西
什么情况下我们会使用 阻塞队列?:多线程并发处理,线程池用的较多 !
学会使用队列
添加、移除
四组API
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞,等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer |
移除 | remove | poll | take | pool |
检查首元素 | element | peek | 无 | 无 |
package com.znb.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class Test01 {
public static void main(String[] args) {
test2();
}
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"));
System.out.println( blockingQueue.remove());
System.out.println(blockingQueue.add("d"));
}
/**
* 2. 有返回值,不抛出异常的方式
*/
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.peek());
// System.out.println(blockingQueue.offer("d"));
// false 不抛出异常!
System.out.println("===========================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
// null 不抛出异常!
}
/**
* 3. 等待,阻塞(一直阻塞)
*/
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());
// 没有这个元素,一直阻塞等待
}
/**
* 4. 等待,阻塞(等待超时)
*/
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);
// 等待超过2秒就退出
System.out.println("===============");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
blockingQueue.poll(2, TimeUnit.SECONDS); // 等待超过2秒就退出
}
}
SynchronousQueue
同步队列,没有容量,进去一个元素,必须等待取出来之后,才能再往里面放一个元素!
put、take
package com.znb.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>(); // 同步队列
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() +" put 1");
// put进入一个元素
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName() +" put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName() +" put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
// 睡眠3s取出一个元素
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() +"=>"+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() +"=>"+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() +"=>"+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
线程池
池化技术
- 程序的运行,本质:占用系统的资源! (优化资源的使用 => 池化技术)
- 线程池、连接池、内存池、对象池///… 创建、销毁。十分浪费资源
- 池化技术:事先准备好一些资源,有人要用,就来我这里拿,用完之后还给我。
线程池的好处:
- 降低系统资源的消耗
- 提高响应的速度
- 方便管理
线程池:3大方法
package com.znb.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test01 {
public static void main(String[] args) {
// Executors 工具类、3大方法
// Executors.newSingleThreadExecutor();// 创建单个线程的线程池
// Executors.newFixedThreadPool(5);// 创建一个固定大小的线程池
// Executors.newCachedThreadPool();// 创建一个可伸缩的线程池
// 单个线程的线程池
ExecutorService threadPool =
Executors.newCachedThreadPool();
try {
for (int i = 1; i < 10; i++) {
// 使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(
Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
7大参数
因为实际开发中工具类Executors 不安全,所以需要手动创建线程池,自定义7个参数。
package com.znb.pool;
import java.util.concurrent.*;
public class Test02 {
public static void main(String[] args) {
// 自定义线程池!工作 ThreadPoolExecutor
ExecutorService threadPool = new ThreadPoolExecutor(
2,// int corePoolSize, 核心线程池大小(候客区窗口2个)
5,// int maximumPoolSize, 最大核心线程池大小(总共5个窗口)
3,// long keepAliveTime, 超时3秒没有人调用就会释,放关闭窗口
TimeUnit.SECONDS,// TimeUnit unit, 超时单位 秒
new LinkedBlockingDeque<>(3),// 阻塞队列(候客区最多3人)
Executors.defaultThreadFactory(),// 默认线程工厂
// 4种拒绝策略之一:
// 队列满了,尝试去和 最早的竞争,也不会抛出异常!
new ThreadPoolExecutor.DiscardOldestPolicy());
//队列满了,尝试去和最早的竞争,也不会抛出异常!
try {
// 最大承载:Deque + max
// 超过 RejectedExecutionException
for (int i = 1; i <= 90; i++) {
// 使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(
Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
4种拒绝策略
new ThreadPoolExecutor.AbortPolicy()
银行满了,还有人进来,不处理这个人的,抛出异常
new ThreadPoolExecutor.CallerRunsPolicy()
哪来的去哪里!比如你爸爸 让你去通知妈妈洗衣服,妈妈拒绝,让你回去通知爸爸洗
new ThreadPoolExecutor.DiscardPolicy()
队列满了,丢掉任务,不会抛出异常!
new ThreadPoolExecutor.DiscardOldestPolicy()
队列满了,尝试去和最早的竞争,也不会抛出异常!
IO密集型,CPU密集型:(调优)
package com.znb.pool;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class Demo01 {
public static void main(String[] args) {
// 自定义线程池!工作 ThreadPoolExecutor
// 最大线程到底该如何定义
// 1、CPU 密集型,几核,就是几,可以保持CPu的效率最高!
// 2、IO 密集型 > 判断你程序中十分耗IO的线程,
// 比如程序 15个大型任务 io十分占用资源!
// IO密集型参数(最大线程数)就设置为大于15即可,一般选择两倍
// 获取CPU的核数
System.out.println(
Runtime.getRuntime().availableProcessors());// 8核
ExecutorService threadPool = new ThreadPoolExecutor(
2,// int corePoolSize, 核心线程池大小
// int maximumPoolSize, 最大核心线程池大小 8核电脑就是8
Runtime.getRuntime().availableProcessors(),
3,// long keepAliveTime, 超时3秒没有人调用就会释放
TimeUnit.SECONDS,// TimeUnit unit, 超时单位 秒
new LinkedBlockingDeque<>(3),// 阻塞队列(候客区最多3人)
Executors.defaultThreadFactory(),// 默认线程工厂
// 4种拒绝策略之一:
// 队列满了,尝试去和 最早的竞争,也不会抛出异常!
new ThreadPoolExecutor.DiscardOldestPolicy());
//队列满了,尝试去和最早的竞争,也不会抛出异常!
try {
// 最大承载:Deque + max
// 超过 RejectedExecutionException
for (int i = 1; i <= 9; i++) {
// 使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(
Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
“怎么样学姐,对JUC是不是有一定的了解了”
“对啊学弟,你写的太棒了”学姐说道
“那剩下的过几天再讲,我现在很累了”
“没事学弟,学姐下面给你吃,学姐下面可好吃了”
“不行,我要回去睡觉了”
学姐天天缠着问我JUC,搞得我没有时间打游戏,无奈之下我写下JUC基础,要她自己去研究 下
注:以上故事纯属扯淡
转载:https://blog.csdn.net/weixin_43145539/article/details/117418046