文章目录
一、idea环境搭建
-
创建一个Maven工程,检查该工程的JDK版本;设置特定的语言级别,JDK-1.8的新特性
二、 多线程知识回顾
- 并行与并发的概念
并行就是多核CPU同时执行线程;
并发是适用于单核CPU,快速的轮转线程
public class Test{
public static void main(String[] args){
//获取CPU核数 一般都是12核
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
- 面试题:java能操作线程吗?
不能,因为启动线程的是start();而看start()的源码可以发现,线程被装进了一个组groud;然后被start0()这个方法执行;而start0()这个是在JVM的本地方法区内的,是C++写的;是与硬件相关的;所以,java不能操作线程,只能调用start()去启动
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. */
//把启动的线程装进这个组内groud
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 */
}
}
}
//JVM中本地方法区内的方法 被native修饰
private native void start0();
- 面试题:线程的状态有几个?
6种 | 直接看源码 | 枚举 |Thread.state
public enum State {
//new状态 新生
NEW,
//可运行状态 一种复合状态 准备运行|正在运行 由JVM决定
RUNNABLE,
//阻塞
BLOCKED,
//等待,死等: 没其他线程唤醒就会一直等下去
WAITING,
//超时等待 有等待时间,时间一到,就不等了
TIMED_WAITING,
//终止
TERMINATED;
}
- 面试题:wait()与sleep()的区别
1.wait来自Object类;sleep来自Thread类
2.wait会释放锁对象;sleep不会释放锁对象
3.wait必须在同步代码块中使用;sleep可以随意使用
4.wait不需要捕获异常;sleep必须要捕获异常
5.一般不写sleep(),而是写TimeUnit.DAYS.sleep(1)*TimeUnit.SECONFD.sleep(2)*
- 传统的多线程并发的去访问统一资源
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
//自定义锁
Object lock = new Object();
//定义同一资源
Bank bank = new Bank();
//创建线程
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
bank.draw();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"李四").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
bank.draw();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"王五").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
synchronized (lock){
bank.save();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"行老板").start();
}
}
//定义一个银行类 Bank类 完全与线程无关 解耦合 一种认可的编码方式
class Bank{
//金额
private double money = 9000;
//取钱
public synchronized void draw() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
if (money <= 300){
System.out.println(Thread.currentThread().getName()+"还剩"+money+"不够取了");
}else {
this.money = money-300;
System.out.println(Thread.currentThread().getName()+"取了300块,还剩"+money);
}
}
public void save() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
this.money = money+600;
System.out.println(Thread.currentThread().getName()+"存了600块,还剩"+money);
}
}
三、lock锁 深度解析
-
先看JAVA-API文档
-
Lock锁 源码分析实现类ReentrantLock()
Lock lock = new ReentrantLock();
//new NonfairSync() 表示一个非公平锁 JVM采用的是 非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//传入一个Boolean值 new FairSync() 表示公平锁 | new NonfairSync() 非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁与非公平锁的区别
公平锁:多个线程排好队,一个一个来占用锁对象;效率低
非公平锁:当A线程一抢占CPU时,就会去尝试占用锁对象;若没有占用到锁对象,就会去排队;占到了就用;所以,当多线程访问同一资源时,有时候是线程A占用锁对象;有时候是线程B占用锁对象;无序的一种状态
这里要分清楚:占用CPU资源 与 抢占锁对象;这是两个概念;当一个线程去占用CPU资源后,才有资格去抢占锁对象
- 使用Lock锁
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test01 {
public static void main(String[] args) {
//同一资源对象
Costs cost = new Costs();
new Thread(()->{
for (int i = 0; i < 15; i++) {
try {
cost.eat();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"大吃货").start();
new Thread(()->{
for (int i = 0; i < 15; i++) {
cost.doBun();
}
},"男老板").start();
new Thread(()->{
for (int i = 0; i < 15; i++) {
cost.doBun();
}
},"女老板").start();
}
}
//定义包子铺
class Costs{
//包子
private int bun = 10;
//lock锁
Lock lock = new ReentrantLock();
//做包子
public void doBun() {
//加锁
lock.lock();
try{
if (bun >= 30){
System.out.println(Thread.currentThread().getName() + "说“包子有30个了,我先睡四秒再做”");
TimeUnit.SECONDS.sleep(4);
}else {
this.bun +=3;
System.out.println(Thread.currentThread().getName() + "-->做了三个包子,现在有"+bun);
}
}catch (Exception e){
e.printStackTrace();
}finally {
//解锁
lock.unlock();
}
}
//吃包子
public synchronized void eat() throws InterruptedException {
if (bun <= 5){
System.out.println(Thread.currentThread().getName()+"包子只有"+bun+"个,不够我吃;我先睡三秒");
TimeUnit.SECONDS.sleep(3);
}else {
this.bun -= 5;
System.out.println(Thread.currentThread().getName()+"吃了5个包子,还剩"+bun);
}
}
}
四、lock锁-condition.await() | condition.signalAll()
查看java-API文档
先来回顾一下synchronize版的消费者与生产者问题
import java.util.concurrent.TimeUnit;
public class Test02 {
public static void main(String[] args) {
Candy candy = new Candy();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
candy.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"老板").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
candy.minus();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"顾客1").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
candy.minus();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"顾客2").start();
}
}
class Candy{
private int sweets = 10;
//+3
public synchronized void add() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
if (this.sweets >= 20){
System.out.println(Thread.currentThread().getName()+"==> 糖果够了,我先休息一下");
this.wait();
}else{
this.sweets+=3;
System.out.println(Thread.currentThread().getName()+"==> 糖果"+sweets);
this.notifyAll();
}
}
//-1
public synchronized void minus() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
if (this.sweets <= 3){
System.out.println(Thread.currentThread().getName()+"==> 糖果不够吃了,我先休息一下");
wait();
}else{
this.sweets--;
System.out.println(Thread.currentThread().getName()+"==> 糖果"+sweets);
this.notifyAll();
}
}
}
使用Lock锁
import java.util.concurrent.TimeUnit;
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) {
Candy candy = new Candy();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
candy.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"老板").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
candy.minus();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"顾客1").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
candy.minus();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"顾客2").start();
}
}
class Candy{
private int sweets = 10;
//lock锁
Lock lock = new ReentrantLock();
//Condition对象
Condition connection01 = lock.newCondition();
Condition connection02 = lock.newCondition();
//+3
public void add() throws InterruptedException {
try {
lock.lock();
TimeUnit.SECONDS.sleep(2);
if (this.sweets >= 20){
System.out.println(Thread.currentThread().getName()+"==> 糖果够了,我先休息一下");
connection01.await();
}else{
this.sweets+=3;
System.out.println(Thread.currentThread().getName()+"==> 糖果"+sweets);
connection01.signal();
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
//-1
public void minus() throws InterruptedException {
try {
lock.lock();
TimeUnit.SECONDS.sleep(2);
if (this.sweets <= 3){
System.out.println(Thread.currentThread().getName()+"==> 糖果不够吃了,我先休息一下");
connection02.await();
}else{
this.sweets--;
System.out.println(Thread.currentThread().getName()+"==> 糖果"+sweets);
connection02.signal();
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
我在上面代码写了两个Condition对象;可以试着比较一下一个Condition对象与两个Contition对象的区别
lock锁实现有序的精准唤醒
import java.util.concurrent.TimeUnit;
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) {
LineUp test = new LineUp();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
test.one();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"1号学生").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
test.tow();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"2号学生").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
test.three();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"3号学生").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
test.fore();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"4号学生").start();
}
}
class LineUp{
Lock lock = new ReentrantLock();
Condition one = lock.newCondition();
Condition tow = lock.newCondition();
Condition three = lock.newCondition();
Condition fore = lock.newCondition();
int number = 1;
public void one() throws InterruptedException {
lock.lock();
TimeUnit.SECONDS.sleep(1);
try {
while(number != 1){
//持有one这个condition对象的线程等待
one.await();
}
System.out.println(Thread.currentThread().getName());
number = 2;
// 唤醒持有tow这个condition对象的线程
tow.signal();
}catch(Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void tow() throws InterruptedException {
lock.lock();
TimeUnit.SECONDS.sleep(1);
try {
while(number != 2){
tow.await();
}
System.out.println(Thread.currentThread().getName());
number = 3;
three.signal();
}catch(Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void three() throws InterruptedException {
lock.lock();
TimeUnit.SECONDS.sleep(1);
try {
while(number != 3){
three.await();
}
System.out.println(Thread.currentThread().getName());
number = 4;
fore.signal();
}catch(Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void fore() throws InterruptedException {
lock.lock();
TimeUnit.SECONDS.sleep(1);
try {
while(number != 4){
fore.await();
}
System.out.println(Thread.currentThread().getName());
number = 1;
one.signal();
}catch(Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
五、锁是什么?
三锁现象
1.同一资源的两个普通的同步方法
public class Test04 {
public static void main(String[] args) {
//同一资源
One one = new One();
new Thread(()->{
for (int i = 0; i < 3; i++) {
one.first();
}
},"一号").start();
new Thread(()->{
for (int i = 0; i < 3; i++) {
one.second();
}
},"二号").start();
}
}
class One{
public synchronized void first(){
System.out.println(Thread.currentThread().getName());
}
public synchronized void second(){
System.out.println(Thread.currentThread().getName());
}
}
上述代码,不用想,肯定是先执行“1号”,再执行“2号”;为什么是这个现象呢?
观点一:一号线程先执行 | 没错,但是不够全面
观点二: 一号线程先抢占了锁 | 正确答案
那么问题又来了,锁指的是什么?
观点一:锁是线程进入同步方法时,One类专门去new的一个对象
观点二:锁就是main()中的同一共享资源
2.不同资源的两个同步方法
import java.util.concurrent.TimeUnit;
public class Test04 {
public static void main(String[] args) {
//资源1
One one = new One();
//资源2
One tow = new One();
new Thread(()->{
for (int i = 0; i < 3; i++) {
try {
one.first();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"一号").start();
new Thread(()->{
for (int i = 0; i < 3; i++) {
try {
tow.second();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"二号").start();
}
}
class One{
public synchronized void first() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName());
}
public synchronized void second() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName());
}
}
上述代码可能先输出“一号”,可能先输出“二号”;所以,两个线程获取的是两把不同的锁;推翻了上述的观点一;所以,锁就是资源;就是在main()内new出来的那个资源对象!
3.同一资源的类加载同步方法与普通同步方法
import java.util.concurrent.TimeUnit;
public class Test04 {
public static void main(String[] args) {
//资源1
One one = new One();
new Thread(()->{
for (int i = 0; i < 3; i++) {
try {
one.first();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"一号").start();
new Thread(()->{
for (int i = 0; i < 3; i++) {
try {
one.second();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"二号").start();
}
}
class One{
public static synchronized void first() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName());
}
public synchronized void second() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName());
}
}
被static修饰的方法,是类加载方法 ;在类一加载时就完成创建了;上述的线程一号和二号,持有的锁是不一样的两把锁!二号线程执行的方法没有被static修饰,所以,线程二号的锁还是资源那把锁one One one = new One();;而一号线程持有的就是 One.class这把锁;任何一个类,在类加载完毕时,都会有一个唯一的class模板
总结
- 无论是八锁现象还是三锁现象,在意的仅仅是:这个方法是用什么锁来锁定的
- 被static修饰的同步方法,持有的锁一定是类.class;且类.class唯一
- 普通方法不受锁的影响普通方法压根就没得锁,怎么影响?
- 同步方法的锁一定是资源对象谁调用逻辑方法,谁就是锁
六、多线程下的集合类不安全
List
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
public class Test01 {
public static void main(String[] args) {
/*List*/
List<String> list = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 10; i++) {
new Thread(()->{
/*加入随机数*/
list.add(UUID.randomUUID().toString().substring(0,3));
System.out.println(Thread.currentThread().getName()+"===>"+list);
},"线程"+i).start();
}
}
}
ConcurrentModificationException 线程的并发修改异常
- ArrayList类为什么不安全?
因为没锁呀,就是这么简单
//ArrayList的add方法没有加锁
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
- 解决方案一:我去给add方法加个锁呗,但是add()是源码,无法修改;JDK官方帮你写了一个加锁的add方法;但是不在ArrayList类中,而在Vector类中
import java.util.*;
public class Test01 {
public static void main(String[] args) {
/*List 下实现了 ArrayList类 和 加锁的Vector类 多态 父类引用指向子类对象*/
List<String> list = new Vector<>();
Random random = new Random();
for (int i = 0; i < 10; i++) {
new Thread(()->{
/*加入随机数*/
list.add(UUID.randomUUID().toString().substring(0,3));
System.out.println(Thread.currentThread().getName()+"===>"+list);
},"线程"+i).start();
}
}
}
来看一下Vector类的add()源码 ;是不是加了锁的
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
问大家一个问题:ArrayList类在哪版JDK出来的,Vector类在哪版JDK出来的?
既然,Vector类是为了解决ArrayList类的线程不安全问题,那么肯定是ArrayList类先出来,然后才是Vector类,没错吧
上述这两个图,看得出来:ArrayList类是在JDK1.2版本才有的,而Vector类在JDK1.0就有了
- 解决方案二:使用 Collections.synchronizedList(new ArrayList<>());来创建list集合
import java.util.*;
public class Test01 {
public static void main(String[] args) {
/*List*/
List<String> list = Collections.synchronizedList(new ArrayList<String>());
Random random = new Random();
for (int i = 0; i < 10; i++) {
new Thread(()->{
/*加入随机数*/
list.add(UUID.randomUUID().toString().substring(0,3));
System.out.println(Thread.currentThread().getName()+"===>"+list);
},"线程"+i).start();
}
}
}
解决方案三:使用JUC包中的CopyOnWriteArrayList<>();写入时复制!并发线程常用!
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class Test01 {
public static void main(String[] args) {
/*List*/
List<String> list = new CopyOnWriteArrayList<>();
Random random = new Random();
for (int i = 0; i < 10; i++) {
new Thread(()->{
/*加入随机数*/
list.add(UUID.randomUUID().toString().substring(0,3));
System.out.println(Thread.currentThread().getName()+"===>"+list);
},"线程"+i).start();
}
}
}
Set
解决方法都类似!只不过Set只有后两种解决方法
1.Set list =Collections.synchronizedSet(new HashSet<>());
使用Collections类转换为同步的方法
2.Set list =new CopyOnWriteArraySet<>();
写入时复制,并发专用
面试题:set集合的底层原理是什么?
HashSet集合的底层就是hashMap;
public HashSet() {
map = new HashMap<>();
}
HashSet类的add()是根据什么来实现的?
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
注意:Set、List 用的是add() | Map用的是put()
Map
跟Set一模一样!重点是Map的原理!
七、Callable接口(解析)
Java-API文档解说Callable接口
- 实现一个Callable的接口,就会返回T类型的数据
- 重写call方法,且不能传参
- 最重要的一点:可能抛出异常!!!
你有见过main线程抛出异常吗?Runnable接口能抛出异常吗?不能!为啥?
有异常抛出,就一定要处理异常!main线程是主线程,它本来就是最底层了,它能抛给谁呀?对吧。那么,Callable接口能抛出异常,就说明它下面还有个东西去给它兜底!这里先做个铺垫
Callable接口的使用
- 只要是线程,都必须得 new Thread(XXX); 看一下Thread的构造器
哎!没有 **Thread(Callable target)**这个构造器呀;那我怎么搞一个线程出来?
-
那就先将就一下吧,先传一个实现了Runnable接口的对象来吧;Runnable接口的实现类有很多!
-
我就直白说了!FutureTask类作为Runnable接口的实现类之一;它的构造方法有:
所以,回到之前那个问题:凭什么Callable接口能抛出异常?
因为,Callable接口经过FutureTask类的转换后,还是变成了Runnable接口的对象!
代码案例
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test02 {
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
//future 未来
FutureTask<String> stringFutureTask = new FutureTask<>(myCallable);
new Thread(stringFutureTask,"线程一号").start();
//获取返回值
try {
String s = stringFutureTask.get();
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + "======> ");
return "返回的字符串类型";
}
}
八、多线程常用的辅助类
类似于减法 CountDownLatch
import java.util.concurrent.CountDownLatch;
public class Test03 {
public static void main(String[] args) throws InterruptedException {
/*倒计时 20次*/
CountDownLatch countDownLatch = new CountDownLatch(20);
Rice rice = new Rice();
/*假设有15个线程顾客*/
for (int i = 1; i <= 20; i++) {
new Thread(()->{
rice.eat();
//计数器 减1
countDownLatch.countDown();
},"第"+i+"位顾客").start();
}
//等待计数器归零,才执行后续代码逻辑
countDownLatch.await();
System.out.println("我是"+Thread.currentThread().getName()+"==>我要打烊了");
}
}
class Rice{
void eat(){
System.out.println(Thread.currentThread().getName() + "==>吃好了");
}
}
类似于加法 CyclicBarrier
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class Test03 {
public static void main(String[] args) throws InterruptedException {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("当计数达到7时,我才执行");
});
for (int i = 0; i <= 7; i++) {
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + "我给计数器cyclicBarrier加了1");
//每执行这串代码一次,计数加 1
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},i+"线程").start();
}
}
}
限定线程个数 Semaphore
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class Test04 {
public static void main(String[] args) {
/*限流 限定线程的数量
* 可以这么理解:一桌八个位置;现在有20个人;怎么才能保证剩余的12人不去抢??
* */
Semaphore semaphore = new Semaphore(8);
for (int i = 1; i <= 20; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"我抢到了位置,开始吃饭");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"我吃好了,我离开位置了;下一个来");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},"我是第"+i+"个人").start();
}
}
}
九、 读写锁 ReadWriteLock接口
写操作: 假如你和你女朋友只有一个手机,你正在玩王者,玩到一半;你女朋友就把手机抢了;你是不是很有难受?所以,要设置一把锁(独占锁);把你关在一个没人打扰的房间内,保证你打王者的原子性
读操作:就如你还是在打游戏;你的朋友在旁边看着你打;无所谓呀,反正他们又不抢我手机;想看就看呗,不看就走呗,不用排队,不用上锁
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Test01 {
/*
* 描述这样一个场景:
* 1.你和你女朋友都想玩游戏,手机只有一个,玩游戏的时候不准相互打扰
* 2.你们周围有很多大妈,只能看你们玩游戏,看了就走;不用排队
* */
public static void main(String[] args) {
Game game = new Game();
new Thread(()->{
game.play();
},"男朋友").start();
new Thread(()->{
game.play();
},"女朋友").start();
for (int i = 0; i < 6; i++) {
new Thread(()->{
game.look();
},i+"号围观大妈").start();
}
}
}
class Game{
/*lock锁*/
Lock lock = new ReentrantLock();
/*读写锁*/
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
public void play(){
/*定义写锁 上锁*/
reentrantReadWriteLock.writeLock().lock();
try{
System.out.println(Thread.currentThread().getName()+"开始玩游戏了");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"赢了");
}catch (Exception e){
e.printStackTrace();
}finally {
/*释放写锁*/
reentrantReadWriteLock.writeLock().unlock();
}
}
public void look(){
/*定义读锁 上锁*/
reentrantReadWriteLock.readLock().lock();
try{
System.out.println(Thread.currentThread().getName()+"开始看小伙打游戏");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"操作真烂,走了");
}catch (Exception e){
e.printStackTrace();
}finally {
/*释放写锁*/
reentrantReadWriteLock.readLock().unlock();
}
}
}
读写锁给我的感觉就像是:写操作加了锁对象;而读操作没有加锁对象。同一个锁有两种状态;即一锁二用
十、阻塞队列BlockingQueue
什么是队列?
队列是一种数据结构,FIFO (先进先出);列如,排队,管道,火车过隧道等
可以看出:BlockingQueue接口的父接口Queue是与Set、List接口同级的!
什么时候会造成阻塞??
把写入比作吃饭,把取出比作上厕所
1.当你吃饱了,没有一点空间了;这个时候你是不是得等待一下?先拉出来再吃嘛;这种就是阻塞等待问题
2.当你很饿,肚子内没有存货了;你怎么拉?拉空气吗?是不是先得等待吃了,再拉?这也是阻塞等待问题
BlockingQueue就是来解决这个问题的
ArrayBlockingqueue的四种API
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class Test02 {
public static void main(String[] args) throws InterruptedException {
// oneWay();
// towWay();
// threeWay();
// fore();
}
/*1.第一种API 会抛出异常 即:报异常*/
public static void oneWay(){
/*参数代表:这个队列能容纳多少个单位;即能放入多少次*/
ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(5);
/*放入数据 add() 返回的是一个boolean*/
arrayBlockingQueue.add("one");
arrayBlockingQueue.add("tow");
arrayBlockingQueue.add("three");
arrayBlockingQueue.add("fore");
arrayBlockingQueue.add("five");
/*此时,在放入一次的话,就会使这个队列阻塞等待 报 java.lang.IllegalStateException异常*/
// arrayBlockingQueue.add("six");
/*取出数据 按照先进先出的顺序取值*/
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
/*此时,无值可取,队列阻塞等待 会报 java.util.NoSuchElementException异常*/
// arrayBlockingQueue.remove();
}
/*2.第二种API 当队列阻塞时,不会报异常,有返回值 (不会耽搁程序的执行)*/
public static void towWay(){
ArrayBlockingQueue<Integer> arrayBlockingQueue = new ArrayBlockingQueue<>(4);
/*放入值 方法为 offer() 若能放入队列,则返回true ; 若不能,则返回false*/
System.out.println(arrayBlockingQueue.offer(1));
System.out.println(arrayBlockingQueue.offer(2));
System.out.println(arrayBlockingQueue.offer(3));
System.out.println(arrayBlockingQueue.offer(4));
/*此时,返回false*/
System.out.println(arrayBlockingQueue.offer(5));
/*取出值 poll() 若取出成功,则返回取出值 ; 若取出失败,则返回 null*/
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
/*此时,返回null*/
System.out.println(arrayBlockingQueue.poll());
}
/*第三种API 超时等待: 在指定的时间内阻塞等待,过了这个时间就退出了*/
public static void threeWay() throws InterruptedException {
ArrayBlockingQueue<Character> arrayBlockingQueue = new ArrayBlockingQueue<>(4);
/*放入值 方法为 offer(值,时间,时间单位) 若能放入队列,则返回true ; 若不能,则等待指定时间,若还是不能,就返回false*/
System.out.println(arrayBlockingQueue.offer('A',2,TimeUnit.SECONDS));
System.out.println(arrayBlockingQueue.offer('B',2,TimeUnit.SECONDS));
System.out.println(arrayBlockingQueue.offer('C',2,TimeUnit.SECONDS));
System.out.println(arrayBlockingQueue.offer('D',2,TimeUnit.SECONDS));
/*此时,队列满了;只能阻塞等待;等个2秒,发现还是放不进去,就返回false*/
System.out.println(arrayBlockingQueue.offer('E',2,TimeUnit.SECONDS));
/*取出值 一样的道理*/
System.out.println(arrayBlockingQueue.poll(2,TimeUnit.SECONDS));
System.out.println(arrayBlockingQueue.poll(3,TimeUnit.SECONDS));
System.out.println(arrayBlockingQueue.poll(1,TimeUnit.SECONDS));
System.out.println(arrayBlockingQueue.poll(3,TimeUnit.SECONDS));
/*此时,队列无值,等待了4秒后,发现还是没有值,就返回 null*/
System.out.println(arrayBlockingQueue.poll(4,TimeUnit.SECONDS));
}
/*4.第四种API 一直等待 就像个杠精*/
public static void fore() throws InterruptedException {
ArrayBlockingQueue<Double> arrayBlockingQueue = new ArrayBlockingQueue<Double>(3);
/*放入 这里是没有返回值的*/
arrayBlockingQueue.put(2.33);
arrayBlockingQueue.put(1.26);
arrayBlockingQueue.put(9.36);
/*此时,放不进去了;会一直阻塞,直到能放进去为zhi*/
// arrayBlockingQueue.put(2.33);
/*取出 会返回取出值*/
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
}
}
总结
方法 | 需要抛出异常 | 不需要抛出异常,但有返回值 | 超时等待,也有返回值 | 一直等待 |
---|---|---|---|---|
放入 | add() | offer() | offer(value,time,TimeUtil.SECOND) | put() |
取出 | remove() | poll() | poll(value,time,TimeUtil.SECOND) | take() |
java-PAI文档还有很多的常用方法,自己去看哟
同步队列SynchronousQueue
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class Test03 {
public static void main(String[] args) {
/*没有容量 同步队列*/
SynchronousQueue<String> synchronousQueue = new SynchronousQueue();
new Thread(()->{
try {
synchronousQueue.put("1");
synchronousQueue.put("2");
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"put线程").start();
new Thread(()->{
try {
System.out.println(synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"take线程").start();
}
}
十一、线程池的使用
三种创建线程池的方法
1.创建单一线程容量的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test04 {
public static void main(String[] args) {
/*
* Executors 线程池工具类 就相当于创建了一个池子,线程就是鱼儿
* */
/*newSingleThreadException() 表示在线程池中只能有一个线程*/
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
for (int i = 0; i < 10; i++) {
int temp = i;
/*
* 学会了线程池,就没必要用 new Thread(()->{}) 来创建线程了
* 1.线程池创建的线程,没有 start()去启动,是自动启动的
* 2.线程池运行完毕后,一定要记得关闭 线程池对象.shutdown()
* 3.借助temp中间值,来传递i值
* */
executorService.execute(()->{
System.out.println(Thread.currentThread().getName() + ": " + temp);
});
}
}catch (Exception e) {
e.printStackTrace();
}
finally {
executorService.shutdown();
}
}
}
2.创建指定线程容量的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test04 {
public static void main(String[] args) {
/*
* Executors 线程池工具类 就相当于创建了一个池子,线程就是鱼儿
* */
/*newFixedThreadPool(X) 表示在线程池中最多有X个线程*/
ExecutorService executorService = Executors.newFixedThreadPool(5);
try {
for (int i = 0; i < 10; i++) {
int temp = i;
executorService.execute(()->{
System.out.println(Thread.currentThread().getName() + ": " + temp);
});
}
}catch (Exception e) {
e.printStackTrace();
}
finally {
executorService.shutdown();
}
}
}
3.缓冲线程池 具体有多少个线程,由CPU的性能来决定
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test04 {
public static void main(String[] args) {
/*
* Executors 线程池工具类 就相当于创建了一个池子,线程就是鱼儿
* */
/*newCachedThreadPool() 我也不知道有多少个线程 一般在30几个线程左右**/
ExecutorService executorService = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 10; i++) {
int temp = i;
executorService.execute(()->{
System.out.println(Thread.currentThread().getName() + ": " + temp);
});
}
}catch (Exception e) {
e.printStackTrace();
}
finally {
executorService.shutdown();
}
}
}
深度分析创建线程池三种方法的源码
Executors.newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
Executors.newFixedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
Executors.newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
可以看出,三种方法都实现了ThreadPoolExecutor这个接口;并且传入了七个参数;作为一个优秀的程序员,创建线程池就不用Executors.三种方法;而是模仿源码写一个自定义的,高精细的,契合自身代码的线程池;看一下 ThreadPoolExecutor接口的源码
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
十二、创建一个线程池的七大参数
参数 | 描述 |
---|---|
int corePoolSize | 核心线程池的大小 |
int maximumPoolSize | 最大核心线程池大小 |
long keepAliveTime | 超时释放,时间一到,没有用我,我就释放了 |
TimeUnit unit | 超时的时间单位 |
BlockingQueue workQueue | 阻塞队列 |
ThreadFactory threadfactory | 线程工厂 一般不动 |
RejectedExecutionHandler handle | 拒绝策略 |
现实场景模拟线程池的七大参数
有这么一家银行,它有五个窗口;有能容纳8个人的等待区;只要等待区没有坐满8个人,它就不会把窗口全开,仅仅开两个窗口就行了。有一天,上午来了好多人办理业务;等待区都坐满了,没办法,只能把所有窗口打开;但是,还是有人一直进来;这个时候的银行保安就说:“不准进了,已经没有空间了”;一直忙到下午;当等待区有一丢丢空位子时,那三个窗口就关了;剩下两个窗口在继续忙;一直到没有人为止
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Test05 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
/*当队列正常运行时,开启的线程数 平时办理业务的 两个窗口*/
2,
/*当阻塞队列的容量满了后,即:阻塞队列开始阻塞 开启的线程数 忙时,所有窗口都要用*/
5,
/*当队列不阻塞时,开始计数 超过三秒 就把那些不常用的线程关了*/
3,
/*超时单位*/
TimeUnit.SECONDS,
/*这个就是队列 指定了容量为4 即: 等待去只有四个位置*/
new ArrayBlockingQueue<>(4),
/*线程的工厂类*/
Executors.defaultThreadFactory(),
/*下列是四种拒绝策略 就是银行保安说的话*/
/*1.银行已经满了,如果还有人来;我就抛出异常 报异常*/
// new ThreadPoolExecutor.AbortPolicy()
/*2.银行已经满了,如果还有人来;我就让他从哪里来回那里去;*/
// new ThreadPoolExecutor.CallerRunsPolicy()
/*3.银行已经满了,如果还有人来;我就去看看最早办理业务的那个人,看他办完没;若办完了,我就让他进去*/
new ThreadPoolExecutor.DiscardOldestPolicy()
/*4.不抛出异常,就当这个人没来过*/
// new ThreadPoolExecutor.DiscardPolicy()
);
/*20个顾客*/
for (int i = 0; i < 10; i++) {
threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+"===>办理业务");
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
**注意:**关闭线程池!!!
最大线程数的设置 CPU密集型 IO密集型
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Test05 {
public static void main(String[] args) {
/*获取CPU的核数*/
System.out.println(Runtime.getRuntime().availableProcessors());
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
/*当队列正常运行时,开启的线程数 平时办理业务的 两个窗口*/
2,
/*当阻塞队列的容量满了后,即:阻塞队列开始阻塞 开启的线程数 忙时,所有窗口都要用
* CPU 密集型 看自己的电脑,有几核处理器,就写几
* IO 密集型 先判断你的程序中有多少个消耗IO流的线程;然后设置为2倍大小
* */
Runtime.getRuntime().availableProcessors(),
/*当队列不阻塞时,开始计数 超过三秒 就把那些不常用的线程关了*/
3,
/*超时单位*/
TimeUnit.SECONDS,
/*这个就是队列 指定了容量为4 即: 等待去只有四个位置*/
new ArrayBlockingQueue<>(4),
/*线程的工厂类*/
Executors.defaultThreadFactory(),
/*下列是四种拒绝策略 就是银行保安说的话*/
/*1.银行已经满了,如果还有人来;我就抛出异常 报异常*/
// new ThreadPoolExecutor.AbortPolicy()
/*2.银行已经满了,如果还有人来;我就让他从哪里来回那里去;*/
// new ThreadPoolExecutor.CallerRunsPolicy()
/*3.银行已经满了,如果还有人来;我就去看看最早办理业务的那个人,看他办完没;若办完了,我就让他进去*/
new ThreadPoolExecutor.DiscardOldestPolicy()
/*4.不抛出异常,就当这个人没来过*/
// new ThreadPoolExecutor.DiscardPolicy()
);
try{
/*20个顾客*/
for (int i = 0; i < 10; i++) {
threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+"===>办理业务");
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}catch (Exception e){
e.printStackTrace();
}finally{
threadPoolExecutor.shutdown();
}
}
}
十三、四大函数式接口
一个接口内,只有一个方法
1. Function 接口
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
import java.util.function.Function;
public class Test06 {
public static void main(String[] args) {
/*传入参数类型和返回值类型可以自己定义,Function<String,object> */
Function<String, Object> Function01 = new Function<String, Object>() {
@Override
public Object apply(String s) {
return s+"你好";
}
};
/*lambda 表达式*/
Function<String, Object> Function02 =(str)->{ return str+"早上好"; };
System.out.println(Function01.apply("陌生人"));
System.out.println(Function02.apply("你这个B"));
}
}
2.断定型接口Predicate
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
import java.util.function.Predicate;
public class Test07 {
public static void main(String[] args) {
/*Predicate<输入值的类型即泛型> 输入一个指定类型的值,返回一个Boolean值 */
Predicate<Integer> integerPredicate01 = new Predicate<Integer>() {
@Override
public boolean test(Integer message) {
return message.equals(0);
}
};
/*lambda*/
Predicate<Integer> integerPredicate02 =(mes)->{return mes.equals(0);};
System.out.println(integerPredicate01.test(12));
System.out.println(integerPredicate02.test(0));
}
}
3.消费型接口Consumer
只有传入参数,没有返回值
public interface Consumer<T> {
void accept(T t);
}
4.供给型接口Supplier
没有传入值,只有返回值!返回值可以自己定义类型
@FunctionalInterface
public interface Supplier<T> {
T get();
}
记住:所有的函数式接口都可以用Lambda来简写
十四、Stream并行流
import java.util.stream.LongStream;
public class Time {
public static void main(String[] args) {
Time time = new Time();
System.out.println(time.forTest());
System.out.println("--------------");
System.out.println(time.streamTest());
}
public long forTest(){
long start = System.currentTimeMillis();
long sum = 0;
for (int i = 0; i <10_0000_0000_0l; i++) {
sum+=i;
}
long end = System.currentTimeMillis();
System.out.println("for=>>"+sum);
return end-start;
}
public long streamTest(){
long start = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0l, 10_0000_0000_0l).parallel().reduce(0,Long::sum);
long end = System.currentTimeMillis();
System.out.println("Steam=>>"+sum);
return end-start;
}
}
当有多个任务时,使用Stream流可以提高效率