一、进程

一个进程可以有多个线程,如视频中同时听声音,看图像,看弹幕,等等
1.说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
2.而进程是执行程序的一次执行过程,它是动态的概念。是系统资源分配的单位
3.通常在一个进程中可以包含若干个线程,当然一个进程至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
注意:很多 多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一时间点,cpu 只能执行一个代码,因为切换的很快,所以有同时执行的错觉。
总结:
1.线程就是独立的执行路径;
2.在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
3.main()称之为主线程,为系统的入口,用于执行整个程序;
4.在一个进程中,如果开辟了多个线程,线程的运行由调度器(CPU)安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。
5.对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。
6.线程会带来额外的开销,如cpu调度时间,并发控制开销。
7.每个线程在自己的工作内存交互,内存控制不当会造成数据不一致;
二、多线程
原来的方法调用
多线程下的调用
2.1线程创建的三种方式

线程实现三种方式
1.继承Thread类
2.实现Runnable 接口
3.实现Callable 接口
继承Thread类实现多线程步骤如下:
1.自定义线程类继承Thread类
2.重写run() 方法,编写线程执行体
3.创建线程对象,调用start() 方法启动线程
创建线程方式1:继承Thread(通过查看源码发现Thread 是实现Runnable接口的)
注意:线程开启不一定立即执行,由CPU调度安排。
第一种 继承Thread类
public class TestThread1 extends Thread {@Overridepublic void run() {//run方法 线程体for (int i = 0; i < 10; i++) {System.out.println("我在撸代码--"+i);}}public static void main(String[] args) {//创建线程对象TestThread1 testThread1 = new TestThread1();//调用start()方法 ,开启线程testThread1.start();//main线程,主线程for (int i = 0; i < 10; i++) {System.out.println("我在学习--"+i);}}}//执行结果我在学习--0我在学习--1我在学习--2我在学习--3我在学习--4我在学习--5我在学习--6我在学习--7我在撸代码--0我在撸代码--1我在学习--8我在撸代码--2我在撸代码--3我在撸代码--4我在学习--9我在撸代码--5我在撸代码--6我在撸代码--7我在撸代码--8我在撸代码--9
第二种、实现Runnable接口
package cn.bloghut.thread;/*** @author by 闲言* @classname TestThread1* @description 实现多线程第二种方式* @date 2021/7/27 18:13*/public class TestThread3 implements Runnable{@Overridepublic void run() {//run方法 线程体for (int i = 0; i < 10; i++) {System.out.println("我在撸代码--"+i);}}public static void main(String[] args) {//创建线程对象TestThread3 testThread3 = new TestThread3();//创建线程对象,通过线程对象来开启我们的线程Thread thread = new Thread(testThread3);thread.start();//main线程,主线程for (int i = 0; i < 10; i++) {System.out.println("我在学习--"+i);}}}//输出我在学习--0我在学习--1我在学习--2我在学习--3我在学习--4我在撸代码--0我在学习--5我在撸代码--1我在学习--6我在撸代码--2我在撸代码--3我在学习--7我在学习--8我在学习--9我在撸代码--4我在撸代码--5我在撸代码--6我在撸代码--7我在撸代码--8我在撸代码--9
小结:
继承Thread类
1.子类继承Thread 类具有多线程能力
2.启动线程:子类对象.start()
3.不建议使用:避免OOP单继承局限性
实现Runnable 接口
1.实现接口Runnable 具有多线程能力
2.启动线程:传入目标对象+Thread对象.start()
3.推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
案例:龟兔赛跑
public class Race implements Runnable{//胜利者private static String name;@Overridepublic void run() {//设置赛道长度for (int i = 0; i <= 100; i++) {//模拟兔子睡觉if (Thread.currentThread().getName().equals("兔子") && i%10==0){try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}//判断游戏是否结束boolean b = gameOver(i);if (b){break;}System.out.println(Thread.currentThread().getName()+"跑到了"+i);}}//根据长度判断游戏是否结束public boolean gameOver(int num){//游戏胜利者已经存在if (name !=null){return true;}else {if (num>=100){name=Thread.currentThread().getName();System.out.println(name+"胜利了!");return true;}}return false;}public static void main(String[] args) {Race race=new Race();new Thread(race,"兔子").start();new Thread(race,"乌龟").start();}}
第三种、实现Callable(了解)
1.实现Callable接口,需要返回值类型
2.重写call 方法,需要抛出异常
3.创建目标对象
4.创建执行服务:
5.提交执行:
6.获取结果:
7.关闭服务:
package cn.bloghut.callable;import cn.bloghut.thread.TestThread2;import org.apache.commons.io.FileUtils;import java.io.File;import java.io.IOException;import java.net.URL;import java.util.concurrent.*;/*** @author by 闲言* @classname TestCallable* @description 线程创建方式三:实现Callable即可* @date 2021/7/29 12:03*/public class TestCallable implements Callable<Boolean> {private String url;//网络图片地址private String name;//保存的文件名public TestCallable(String url, String name) {this.name = name;this.url = url;}//下载图片线程的执行体@Overridepublic Boolean call() throws Exception {WebDownloader webDownloader = new WebDownloader();webDownloader.downLoader(url,name);System.out.println("下载了文件名为:"+name);return true;}public static void main(String[] args) throws ExecutionException, InterruptedException {TestCallable t1 = new TestCallable("https://img-blog.csdnimg.cn/img_convert/d8885c9a178b2fcaea732190717b516d.png", "1.jpg");TestCallable t2 = new TestCallable("https://img-blog.csdnimg.cn/img_convert/d8885c9a178b2fcaea732190717b516d.png", "2.jpg");TestCallable t3 = new TestCallable("https://img-blog.csdnimg.cn/img_convert/d8885c9a178b2fcaea732190717b516d.png", "3.jpg");//1.创建执行服务ExecutorService ser = Executors.newFixedThreadPool(3);//2.提交执行Future<Boolean> r1 = ser.submit(t1);Future<Boolean> r2 = ser.submit(t2);Future<Boolean> r3 = ser.submit(t3);//获取结果Boolean rs1 = r1.get();Boolean rs2 = r2.get();Boolean rs3 = r3.get();//关闭服务ser.shutdownNow();}}class WebDownloader {public void downLoader(String url, String name) {try {FileUtils.copyURLToFile(new URL(url), new File(name));} catch (IOException e) {e.printStackTrace();System.out.println("IO 异常,Downloader方法出现问题");}}}
三、静态代理模式
静态模式总结:
1、真实对象和代理对象必须要实现同一个接口
2、代理对象要代理真实角色
好处:可以对真实对象的方法执行前进行增强
//静态代理//真实对象和代理对象需要实现同一个接口//代理对象要代理真实角色public class StaticProxy {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {System.out.println("");}});Cat cat=new CatPlus(new You());cat.catHamburger();}}interface Cat{void catHamburger();}class You implements Cat{//实现接口方法@Overridepublic void catHamburger() {System.out.println("我要吃汉堡王");}}class CatPlus implements Cat{protected Cat cat;public CatPlus(Cat cat) {this.cat = cat;}@Overridepublic void catHamburger() {before();this.cat.catHamburger();//调用真实目标的方法after();}private void after() {System.out.println("我吃完了非常饱");}private void before() {System.out.println("我饿了!");}}
四、Lambda表达式
入 希腊字母表中排序第十一位的字母,英语名称为Lambda
避免内部类定义过多
其实质属于函数式编程概念
(params) -> expression [表达式](params) -> statement [语句](params) -> {statement }new Thread(()-> System.out.println("多线程学习")).start();
为什么要使用lambda 表达式
1.避免你们内部类定义过多
2.可以让你的代码看起来很简洁
3.去掉了一堆没有意义的代码,只留下核心的逻辑
函数式接口的定义
1.任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
2.对于函数式接口,我们可以通过lambda 表达式来创建该接口的对象。
案例
public class TestLamda {//1.静态内部类static class LikeStatic implements Like{@Overridepublic void like() {System.out.println("静态内部类!");}}public static void main(String[] args) {Like like=new LikeImpl();like.like();//1.静态内部类LikeStatic likeStatic=new LikeStatic();likeStatic.like();//2.局部内部类class Like2 implements Like{@Overridepublic void like() {System.out.println("局部内部类");}}Like2 like2=new Like2();like2.like();//3.匿名内部类Like like3 = new Like() {@Overridepublic void like() {System.out.println("匿名内部类");}};like3.like();//lambda表达式Like like4 = () -> {System.out.println("lambda表达式");};like4.like();}}//定义一个函数接口interface Like{void like();}//实现类class LikeImpl implements Like {@Overridepublic void like() {System.out.println("实现类");}}
五、线程状态


线程方法
线程停止
1.建议线程正常停止——》利用次数。不建议死循环
2.建议使用标志位——》设置一个标志位
3.不用使用stop或destory 等过时或者JDK 不建议使用的方法
package cn.bloghut.state;/*** @author by 闲言* @classname TestStop* @description 测试stop* @date 2021/7/29 17:51*/public class TestStop implements Runnable {//1.设置一个标志位private boolean flag = true;@Overridepublic void run() {int i = 0;while (flag) {System.out.println("run...Thread->" + i++);}}//2.设置一个公开的方法停止线程,转换标志位public void stop() {this.flag = false;}public static void main(String[] args) {TestStop testStop = new TestStop();//开启线程new Thread(testStop).start();for (int i = 0; i < 1000; i++) {System.out.println("main" + i);if (i == 900) {//调用stop方法切换标志位,让线程停止testStop.stop();System.out.println("该线程停止了");}}}}
线程休眠(sleep)
1.sleep(时间)指定当前线程阻塞的毫秒数;
2.sleep 存在异常InterruptedException;
3.sleep 时间达到后线程进入就绪状态
4.sleep 可以模拟网络延时,倒计时等。
5.每一个对象都有一个锁,sleep不会释放锁;
package cn.bloghut.state;/*** @author by 闲言* @classname TestSleep* @description 模拟网络延时:放大问题的发生性* @date 2021/7/29 18:09*/public class TestSleep implements Runnable {//票数private int ticketNums = 10;@Overridepublic void run() {while (true) {if (ticketNums <= 0) {break;}//模拟延时try {Thread.sleep(100);} catch (Exception e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "--->拿到了第" + ticketNums-- + "票");}}public static void main(String[] args) {TestSleep testSleep = new TestSleep();new Thread(testSleep,"小明").start();new Thread(testSleep,"小红").start();new Thread(testSleep,"小黄牛").start();}}
线程礼让(yield)
1.礼让线程,让当前正在执行的线程暂停,但不阻塞
2.将线程从运行状态转为就绪状态
3.让cpu 重新调度,礼让不一定成功!看cpu心情
/*** @author chenGen* @version 1.0.0* @ClassName TestThread6.java* @Description TODO* @createTime 2021年09月15日 09:46:00*///线程礼让public class TestThread6 {public static void main(String[] args) {Thread thread = new Thread(new MyThread());Thread thread1 = new Thread(new MyThread());thread.start();thread1.start();}}class MyThread implements Runnable{@Overridepublic void run() {System.out.println("线程开始了"+Thread.currentThread().getName());Thread.yield();System.out.println("线程开始了!"+Thread.currentThread().getName());}}
线程强制执行:(join)
join合并线程 ,待此线程执行完成之后,在执行其他线程,其他线程阻塞 类似于排队
/*** @author chenGen* @version 1.0.0* @ClassName TestThread7.java* @Description TODO* @createTime 2021年09月15日 10:38:00*///线程礼让public class TestThread7 implements Runnable {public static void main(String[] args) throws InterruptedException {TestThread7 thread=new TestThread7();Thread thread1=new Thread(thread);for (int i = 0; i < 1000; i++) {if (i==200){thread1.start();thread1.join();}System.out.println("main"+i);}}@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("我是vip线程!");}}}
Thread.State
线程状态,线程可以处于以下状态之一:
NEW
尚未启动的线程处于此状态
RUNNABLE
在Java虚拟机中执行的线程处于此状态
BLOCKED
被阻塞等待监视器锁定的线程处于此状态
WAITING
正在等待另一个线程执行特定动作的线程处于此状态
TIMED WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
TERMINATED
已退出的线程处于此状态
一个线程可以在给定时间点处于一个状态,这些状态不反映任何操作系统线程状态的虚拟机状态。
/*** @author chenGen* @version 1.0.0* @ClassName TharedState.java* @Description TODO* @createTime 2021年09月15日 13:44:00*///线程状态public class TharedState {public static void main(String[] args) throws InterruptedException {Thread thread= new Thread(()->{for (int i = 0; i < 5; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("///////");}});//准备状态State state = thread.getState();System.out.println(state);//启动状态thread.start();state=thread.getState();System.out.println(state);//只要线程不终止,就一直输出状态while (state!= State.TERMINATED){Thread.sleep(100);state=thread.getState();//更新线程状态System.out.println(state);}}}
线程优先级
1.Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器安装优先级决定应该调度哪个线程来执行。
2.线程的优先级用数字表示,范围从1——10
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NOPM_PRIORITY = 5;
使用以下方式改变或获取优先级
getPriority()
setPriority(int xx)
/*** @author chenGen* @version 1.0.0* @ClassName TestThreadGrade.java* @Description TODO* @createTime 2021年09月15日 16:28:00*///线程优先级public class TestThreadGrade {public static void main(String[] args) {//主线程System.out.println("Main"+Thread.currentThread().getPriority());MyTherad myThread=new MyTherad();Thread thread=new Thread(myThread);Thread thread1=new Thread(myThread);Thread thread2=new Thread(myThread);Thread thread3=new Thread(myThread);thread.setPriority(4);thread1.setPriority(7);thread2.setPriority(10);thread.start();thread1.start();thread2.start();}}class MyTherad implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());}}
守护(daemon)线程
1.线程分为用户线程和守护线程
2.虚拟机必须确保用户线程执行完毕
3.虚拟机不用等待守护线程执行完毕
如:后台记录操作日志、监控内存、垃圾回收等等…
/*** @author chenGen* @version 1.0.0* @ClassName TestThread8.java* @Description TODO* @createTime 2021年09月15日 17:26:00*///守护线程public class TestThread8 {public static void main(String[] args) {YOU you=new YOU();God god=new God();Thread thread=new Thread(god);thread.setDaemon(true);thread.start();new Thread(you).start();}}class God implements Runnable{@Overridepublic void run() {while (true){System.out.println("上帝与你同在!");}}}class YOU implements Runnable{@Overridepublic void run() {for (int i = 0; i < 365000; i++) {System.out.println("我在活着!");}System.out.println("我离开了!");}}
线程同步
多个线程操作同一个资源
并发:同一个对象被多个线程同时操作
处理多线程问题时,多个线程访问同一个对象(并发),并且某些线程还想修改这个对象,这个时候我们就需要线程同步,线程同步就是一种机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
队列和锁
由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入 锁机制 synchronized ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。
存在以下问题:
1.一个线程有锁会导致其他需要此锁的线程挂起;
2.在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题;
3.如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题。
案例1
/*** @author chenGen* @version 1.0.0* @ClassName TestThread9.java* @Description TODO* @createTime 2021年09月16日 14:08:00*///不安全的买票public class TestThread9 {public static void main(String[] args) {Train train=new Train();new Thread(train,"学生").start();new Thread(train,"老师").start();new Thread(train,"工作人员").start();}}class Train implements Runnable{private int TrainNum=10;private boolean flag=true;@Overridepublic void run() {while (flag){try {buy();} catch (InterruptedException e) {e.printStackTrace();}}}//买票protected void buy() throws InterruptedException {if (TrainNum<=0){flag=false;return;}//模拟延时Thread.sleep(100);System.out.println(Thread.currentThread().getName()+"拿到第"+TrainNum--);}}
案例2
/*** @author chenGen* @version 1.0.0* @ClassName TestTheard10.java* @Description TODO* @createTime 2021年09月16日 15:49:00*///不安全的取钱public class TestTheard10 {public static void main(String[] args) {//初始模拟账户Account account=new Account("存款",1000);//取钱用户Bank My=new Bank(account,500,"小明");Bank you=new Bank(account,600,"小红");//开启线程My.start();you.start();}}class Account {String name;//余额int balance;public Account(String name, int balance) {this.name = name;this.balance = balance;}}//银行模拟取款class Bank extends Thread{//银行账户Account account;//取钱数量int drawMoney;//手上的钱int wallet;@Overridepublic void run() {//如果取的钱数大于余额if (account.balance-drawMoney<0){System.out.println("余额不足,不能取!");return;}//放大问题的发生try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//余额account.balance=account.balance-drawMoney;//手里的钱wallet=wallet+drawMoney;System.out.println(account.name+"卡内余额为:"+account.balance);System.out.println(this.getName()+"手上的钱有:"+wallet);}public Bank(Account account, int drawMoney, String name) {super(name);this.account = account;this.drawMoney = drawMoney;}}
案例3
package cn.bloghut.syn;import java.util.ArrayList;import java.util.List;/*** @author by 闲言* @classname UnsafeList* @description 线程不安全的集合* @date 2021/7/30 18:09*/public class UnsafeList {public static void main(String[] args) throws InterruptedException {List<String> list = new ArrayList<>();for (int i = 0; i < 10000; i++) {new Thread(()->{list.add(Thread.currentThread().getName());}).start();}Thread.sleep(3000);System.out.println(list.size());}}
同步方法
由于我们可以通过private 关键字来保证数据对象只能被方法访问,所以我们只需要对方法提出一套机制,这套机制就是synchronized 关键字,它包括两种用法:synchronized 方法和synchronized 块;
同步方法:public synchronized void method(int args){}
synchronized 方法 控制对 “ 对象”的访问,每个对象对应一把锁,每个synchronized 方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
缺陷:若将一个大的方法声明为synchronized 将会影响效率。
方法里面需要修改的内容才需要锁,锁的太多,浪费资源。
同步块:synchronized (obj){ }
obj 称之为 同步监视器
obj 可以是任何对象,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是和这个对象本身,或者是class
同步监视器的执行过程
1.第一个线程访问,锁定同步监视器,执行其中代码
2.第二个线程访问,发现同步监视器,无法访问
3.第一个线程访问完毕,解锁同步监视器
4.第二个线程访问,发现同步监视器没有锁,然后锁定并访问
锁的对象就是变化的量,需要增删改的数据
死锁
多个线程互相抱着对象需要的资源,然后形成僵持。
多个线程各自占有一些资源,并且互相等待其他线程占有的资源才能运行,而导致这两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有“两个以上对象的锁时”,就可能发生“死锁”的问题。
死锁案例
/*** @author chenGen* @version 1.0.0* @ClassName TestTherad11.java* @Description TODO* @createTime 2021年09月16日 17:48:00*/public class TestTherad11 {public static void main(String[] args) {MakeUp makeUp=new MakeUp(0,"女主角");MakeUp makeUp2=new MakeUp(1,"女二号");makeUp.start();makeUp2.start();}}//口红class LlipStick{}//镜子class Mirror{}class MakeUp extends Thread {static LlipStick llipStick=new LlipStick();static Mirror mirror=new Mirror();//选择int choice;//姓名String name;public MakeUp(int choice, String name) {this.choice = choice;this.name = name;}@Overridepublic void run() {try {makeup();} catch (InterruptedException e) {e.printStackTrace();}}public void makeup() throws InterruptedException {//为0if (choice==0){synchronized (llipStick){System.out.println("我要用镜子"+this.name);Thread.sleep(1000);synchronized (mirror){System.out.println("我需要用口红"+this.name);}}}else {synchronized (mirror){System.out.println("我需要用口红"+this.name);Thread.sleep(1000);synchronized (llipStick){System.out.println("我要用镜子"+this.name);}}}}}
Lock锁
可重入锁
1.从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
2.java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
3.ReentrantLock 类实现了Lock,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
Lck案例
/**
* @author chenGen
* @version 1.0.0
* @ClassName TestLock.java
* @Description TODO
* @createTime 2021年09月17日 15:34:00
*/
public class TestLock {
public static void main(String[] args) {
Lock lock = new Lock();
new Thread(lock).start();
new Thread(lock).start();
new Thread(lock).start();
}
}
class Lock implements Runnable{
//重入锁
private final ReentrantLock reentrantLock=new ReentrantLock();
int num=10;
@Override
public void run() {
while (true){
try {
//开启锁
reentrantLock.lock();
if (num>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num--);
}else{
break;
}
}finally {
//释放锁
reentrantLock.unlock();
}
}
}
}
Synchronized 与 Lock 的对比
1.Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁,出了作用域自动释放
2.Lock只有代码块锁,synchronized有代码块锁和方法锁
3.使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
4.优先使用顺序:
Lock > 同步代码块 (已经进入了方法体,分配相应资源) > 同步方法(在方法体之外)
线程通信问题
生产者和消费者问题
1.假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库﹐消费者将仓库中产品取走消费。
2.如果仓库中没有产品﹐则生产者将产品放入仓库﹐否则停止生产并等待,直到仓库中的产品被消费者取走为止。
3.如果仓库中放有产品,则消费者可以将产品取走消费﹐否则停止消费并等待,直到仓库再次放入产品为止。
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
1.对于生产者﹐没有生产产品之前,要通知消费者等待﹒而生产了产品之后﹐又需要马上通知消费者消费
2.对于消费者﹐在消费之后﹐要通知生产者已经结束消费﹐需要生产新的产品以供消费.
3.在生产者消费者问题中,仅有synchronized是不够的
synchronized可阻止并发更新同一个共享资源,实现了同步
synchronized不能用来实现不同线程之间的消息传递(通信)
Java提供了几个方法解决线程之间的通信问题
注意:均是Object类 的方法,都只能在同步方法或者同步代码块中使用,否则会抛出lllegalMonitorStateException
解决方式1:
并发协作模型“生产者/消费者模式”—>管程法
1.生产者:负责生产数据的模块(可能是方法﹐对象﹐线程,进程);
2. 消费者∶负责处理数据的模块(可能是方法,对象﹐线程﹐进程);
3.缓冲区:消费者不能直接使用生产者的数据﹐他们之间有个“缓冲区
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
/**
* @author chenGen
* @version 1.0.0
* @ClassName Tesy.java
* @Description TODO
* @createTime 2021年09月17日 16:29:00
*/
//生产者 消费者 缓冲区 产品
public class TestProduct {
public static void main(String[] args) {
ProductBuffer productBuffer=new ProductBuffer();
Product product=new Product(productBuffer);
Consumer consumer=new Consumer(productBuffer);
product.start();
consumer.start();
}
}
//生产者
class Product extends Thread{
ProductBuffer productBuffer;
public Product(ProductBuffer productBuffer) {
this.productBuffer = productBuffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
productBuffer.push(new Chick(i));
System.out.println("生产了第"+i+"只鸡");
}
}
}
//消费者
class Consumer extends Thread{
ProductBuffer productBuffer;
public Consumer(ProductBuffer productBuffer) {
this.productBuffer = productBuffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//消费
Chick pop = productBuffer.pop();
System.out.println("消费了第"+pop.getId()+"只鸡");
}
}
}
//产品
class Chick {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Chick(int id) {
this.id = id;
}
}
//产品缓冲区
class ProductBuffer{
//产品池
private Chick chicks[] =new Chick[10];
//当前产品数量
int count=0;
//生产产品
public synchronized void push(Chick chick){
if (count == chicks.length){
//如果产品满了 通知消费者消费,生产等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//放产品
chicks[count]=chick;
count++;
//通知消费者消费
this.notifyAll();
}
//消费产品
public synchronized Chick pop(){
//如果产品为0 通知消费者消费
if (count==0){
//等待生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Chick chick= chicks[count];
//消费完了,唤醒生产者生产
this.notifyAll();
return chick;
}
}
第二种方式:信号灯法
采用标示来控制
/**
* @author chenGen
* @version 1.0.0
* @ClassName TestTherad12.java
* @Description TODO
* @createTime 2021年09月18日 09:57:00
*/
//消费者 生产者 信号灯法
public class TestTherad12 {
public static void main(String[] args) {
TV tv=new TV();
new Player(tv).start();
new Watch(tv).start();
}
}
//生产者--》演员
class Player extends Thread {
TV tv;
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%2==0){
this.tv.player("天天向上");
}else {
this.tv.player("新闻联播");
}
}
}
public Player(TV tv) {
this.tv = tv;
}
}
//消费者--》观众
class Watch extends Thread{
TV tv;
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.tv.watch();
}
}
public Watch(TV tv) {
this.tv = tv;
}
}
//产品-->节目
class TV{
//节目名称
String name;
//标识
boolean flag=true;
//演员录制节目
public synchronized void player(String name){
//判断 如果节目还没有消费就等待
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员录制了"+name);
//唤醒观众观看节目
this.notifyAll();
this.name=name;
this.flag=!this.flag;
}
//消费者 --》观看
public synchronized void watch(){
//判断 如果没有节目就等待
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费节目
System.out.println("我观看了"+name);
//唤醒演员观看节目
this.notifyAll();
this.flag=!this.flag;
}
}
线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路︰提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理(…)
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
JDK 5.0起提供了线程池相关API:
ExecutorService和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable cgmmand):执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown():关闭连接池
Executors:
工具类、线程池的工厂类,用于创建并返回不同类型的线程池
package cn.bloghut.gaoji;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author by 闲言
* @classname TestPool
* @description 测试线程池
* @date 2021/7/31 12:31
*/
public class TestPool {
public static void main(String[] args) {
//1.创建服务,创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//2.关闭连接
service.shutdown();
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
线程池的使用
线程池的真正实现类是 ThreadPoolExecutor,其构造方法有如下4种:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
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;
}
参数:
- corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
线程池的使用如下:
//创建了核心线程为6 最大线程数为10 超时时间5秒
ThreadPoolExecutor poolExecutor=new ThreadPoolExecutor(6,10, 5, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(10));
Runnable myRunnable= new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"run");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
executor.execute(myRunnable);
// 关闭线程池
executor.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
executor.shutdownNow(); // 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
线程池的工作原理

线程池的参数
任务队列(workQueue)
任务队列是基于阻塞队列实现的,即采用生产者消费者模式,在 Java 中需要实现 BlockingQueue 接口。但 Java 已经为我们提供了 7 种阻塞队列的实现:
常用的队列:SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE。
PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用 put() 方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
LinkedTransferQueue: 它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。
注意有界队列和无界队列的区别:如果使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略;而如果使用无界队列,因为任务队列永远都可以添加任务,所以设置 maximumPoolSize 没有任何意义。
线程工厂(threadFactory)
线程工厂指定创建线程的方式,需要实现 ThreadFactory 接口,并实现 newThread(Runnable r) 方法。该参数可以不用指定,Executors 框架已经为我们实现了一个默认的线程工厂:
/**
* The default thread factory.
*/
private static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
拒绝策略(handler)
当线程池的线程数达到最大线程数时,需要执行拒绝策略。拒绝策略需要实现 RejectedExecutionHandler 接口,并实现 rejectedExecution(Runnable r, ThreadPoolExecutor executor) 方法。不过 Executors 框架已经为我们实现了 4 种拒绝策略:
AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
CallerRunsPolicy:由调用线程处理该任务。
DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。
功能线程池
嫌上面使用线程池的方法太麻烦?其实Executors已经为我们封装好了 4 种常见的功能线程池,如下:
定长线程池(FixedThreadPool)
定时线程池(ScheduledThreadPool )
可缓存线程池(CachedThreadPool)
单线程化线程池(SingleThreadExecutor)
定长线线程池
创建源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
- 特点:只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。
- 应用场景:控制线程最大并发数。
使用示例:
// 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run() {
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务
fixedThreadPool.execute(task);
定时线程池
创建方式
private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue(), threadFactory);
}
- 特点:核心线程数量固定,非核心线程数量无限,执行完闲置 10ms 后回收,任务队列为延时阻塞队列。
- 应用场景:执行定时或周期性的任务。
使用示例:
// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run() {
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务
scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务
可缓存线程池(CachedThreadPool)
创建方法源码:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
- 特点:无核心线程,非核心线程数量无限,执行完闲置 60s 后回收,任务队列为不存储元素的阻塞队列。
- 应用场景:执行大量、耗时少的任务。
示例:
// 1. 创建可缓存线程池对象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run() {
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务
cachedThreadPool.execute(task);
单线程化线程池(SingleThreadExecutor)
创建方法源码:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
- 特点:只有 1 个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列。
- 应用场景:不适合并发但可能引起 IO 阻塞性及影响 UI 线程响应的操作,如数据库操作、文件操作等。
示例
// 1. 创建单线程化线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run() {
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务
singleThreadExecutor.execute(task);
对比
总结:
Executors 的 4 个功能线程池虽然方便,但现在已经不建议使用了,而是建议直接通过使用 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
其实 Executors 的 4 个功能线程有如下弊端:
FixedThreadPool 和 SingleThreadExecutor:主要问题是堆积的请求处理队列均采用 LinkedBlockingQueue,可能会耗费非常大的内存,甚至 OOM。
CachedThreadPool 和 ScheduledThreadPool:主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。
