多线程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号