- 线程的简介
进程(process)
线程(thread)
调度模式
内存开销
普通方法调用和多线程调用 - 线程实现的三种方式
继承Thread类
实现Runnable接口
实现Calleble 接口
初识并发问题 - 线程状态,线程同步
线程的五大状态
线程方法
线程停止
线程休眠
线程礼让
线程强制执行
观测线程状态
线程优先级
守护线程 - 线程通信问题
-生产者与消费者模式
管程法
红绿灯法 - 高级主题
线程池
静态代理模式
Lamda表达式
线程的简介
例如边吃饭边玩手机
生活中有太多这样同时做多件事情的例子,看起来是多个任务都在做,其本质上我们的大脑在同一时间依旧只做了一件事情
多个线程组成一个进程,QQ,应用,播放器,等
1、进程(process)
一个启动的应用程序就是进程,执行程序的过程,启动后就分配了内存 和 CPU
2、线程(thread)
进程中的一段顺序执行流,它是cpu执行和调度的最基本的单元
只是程序计数器用一点点内存,其他资源共享进程资源
3、调度模式
分时调度
抢占式调度
4、内存开销
进程:创建和销毁内存消耗比较大
线程:创建和销毁内存消耗比较小
同一个进程当中的所有线程共享次进程的所有资源
5、普通方法调用和多线程调用
主线程下来,调用run方法,方法执行完,再往下走,
主线程走主线程,子线程走子线程
总结第一章核心概念:
- 线程就是独立的执行路径;
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
- main()称之为主线程,为系统入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排,调度器与操作系统紧密相连,不可控;
- 对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制;
- 线程对带来额外的开销,如cpu调度时间,并发控制开销;
- 每个线程在自己的工作内存交互,内存控制不会造成数据不一致;
线程实现的三种方式
创建maven项目方便点
1、继承Thread类
覆盖run方法。run方法里面就是线程要执行的顺序流。
书写顺序:
自定义线程类继承Thread类
重写run()方法,编写线程的执行体
创建线程对象,调用start()方法启动线程
//创建线程的方式一:继承Thread类,重写run*()方法,调用start开启线程
public class Threadtest1 extends Thread{
@Override
//run()方法线程体
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码-----"+i);
}
}
//main线程,主线程
public static void main(String[] args) {
//创建一个线程对象
Threadtest1 threadtest1 = new Threadtest1();
//调用start方法开启线程
threadtest1.start();
for (int j = 0; j < 20; j++) {
System.out.println("我在学习多线程--"+j);
}
}
}
总结:线程不一定立即执行,CPU安排调度。
异步执行,没有顺序。
写一个案例练习1:
maven下载依赖
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
代码:
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//练习Rread,实现多线程同步下载图片
public class Treadtest2 extends Thread{
private String url;//文件地址
private String name;//保存的文件名
public Treadtest2(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载的文件名字是"+name);
}
public static void main(String[] args) {
Treadtest2 treadtest1 = new Treadtest2("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=4179487881,3847867256&fm=11&gp=0.jpg","1.jpg");
Treadtest2 treadtest2 = new Treadtest2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1593598260498&di=1751f388a8f222d966efe98c0b0e5d88&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201308%2F05%2F20130805105309_5E2zE.jpeg","2.jpeg");
Treadtest2 treadtest3 = new Treadtest2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1593598260493&di=d45ac86c1fafba25175847b96e0184d6&imgtype=0&src=http%3A%2F%2F00.minipic.eastday.com%2F20170331%2F20170331113141_98c8105e504ff71e68d59a6eaa30bd7e_5.jpeg","3.jpeg");
treadtest1.start();
treadtest2.start();
treadtest3.start();
}
}
//下载类
class WebDownloader{
//下载方法
public void downloader(String url, String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));//url变成文件
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常");
}
}
}
结果:
2、实现Runnable接口,
覆盖run方法。run方法里面就是线程要执行的顺序流。
将实现实现Runnable接口的类对象放入线程对象中
注意:Tread类也实现了Runable接口
//创建线程方式2:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法
public class Testrunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("当前执行的线程是---"+Thread.currentThread().getName()+"--------"+i);
}
}
public static void main(String[] args) {
//创建runnable接口实现类对象
Testrunnable testrunnable = new Testrunnable();
//创建线程对象,通过线程对象来开启线程,代理
Thread thread = new Thread(testrunnable,"线程1");
Thread thread2 = new Thread(testrunnable,"线程2");
thread.start();
thread2.start();
//new Thread(testrunnable).start 匿名对象方式
for (int i = 0; i < 200; i++) {
System.out.println("我在看代码----"+i);
}
}
}
自行练习将案例一改为runnable接口方式
小结:
继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程,子类对象.start()
- 不建议使用:避免OOP单继承局限性
实现Runnable接口
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用,避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用
写一个案例练习2(初识并发):
//多个线程同时操作同一个对象
// 发现问题,多个线程操作同一个资源的情况下,线程不安全,数据紊乱
//可参考多线程同步锁我写的那篇,也可以继续往下看
public class Testrunnable2 implements Runnable {
//票数
private int ticketNums=10;
@Override
public void run() {
while (true){
if (ticketNums<=0) {
break;
}
//模拟延迟
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"票");//每拿一张递减
}
}
public static void main(String[] args) {
Testrunnable2 testrunnable2 = new Testrunnable2();
new Thread(testrunnable2,"小明").start();
new Thread(testrunnable2,"小红").start();
new Thread(testrunnable2,"小芳").start();
}
}
案例3:龟兔赛跑
步骤:
1,首先来个赛道,然后距离终点越来越近
2,判断比赛是否结束
3,打印出胜利者
4,龟兔赛跑开始
5,股市是乌龟赢了,兔子需要睡觉
6,乌龟赢得比赛
public class Race implements Runnable {
//胜利者
private static String winner;
@Override
public 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 flag=gameOver(i);
//比赛结束停止程序
if (flag) {
break;
}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"米");
}
}
//判断是否完成比赛
private boolean gameOver(int steps){
if (winner!=null) {
return true;
}{
if (steps>=100) {
winner=Thread.currentThread().getName();
System.out.println("winner is"+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
Thread thread = new Thread(race,"兔子");
Thread thread2 = new Thread(race,"污龟");
thread.start();
thread2.start();
}
}
3、实现Calleble 接口(前期先了解)
概要:
1,实现Callable接口,需要返回值类型
2,重写call方法,需要抛出异常
3,创建目标对象
4,创建执行服务:ExecutorService ser=Executors.newFixedThreadPool(1);
5,提交执行:Futureresult1=ser.submit(t1);
6,获取结果:boolean r1=result1.get()
7,关闭服务ser.shutdownNow();
修改下载图片的案例
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
public class CallAble implements Callable<Boolean> {
private String url;//文件地址
private String name;//保存的文件名
public CallAble(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public Boolean call() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载的文件名字是"+name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallAble callAble1 = new CallAble("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=4179487881,3847867256&fm=11&gp=0.jpg","1.jpg");
CallAble callAble2 = new CallAble("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1593598260498&di=1751f388a8f222d966efe98c0b0e5d88&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201308%2F05%2F20130805105309_5E2zE.jpeg","2.jpeg");
CallAble callAble3 = new CallAble("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1593598260493&di=d45ac86c1fafba25175847b96e0184d6&imgtype=0&src=http%3A%2F%2F00.minipic.eastday.com%2F20170331%2F20170331113141_98c8105e504ff71e68d59a6eaa30bd7e_5.jpeg","3.jpeg");
//创建执行服务
ExecutorService ser= Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> result1=ser.submit(callAble1);
Future<Boolean> result2=ser.submit(callAble2);
Future<Boolean> result3=ser.submit(callAble3);
//获取结果
boolean rs1=result1.get();
boolean rs2=result2.get();
boolean rs3=result3.get();
System.out.println(rs1);
System.out.println(rs2);
System.out.println(rs3);
//关闭服务
ser.shutdownNow();
// treadtest1.start();
// CallAble.start();
// treadtest3.start();
}
}
//下载类
class WebDownloader{
//下载方法
public void downloader(String url, String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));//url变成文件
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常");
}
}
}
线程状态,线程同步
转连接:线程的五大状态与线程方法加案例:
转链接:线程同步与同步锁详解加案例
线程通信问题
生产者消费者模式
是个问题不是设计模式
应用场景:生产者和消费者问题
◆假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费.
◆如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止.
◆如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止.
分析问题:
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件.
◆对于生产者,没有生产产品之前,要通知消费者等待.而生产了产品之后,又需要马上通知消费者消费
◆对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费.
◆在生产者消费者问题中 ,仅有synchronized是不够的
◆synchronized 可阻止并发更新同一个共享资源,实现了同步
◆synchronized 不能用来实现不同线程之间的消息传递(通信)
所以java提供了几个方法解决线程之间的通信问题
注意:均为Object类的方法,都只能在同步方法或者同步代码块中只用,否则会抛出异常
解决方式一:
并发协作模型“生产者/消费者模式”–>管程法
◆生产者:负责生产数据的模块(可能是方法,对象,线程,进程);
◆消费者:负责处理数据的模块(可能是方法,对象,线程,进程);< >
◆缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
解决方式二:
并发协作模型“生产者/消费者模式”–>信号灯法
做一个标识位,boolean类型,判断为真,就让其等待,判断为假就让他去通知,红灯停绿灯行。
管程法:
//测试生产者消费者模型,利用缓冲区解决-->管程法
public class TestPc {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Produter(container).start();
new Consumer(container).start();
}
}
//生产者
class Produter extends Thread{
SynContainer container;
public Produter(SynContainer container){
this.container=container;
}
//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了"+i+"只鸡");
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container=container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了--》"+container.pop().id+"只鸡");
// container.push(new Chicken(i));
}
}
}
//产品鸡
class Chicken{
int id;
public Chicken(int id) {
this.id = id;
}
}
//缓冲区
class SynContainer{
//需要一个容器大小
Chicken[] chickens=new Chicken[10];
//容量计数器
int count=0;
//生产者放入产品
public synchronized void push(Chicken chicken){
//如果容器满了,就需要等待消费者消费
if (count==chickens.length) {
//通知消费者消费,生产者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满,我们就需要丢入产品
chickens[count]=chicken;
count++;
//可以通知消费者消费了
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop(){
//判断能否消费
if (count==0) {
//等待生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Chicken chicken=chickens[count];
//c吃完了,通知消费者生产
this.notifyAll();
return chicken;
}
}
信号灯法:
//测试生产者消费者,信号灯法,标志位解决
public class TestPc2 {
public static void main(String[] args) {
TV tv = new TV();
new Actor(tv).start();
new Watch(tv).start();
}
}
//生产者-->演员
class Actor extends Thread{
TV tv;
public Actor(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
this.tv.play("快乐大本营");
} else {
this.tv.play("抖音:记录美好生活");
}
}
}
}
//消费者-->观众
class Watch extends Thread{
TV tv;
public Watch(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//产品-->节目
class TV{
//演员表演,观众等待
//观众观看,演员等待
String voice;//表演的节目
boolean flag=true;
//表演
public synchronized void play(String voice){
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了"+voice);
//通知观众观看
this.notifyAll();//通知唤醒
this.voice=voice;
this.flag= !this.flag; //取反,是真就是假,是假就是真
}
//观看
public synchronized void watch(){
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了"+voice);
//通知演员表演
this.notifyAll();
this.flag= !this.flag;
}
}
高级主题
线程池
◆背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
◆思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
◆好处:
◆提高响应速度(减少了创建新线程的时间)
◆降低资源消耗(重复利用线程池中线程,不需要每次都创建)
◆便于线程管理…
◆corePoolSize: 核心池的大小
◆maximumPoolSize:最大线程数
◆keepAliveTime: 线程没有任务时最多保持多长时间后会终止
使用线程池:
◆JDK 5.0起提供了线程池相关API: ExecutorService和Executors
◆ExecutorService:真正的线程池接口,常见子类: ThreadPoolExecutor
◆void execute(Runnable command) : 执行任务/命令,没有返回值,一般用来执行Runnable
◆ Future submit(Callable task): 执行任务,有返回值,一般用来执行Callable
◆void shutdown() :关闭连接池
◆Executors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool implements Runnable {
public static void main(String[] args) {
ExecutorService executorService= Executors.newFixedThreadPool(10);
executorService.execute(new TestPool());
executorService.execute(new TestPool());
executorService.execute(new TestPool());
executorService.execute(new TestPool());
executorService.shutdown();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
静态代理模式(动态代理见博客其他部分)
概述:
你:真实角色
婚庆公司:代理你,处理结婚的事情
结婚:实现都实现结婚接口即可
演示静态代理对比Thread
//静态代理模式
//真实对象(目标对象)都要实现同一个接口
//代理对象要代理真实角色
//优势:
//代理对象可以做真实对象做不了的事情
//真实对象专注做自己的事情
public class StaticProxy {
public static void main(String[] args) {
/***
//加入线程,最后再看
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
//换做lamda表达式
new Thread(() -> System.out.println("我爱你")).start();
new WeddingCompany(new You()).HappyMarry();
***/
//You you = new You();
WeddingCompany weddingCompany=new WeddingCompany(new You());
weddingCompany.HappyMarry();
}
}
//定义接口
interface Marry{
void HappyMarry();
}
//你,真实角色
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("你要结婚了");
}
}
//婚庆公司,代理角色,帮助你
class WeddingCompany implements Marry{
//代理谁--》目标对象(真实目标角色)
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();//真实角色对象
after();
}
private void after() {
System.out.println("结婚之后,送入洞房");
}
private void before() {
System.out.println("结婚之前,准备现场");
}
}
Lambda表达式
概述:
1,避免匿名内部类定义过多(通常写个外部类new一个对象,因为只用一次,写成匿名内部类)。
2,让代码更简洁。
3,去掉没有意义的代码,只留核心逻辑。
4,理解函数式接口(Function Inrweface),是学习java8 lamda表达式关键。
5,函数式接口定义:任何接口,如果只包含唯一一个抽象方法,那么就是一个函数式接口,对于函数式接口,可以通lamda表达式来创建该接口的对象。
正常表达方式:
public class TestLamda {
public static void main(String[] args) {
//创建一个接口对象,接口new一个实现类
Ilike ilike=new Like();
ilike.lambda();
}
}
//定义一个函数式接口
interface Ilike{
//一个抽象方法
void lambda();
}
//实现类
class Like implements Ilike{
@Override
public void lambda() {
System.out.println("我喜欢lambda");
}
}
优化简化–>推导过程:
public class TestLamda {
//2.静态内部类
static class Like2 implements Ilike{
@Override
public void lambda() {
System.out.println("我喜欢lambda2");
}
}
public static void main(String[] args) {
//创建一个接口对象,接口new一个实现类
Ilike ilike=new Like();
Ilike ilike2=new Like2();
ilike.lambda();
ilike2.lambda();
//3.局部内部类
class Like3 implements Ilike{
@Override
public void lambda() {
System.out.println("我喜欢lambda3");
}
}
Ilike ilike3=new Like3();
ilike3.lambda();
//4.匿名内部类:没有类的名称,必须借助接口或父类
Ilike ilike4=new Ilike() {
@Override
public void lambda() {
System.out.println("我喜欢lambda4");
}
};
ilike4.lambda();
//5.用lambda表达式
Ilike ilike5=()->{
System.out.println("我喜欢lambda5");
};
ilike5.lambda();
}
}
//定义一个函数式接口
interface Ilike{
//一个抽象方法
void lambda();
}
//1.实现类
class Like implements Ilike{
@Override
public void lambda() {
System.out.println("我喜欢lambda");
}
}
结果:
简化lambda过程精要:
public class TestLamda2 {
public static void main(String[] args) {
ILove love=new Love();
love.love(3);
//匿名内部类
ILove love2=new ILove() {
@Override
public void love(int a) {
System.out.println("I LOVE YOU----"+a);
}
};
love2.love(520);
//lambda表达式
ILove love3=(int a)->{
System.out.println("I LOVE YOU----"+a);
};
love3.love(521);
//lambda表达式简化1:参数类型
ILove love4=(a)->{
System.out.println("I LOVE YOU----"+a);
};
love4.love(522);
//lambda表达式简化2:简化括号
ILove love5=a->{
System.out.println("I LOVE YOU----"+a);
};
love5.love(1314);
//lambda表达式简化3:简化花括号
ILove love6=a-> System.out.println("I LOVE YOU----"+a);
love6.love(1314520);
}
}
//总结:
//lambda表达式只能有一行代码的情况下才能简化,如果有多行,那么就用花括号包裹
//前提是接口为函数式接口,只能有一个抽象方法
//多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号
interface ILove{
void love(int a);
}
class Love implements ILove{
@Override
public void love(int a) {
System.out.println("I LOVE YOU----"+a);
}
}