狂神说Java多线程详解
1、基本概念
-
进程
-
在操作系统中运行的程序就是进程,进程就是执行程序的一次执行过程,它是一个动态的概念式系统资源分配的单位
-
通常再一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义,线程是CPU调度和执行的单位
-
-
线程
- 线程就是独立的执行路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,比如主线程,GC线程
- main()称之为主线程,为系统的入口,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行是由调度器安排调度的,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
- 线程会带来额外的开销,如CPU调度时间,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
-
多线程
- 多条执行路径,主线程与子线程并行交替执行(普通方法只有主线程一条路径)
2、线程创建
2.1、 继承 Thread 类(重点)
- 自定义线程类,继承Thread类
- 重写run()方法,编写线程执行体
- 在主函数中创建一个线程对象,调用start()方法开启线程。
案例:
package com.nty.test02;
public class TestThread extends Thread {
@Override
public void run() {
//run方法线程方法体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码----" + i);
}
}
public static void main(String[] args) {
//创建一个线程对象
TestThread testThread = new TestThread();
//start开启线程
testThread.start();
//主线程
for (int i = 0; i < 200; i++) {
System.out.println("我在学习多线程-----" + i);
}
}
}
总结:线程开启不一定立即执行,由CPU调度执行。
案例:图片下载
package com.nty.test02;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class TestThread1 extends Thread {
private String url; //网络历经
private String name; // 保存的文件名
public TestThread1(String url, String name) {
this.name = name;
this.url = url;
}
//下载图片线程的执行体
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("下载了文件名为:" + name);
}
public static void main(String[] args) {
TestThread1 testThread1 = new TestThread1("https://img-blog.csdnimg.cn/20210531145950543.png", "2.png");
TestThread1 testThread2 = new TestThread1("https://img-blog.csdnimg.cn/20210531145950543.png", "3.png");
TestThread1 testThread3 = new TestThread1("https://img-blog.csdnimg.cn/20210531145950543.png", "4.png");
TestThread1 testThread4 = new TestThread1("https://img-blog.csdnimg.cn/20210531145950543.png", "5.png");
testThread1.start();
testThread2.start();
testThread3.start();
testThread4.start();
}
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异常,downler方法出现问题");
}
}
}
}
2.2、 实现Runnable接口(重点)
- 自定义线程类,实现Runnable接口
- 重写run()方法,编写线程执行体
- 执行线程需要丢入runnable接口实现类,调用start()方法。
案例:
package com.nty.test02;
public class TestThread2 implements Runnable {
@Override
public void run() {
//run方法线程方法体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码----" + i);
}
}
public static void main(String[] args) {
//创建一个线程对象
TestThread2 testThread2 = new TestThread2();
//创建线程对象,通过线程对象来开启线程,代理
// Thread thread = new Thread(testThread2);
//
// //start开启线程
// thread.start();
new Thread(testThread2).start();
//主线程
for (int i = 0; i < 200; i++) {
System.out.println("我在学习多线程-----" + i);
}
}
}
以上两种方式的比较:
继承 Thread 类
- 子类继承 Thread 类具备多线程能力
- 启动线程:子类对象 .start()
- 不建议使用:避免 OOP 单继承局限性
- 实现 Runnable 接口
实现接口 Runnable
- 具有多线程能力
- 启动线程:传入目标对象 + Thread对象.start()
- 推荐使用:避免单继承局限性,方便同一个对象被多个线程使用。
2.3、 实现Callable接口(了解)
实现Callable接口,重写call方法。
- 实现 Callable 接口,需要返回值类型
- 重写 call 方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService = Executor.newFixedThreadPool(1);
- 提交执行:Future result1 = ser.submit(1);
- 获取结果:boolean r1 = result.get()
- 关闭服务:ser.shutdownNow():
图片下载改写:
package com.nty.test02;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
public class TestCallable implements Callable<Boolean> {
private String url; //网络历经
private String name; // 保存的文件名
public TestCallable(String url, String name) {
this.name = name;
this.url = url;
}
//下载图片线程的执行体
@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 {
TestCallable testThread1 = new TestCallable("https://img-blog.csdnimg.cn/20210531145950543.png", "2.png");
TestCallable testThread2 = new TestCallable("https://img-blog.csdnimg.cn/20210531145950543.png", "3.png");
TestCallable testThread3 = new TestCallable("https://img-blog.csdnimg.cn/20210531145950543.png", "4.png");
TestCallable testThread4 = new TestCallable("https://img-blog.csdnimg.cn/20210531145950543.png", "5.png");
//创建执行服务:
ExecutorService service = Executors.newFixedThreadPool(4);
//提交执行:
Future<Boolean> r1 = service.submit(testThread1);
Future<Boolean> r2 = service.submit(testThread2);
Future<Boolean> r3 = service.submit(testThread3);
Future<Boolean> r4 = service.submit(testThread4);
// 获取结果:
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
boolean rs4 = r4.get();
//关闭服务:
service.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异常,downler方法出现问题");
}
}
}
}
案例2:
package com.nty.test02;
import java.util.concurrent.*;
public class TestCallable2 implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
System.out.println("创建成功");
return true;
}
public static void main(String[] args) {
TestCallable2 callable = new TestCallable2();
//创建执行服务
ExecutorService service = Executors.newFixedThreadPool(1);
//提交执行
Future<Boolean> result = service.submit(callable);
try {
boolean isTrue = result.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
service.shutdownNow();
}
}
2.4、 Lambda表达式
Lambda 表达式属于函数式编程的概念
- λ 希腊字母表中排序第十一位的字母,英语名称为 Lamda
- 避免匿名内部类定义过多
- 其实质属于函数式编程的概念
- 去掉了一堆没有意义的代码,只留下核心逻辑
案例:
(paraems) -> expressionp[表达式]
(params) -> statement[语句]
(params) -> {statements}
a->System.out.println("i like lamda-->"+a);
new Thread(()->System.out.println("多线程学习...")).start();
-
理解 Functional Interface(函数式接口)是学习 Java 8 Lambda 表达式的关键所在
-
函数式接口的定义:
- 任何接口,如果只包含唯一一个抽象方法,那么它就是函数式接口
public interface Runnable{ public abstract void run(); }
- 对于函数式接口,可以通过 Lambda 表达式来创建该接口的对象
//1.定义一个函数式接口 interface ILike{ void like(); } //6.用lambda简化,-->函数式接口 like = ()->{ System.out.println("i like lambda5"); };
Lambda表达式简化:
package com.nty.Lambda;
public class TestLambda {
public static void main(String[] args) {
ILove love = null;
/*
ILove love = (String name) -> {
System.out.println("I love you, " + name + "!");
};
love.love("xiahouxue");
//简化Lambda表达式,1.简化类型
love = (name) -> {
System.out.println("I love you, " + name + "!");
};
//简化Lambda表达式,2.去掉括号
love = name -> {
System.out.println("I love you, " + name + "!");
};
*/
//3. 简化花括号
love = name -> System.out.println("I love you, " + name + "!");
love.love("521");
//总结:
//lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹。
//前提是接口为函数式接口
//多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号,
}
}
interface ILove {
void love(String name);
}
package com.nty.Lambda;
/*推导lambda表达式*/
public class Lambda {
//3.静态内部类
static class Like2 implements ILike {
@Override
public void lambda() {
System.out.println("I like Lambda2!");
}
}
public static void main(String[] args) {
ILike like = new Like();
like.lambda();
Like2 like1 = new Like2();
like1.lambda();
//4.局部内部类
class Like3 implements ILike {
@Override
public void lambda() {
System.out.println("I like Lambda3!");
}
}
Like3 like3 = new Like3();
like3.lambda();
//5.匿名内部类,没有类的名称,必须借助接口或者父类
like = new ILike() {
@Override
public void lambda() {
System.out.println("I like Lambda4!");
}
};
like.lambda();
//lambda简化
like = () -> {
System.out.println("I like Lambda5!");
};
like.lambda();
}
}
//定义一个函数式接口
interface ILike {
void lambda();
}
//实现类
class Like implements ILike {
@Override
public void lambda() {
System.out.println("I like Lambda!");
}
}
2.5、 静态代理模式
案例:
package com.nty.marry01;
public class StaticProxy {
public static void main(String[] args) {
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 before() {
System.out.println("结婚之前,布置现场,邀请客人");
}
private void after() {
System.out.println("送客,清理现场");
}
}
3、线程状态
五大状态:
- 创建状态
- 就绪状态
- 阻塞状态
- 运行状态
- 死亡状态
3.1、 线程的一些常用方法
3.2、 线程休眠——sleep()
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep 存在异常 InterruptedException
- sleep 时间达到后线程进入就绪状态
- sleep 可以模拟网络延时,倒计时等 (故意设置延时收优化钱