多线程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 接口(了解即可)

  1. 实现Callable 接口,需要返回只类型
  2. 重写call方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务: ExecutorService ser = Excutors.newFixedThreadPool(1);
  5. 提交执行:Future result1 = ser.submit(t1);
  6. 获取结果:boolean r1 = result1.get()
  7. 关闭服务
    =代码补齐=
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());
		}
	}

}
posted @ 2021-03-24 19:59  晒网达人  阅读(60)  评论(0)    收藏  举报