多线程Thread 一
目录
线程简介
线程实现(重点)
线程状态
线程同步(重点)
线程通信问题
高级主题
1 线程简介
任务,进程,线程,多线程
1.1 多任务
多任务---例 一边吃饭一边玩手机
看起来多个任务都在做,本质上每个微小时间上(大脑/CPU(非多核))依旧只做了一件事
多线程
类比生活中的例子,一条马路拥堵,划分出多个车道,井然有序。

1.2程序.进程.线程
操作系统中运行的程序就是进程。
每个程序可能同时包含声音、图像、字幕,这是因为进程中有多个线程,每个线程负责声音、图像、字幕不同的任务
1.3 Process与Thread
程序是指令和数据的有序集合,是一个静态的概念。而进程是执行程序的过程,是一个动态的概念,是系统资源分配的单位。
一个进程包含一个或多个线程。线程是CPU调度和执行的单位。
注意:
很多多线程是模拟出来的,真正的多线程是指有多个cpu,如多核服务器。如果是模拟出来的多线程,一个cpu在同一个时间点上只能执行一个代码
因为切换的很快,所以貌似同时执行。
1.4 本章核心概念
- 线程就是独立的执行路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如main,gc线程
- main()主线程,系统入口,用于执行整个程序
- 在一个进程中,如果开辟了多个线程, 线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
- 线程会带来额外的开销,如cpu调度时间,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
2 线程创建
Thread、Runnable、Callable
2.2 三种创建方式
- 继承Thread类
- 实现Runnable接口(Thread也是实现了Runnale接口,核心知识点)
- 实现Callable接口(了解)
2.2.1 继承Thread类,重写run()方法,调用start开启线程
注意 线程开始不一定立即执行,由CPU调度执行,
调用start才会开启线程,如果对象.run会直接作为普通方法调用,不会开启线程
例1
package demo1;
public class TestThread1 extends Thread {
public void run(){
//thread body
for (int i =0; i<50;i++){
System.out.println("I'm sleeping ***"+i);
}
}
public static void main(String[] args){
//create Thread oject, call start to open thread
new TestThread1().start();
for(int i=0;i<50;i++){
System.out.println("I'm working ***"+i);
}
}
}
执行结果,交叉显示
I'm working ***0
I'm working ***1
I'm working ***2
I'm working ***3
I'm working ***4
I'm working ***5
I'm working ***6
I'm working ***7
I'm working ***8
I'm working ***9
I'm working ***10
I'm working ***11
I'm working ***12
I'm working ***13
I'm working ***14
I'm working ***15
I'm working ***16
I'm working ***17
I'm working ***18
I'm sleeping ***0
I'm sleeping ***1
I'm working ***19
I'm sleeping ***2
I'm sleeping ***3
I'm sleeping ***4
I'm sleeping ***5
I'm sleeping ***6
I'm sleeping ***7
I'm working ***20
I'm sleeping ***8
I'm sleeping ***9
....
I'm working ***48
I'm working ***49
例2 多线程下载图片
注意:引用commons-io-2.jar,apache下的IO类库,可从官网下载。在IDEA里拷贝工程里然后邮件add as library.此时可以看见里面的资源了
此处主要使用copyURLToFile方法
package demo1;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class TestThread2 extends Thread {
private String url;
private String name;
//In Idea,shortcuts is TT2 initial abbr
public TestThread2(String url, String name){
this.url = url;
this.name = name;
}
public static void main(String[] args) {
//launch some threads
new TestThread2("https://pic.cnblogs.com/avatar/2278103/20210111214435.png", "1.png").start();
new TestThread2("https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=136548891,2778506376&fm=15&gp=0.jpg", "2.jpg").start();
new TestThread2("https://img2020.cnblogs.com/blog/2278103/202101/2278103-20210114210508555-478477828.jpg", "3.jpg").start();
}
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("download file "+ name);
}
}
class WebDownloader{
// download method
public void downloader(String url,String name){
try{
FileUtils.copyURLToFile(new URL(url),new File(name));
}catch(IOException e){
e.printStackTrace();
System.out.println("downloader IO Exception");
}
}
}
运行结果,下载完成的顺序不相同
download file 2.jpg
download file 3.jpg
download file 1.png
Process finished with exit code 0
2.2.2 实现Runable接口创建线程
自定义类如MyRunnable实现Runable接口,并实现run()方法
创建线程对象,此时传递自定义类的实例,调用start()方法来启动线程 (此处原理在动态代理部分详解)
例1
package demo1;
public class TestThread3 implements Runnable{
@Override
public void run() {
//shortcut fori in IDEA
for (int i = 0; i < 200; i++) {
System.out.println("我在看代码----");
}
}
public static void main(String[] args) {
//创建runnable接口实现类对象
TestThread3 t3 = new TestThread3();
//创建线程对象,通过线程对象来开启我们的线程,代理
new Thread(t3).start();
for (int i = 0; i <200 ; i++) {
System.out.println("我在学多线程");
}
}
}
例2 Runnable实现下载图片
package demo1;
public class TestThread3 implements Runnable {
private String url;
private String name;
//In Idea,shotcut is TT2 intial abbr
public TestThread3(String url, String name){
this.url = url;
this.name = name;
}
public static void main(String[] args) {
//launch some threads
TestThread3 thread1 = new TestThread3("https://pic.cnblogs.com/avatar/2278103/20210111214435.png", "1.png");
TestThread3 thread2 = new TestThread3("https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=136548891,2778506376&fm=15&gp=0.jpg", "2.jpg");
TestThread3 thread3 = new TestThread3("https://img2020.cnblogs.com/blog/2278103/202101/2278103-20210114210508555-478477828.jpg", "3.jpg");
new Thread(thread1).start();
new Thread(thread2).start();
new Thread(thread3).start();
}
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("download file "+ name);
}
}
class WebDownloader{
// download method
public void downloader(String url,String name){
try{
FileUtils.copyURLToFile(new URL(url),new File(name));
}catch(IOException e){
e.printStackTrace();
System.out.println("downloader IO Exception");
}
}
}
例 3 多个线程操作同一个对象
可以发现有多个线程抢同一张票,甚至出现负票
package demo1;
// many threads deal with same one object
// buy train ticket
public class TestThread4 implements Runnable {
private int ticketNums =10;
public static void main(String[] args) {
//launch some threads
TestThread4 t = new TestThread4();
new Thread(t,"xiaoming").start();
new Thread(t,"teacher").start();
new Thread(t,"xiaohong").start();
}
@Override
public void run() {
while(true){
if(ticketNums <= 0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢第 "+ ticketNums--+"张票");
}
}
}
例4 案例 龟兔赛跑 说明Runnable接口创建线程的好处
1.首先来个赛道距离,然后要离终点越来越近
2.判断比赛是否结束
3.打印出胜利者
4.龟兔赛跑开始
5.故事中乌龟赢的,要模拟兔子睡觉
6. 终于,乌龟赢得比赛
代码自己改的
package thread;
public class Race implements Runnable {
private static String winner;
private final int rabbitSpeed = 5;
private final int turtelSpeed = 1;
private final int length = 100;
public void run() {
String player = Thread.currentThread().getName();
int speed = 1;
for (int i = 0; i <= 100; i++) {
if (winner != null) {
break;
}
if ("rabbit".equals(player)) {
speed = rabbitSpeed;
if (i % 10 == 0) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
System.out.println(player + " has run " + i * speed + " miles");
if (i * speed >= 100) {
winner = player;
System.out.println("winner is " + winner);
break;
}
}
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race, "rabbit").start();
new Thread(race, "turtle").start();
}
}
2.3 实现Callable 接口(了解即可)
- 实现Callable 接口,需要返回只类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务: ExecutorService ser = Excutors.newFixedThreadPool(1);
- 提交执行:Future
result1 = ser.submit(t1); - 获取结果:boolean r1 = result1.get()
- 关闭服务
=代码补齐=
package thread;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.Callable;
/*
*
*/
public class TestCallable implements Callable {
}
class WebDownloader{
// download method
public void downloader(String url,String name){
try{
FileUtils.copyURLToFile(new URL(url),new File(name));
}catch(IOException e){
e.printStackTrace();
System.out.println("downloader IO Exception");
}
}
}
2.4 Lambda表达式
-
函数式接口定义
- 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
public interface Runnable{ public abstract void run(); }- 对于函数式接口,我们可以通过lambda来创建该接口的对象
-
lambda好处:
避免匿名内部类定义过多,去掉一堆没有意义的代码,只留下核心逻辑
其实质属于函数式编程的概念,只关注输入和输出 -
语法 (形式参数)->{代码块}
形式参数可有多个,用逗号隔开,如果没有参数,留空即可
(params) -> expression[表达式]
(params) -> statement[语句]
(params) -> {statements}
a->System.out.println("I like lambda-->"+a)
new Thread(()->System.out.println("多线程学习...")).start(); -
runnable 接口里只有run方法正符合lambda接口式函数
例1.推导无参lambda使用过程
package proxy;
/*
推导lambda表达式
*/
public class TestLambda1 {
//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();
like = new Like2();
like.lambda();
//4. 局部内部类
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda3");
}
}
like = new Like3();
like.lambda();
//5.匿名内部类, 没有类的名称,必须借助解耦或者父类
like = new ILike() {
@Override
public void lambda() {
System.out.println("I like lambda4");
}
};
like.lambda();
//6.用lambda简化
like = ()->{
System.out.println("I like lambda5");
};
like.lambda();
}
}
//1. 定义一个函数式接口
interface ILike {
void lambda();
}
//2. 实现类
class Like implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda1");
}
}
- 有参数lambda简化
- lambda 表达式只有一行代码的时候才可以简化花括号分号成一行,多行必须用代码块包装
- 单个参数可以去掉参数的小括号,多个参数必须奥有小括号
- 可以去掉参数类型,但针对多个参数必须要去掉就都去掉,要么就都不去掉
- 单行简化形式没有花括号时,有return也要把return关键字也省略
package proxy;
public class TestLambda2 {
public static void main(String[] args) {
//1. lambda表达式简化
Ilove love = (String name) -> {
System.out.println("I love " + name);
};
love.love("paradise");
//简化1. 省略参数类型
love = (a) -> {
System.out.println("I love 1 " + a);
};
love.love("简化1");
//简化2. 简化括号,直接参数箭头方法体
love = name -> {
System.out.println("I love 2 " + name);
};
love.love("简化参数括号");
//简化3 . 去掉花括号
love = name -> System.out.println("I love 3 "+name);
love.love("简化函数体花括号");
//总结:
//lambda 表达式只有一行代码的时候才可以简化花括号成一行,多行必须用代码块包装
//前提是接口必须是函数式接口,即接口内只有一个方法
//单个参数可以去掉参数的小括号,多个参数必须奥有小括号
// 单个参数多个参数都可以去掉参数类型,但必须要去掉就都去掉,要么就都不去掉
}
}
interface Ilove {
void love(String name);
}
- Lambda表达式注意事项
- 使用Lambda必须要有接口,并要求接口中有且仅有一个抽象方法
- 必须要有上下文环境,才能推到出Lambda对应的接口
根据局部变量赋值得知Lambda对应的接口:Runnable r =()-> System.out.println("Lambda表达式“);
根据调用方法的参数得知Lambda对应的接口:new Thread(()->System.out.println("Lambda表达式").start;
- Lambda表达式和匿名内部类的区别
- 所需类型不同
匿名内部类:可以是接口,抽象类,具体类
Lambda:只能是接口 - 使用限制不同
匿名内部类应用在接口上,接口内并不限制有且只有一个抽象方法 - 实现原理不同
匿名内部类:编译后产生一个单独的.class字节码文件
Lambda表达式:对应的字节码文件会在运行时动态生成,编译后不会产生单独的.classs文件
- 所需类型不同
2.5 静态代理
静态代理模式总结:
- 真实对象和代理对象都要实现同一个接口
- 代理对象要代理真实角色,类里有真实对象属性
好处: - 代理对象可以完成真实对象做不了的事情
- 真实对象专注自己的事情
例 婚庆公司代理结婚准备婚礼和清场杂事
package proxy;
public class StaticProxy {
public static void main(String[] args) {
new WeddingCompany(new You()).happyMarry();
// thread代理的是Runnable接口对象,调用的start方法,和上面WeddingCompany实现接口HappMarr,代理you调用happMarry一样
new Thread(()-> System.out.println("I love you")).start();
}
}
interface HappyMarry {
void happyMarry();
}
class You implements HappyMarry{
@Override
public void happyMarry() {
System.out.println("qing is going to marry");
}
}
class WeddingCompany implements HappyMarry{
HappyMarry target;
public WeddingCompany(HappyMarry target) {
this.target = target;
}
@Override
public void happyMarry() {
System.out.println("before --- prepare wedding ceremony");
this.target.happyMarry();
System.out.println("after --- clean up the site");
}
}
3 线程状态
3.1 状态概述
5大状态

线程方法

3.2 停止线程
- 不推荐使用JDK提供的stop()、destroy方法废弃了
- 推荐线程自己停下来-->控制循环次数,不建议死循环
- 建议一个标志位进行终止变量,当flag=false则终止线程运行
package thread;
public class TestStop implements Runnable {
//1. define flag
private boolean flag = true;
public void run(){
int i = 0;
//2. use flag in the thread body
while(flag){
System.out.println("run ... Thread "+ i++);
}
}
//3. provide a public method to change flag
public void stop(){
this.flag = false;
}
public static void main (String[] args) throws InterruptedException{
TestStop testStop = new TestStop();
new Thread(testStop).start();
for(int i = 0; i<1000; i++){
System.out.println("main " +i);
if (i == 900){
//4. call stop method to switch flag
testStop.stop();
System.out.println("stop the thread");
}
}
}
}
3.3 线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数;
- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时
- 每个对象都有一个锁,sleep不会释放锁
例 模拟网络延迟,放大问题的发生性
买票参上
例 倒计时
public class TestSleep2 {
public static void tenDown() throws InterruptedException{
int num = 10;
while(true){
Thread.sleep(1000);
System.out.println(num--);
if (num==0){
break;
}
}
}
public static void main(String[] args) throws InterruptedException {
tenDown();
Date startTime = new Date(System.currentTimeMillis());
while(true){
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
//update time
startTime = new Date(System.currentTimeMillis());
}
}
}

浙公网安备 33010602011771号