1. 线程简介
程序:程序时指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
进程:执行程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程。由它自身的产生、存在和消亡的过程
线程是由进程创建的,是进程的一个实体。通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位
很多多线程是模拟出来的,真正多线程实现并行,是有多个CPU,即多核。一个CPU统一时间点只能执行一个代码,因为切换快,所以就有同时执行的错觉。 宏观并行,微观串行- public static void main(String[] args) {
- Runtime runtime = Runtime.getRuntime();
- //获取当前电脑的CPU数量
- int i = runtime.availableProcessors();
- System.out.println(i);
- }
复制代码 2. 线程实现(重点)
2.1 实现方式一:继承Thread类
实现步骤:
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
- package com.ThreadStudy;
- /**
- * @ClassName ThreadDemo01
- * @Description TODO 创建线程方式一:继承Thread类
- * @Author Mark
- * @Date 2022/6/23 13:40
- * @Version 1.0
- */
- //创建线程方式一:继承Thread类,重写run方法,调用start开始线程
- public class ThreadDemo01 {
- public static void main(String[] args) {
- Cat cat = new Cat(); //new Cat() .var/ctrl+alt+v/alt+enter导入前半部分
- cat.start();//启动线程
- //当main线程启动一个子线程 Thread-0 主线程不会阻塞,会继续执行
- /*
- * 源码:
- * public synchronized void start() {
- * start0();
- * }
- *
- * private native void start0();//start0是本地方法,是JVM调用,底层由C/C++实现的
- * //真正实现多线程效果由start0()实现
- * */
- }
- }
- //Thread实现了Runnable接口的run方法
- /*
- @Override
- public void run() {
- if (target != null) {
- target.run();
- }
- }
- */
- class Cat extends Thread {//Cat继承于Thread类,该类可当作线程使用
- int times = 0;
- @Override
- public void run() {//重写run方法,写入自己的业务代码
- while (true) {
- //每隔一秒输出一次“喵喵”
- System.out.println("喵喵 " + (++times)+" 线程名:"+Thread.currentThread().getName());
- //sleep:使线程休眠 ctrl+alt+t写入try-catch,或者鼠标指向要抛出异常的方法,ctrl+alt+enter
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- if (times == 80)
- break;//当times=8,线程停止
- }
- }
- }
复制代码 进程启动--->创建main线程--->在主线程中创建一个新的子线程Thread-0
可以使用控制台jconsole打开Java监视和管理控制台查看线程执行情况,当所有的线程结束后,进程才会结束(当主线程结束,主线程的子线程还未结束,进程并不会关闭,而是等待子线程结束后关闭)
案例:多线程下载图片- package com.ThreadStudy;
- import org.apache.commons.io.FileUtils;
- import java.io.File;
- import java.io.IOException;
- import java.net.URL;
- /**
- * @ClassName ThreadDemo02
- * @Description TODO 练习Thread,实现多线程同步下载图片
- * @Author Mark
- * @Date 2022/6/23 19:09
- * @Version 1.0
- */
- public class ThreadDemo02 extends Thread {
- private String url;//网络地址
- private String name;//保存的文件名
- public ThreadDemo02(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) {
- ThreadDemo02 thread1 = new ThreadDemo02("https://pics0.baidu.com/feed/1b4c510fd9f9d72a803a1a357e5dae3e349bbb3a.jpeg", "fpx.jpeg");
- ThreadDemo02 thread2 = new ThreadDemo02("https://pics0.baidu.com/feed/0d338744ebf81a4c46cbca834bc6e653272da6f1.png", "mgn.jpeg");
- ThreadDemo02 thread3 = new ThreadDemo02("https://pic.rmb.bdstatic.com/bjh/down/e2cbad3b771358fec7de7727ca450426.png", "edg.png");
- thread1.start();
- thread2.start();
- thread3.start();
- //下载了的文件名为:mgn.jpeg --thread2
- //下载了的文件名为:fpx.jpeg --thread1
- //下载了的文件名为:edg.png --thread3
- }
- }
- //下载器
- class WebDownloader {
- //下载方法
- public void downloader(String url, String name) {
- try {
- FileUtils.copyURLToFile(new URL(url), new File("myThread\\src\\com\" + name));
- } catch (IOException e) {
- e.printStackTrace();
- System.out.println("IO异常,downloader方法出现异常");
- }
- }
- }
复制代码 2.2 实现方式二:实现Runnable接口
java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类创建线程显然不可行了,因此我们通过实现Runnable接口来创建线程
实现步骤:
- 定义MyRunnable类实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
ThreadDemo03 threadDemo03=new ThreadDemo03();
new Thread(threadDemo03).start();
推荐使用Runnable对象,因为Java单继承的局限性- package com.ThreadStudy;
- /**
- * @ClassName TheardDemo03
- * @Description TODO 创建线程方式二:实现Runnable接口
- * @Author Mark
- * @Date 2022/6/23 20:54
- * @Version 1.0
- */
- public class ThreadDemo03 {
- public static void main(String[] args) {
- // Dog dog = new Dog();
- // //dog.start; dog不能调用start
- // //创建Thread对象,把dog对象(实现Runnable的对象)放入Thread
- // new Thread(dog).start();
- // //这里底层使用了设计模式:代理模式
- Tiger tiger = new Tiger();
- Proxy proxy = new Proxy(tiger);
- proxy.start();
- }
- }
- class Animal{
- }
- class Tiger extends Animal implements Runnable{
- @Override
- public void run() {
- System.out.println("嗷嗷");
- }
- }
- //线程代理类,模拟一个极简的Thread类
- class Proxy implements Runnable { //这里可以把Proxy类当作Thread
- private Runnable target = null;//属性、类型是Runnable
- @Override
- public void run() {
- if (target != null) {
- target.run();//动态绑定
- }
- }
- public Proxy(Runnable target) {
- this.target = target;
- }
- public void start() {
- start0();//⭐⭐⭐⭐⭐
- }
- private void start0() {
- run();
- }
- }
- class Dog implements Runnable {
- int times = 0;
- @Override
- public void run() {
- while (true) {
- System.out.println("汪汪" + (++times) + " 线程名:" + Thread.currentThread().getName());
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- if (times == 8) {
- break;
- }
- }
- }
- }
复制代码 代理是为了不改变原来代码的基础上增强代码
静态代理:- package com.ThreadStudy;
- /**
- * @ClassName StaticProxy
- * @Description TODO 静态代理
- * @Author Mark
- * @Date 2022/6/24 16:10
- * @Version 1.0
- */
- public class StaticProxy {
- public static void main(String[] args) {
- //代理是为了不改变原来代码的基础上增强代码
- //在原来只能”嘿嘿嘿“的基础上增加了”准备婚礼“和”付尾款“
- // WeddingCompany weddingCompany=new WeddingCompany(new You());//WeddingCompany代理You
- // weddingCompany.Marry();
- You you=new You();
- new Thread(()-> System.out.println("我爱你")).start();
- new WeddingCompany(you).Marry();
- }
- }
- //功能接口
- interface Marry{
- //结婚方法
- void Marry();
- }
- //You:真实对象,实现结婚接口
- class You implements Marry{
- @Override
- public void Marry() {
- System.out.println("嘿嘿嘿");
- }
- }
- //WeddingCompany:代理角色,帮助You
- class WeddingCompany implements Marry{
- //帮助对象
- private Marry target;
- public WeddingCompany(Marry target) {
- this.target = target;
- }
- @Override
- public void Marry() {
- before();
- this.target.Marry();
- after();
- }
- private void after() {
- System.out.println("付尾款");
- }
- private void before() {
- System.out.println("准备婚礼");
- }
- }
复制代码 方式一和方式二对比:
继承Thread类实现Runnable接口子类继承Thread类具备多线程能力实现接口Runnable具有多线程能力启动线程:子类对象.start()启动线程:线程对象(目标对象).start()不建议使用:避免OOP单继承局限性建议使用:避免单继承,灵活方便,方便同一个对象被多个线程使用案例:售票
通过继承Thread实现:- public class SellTicket {
- public static void main(String[] args) {
- SellTicket01 sellTicket01 = new SellTicket01();
- SellTicket01 sellTicket02 = new SellTicket01();
- SellTicket01 sellTicket03 = new SellTicket01();
- sellTicket01.start();
- sellTicket02.start();
- sellTicket03.start();
- }
- }
- class SellTicket01 extends Thread {
- private static int ticketNum = 100;//让多个线程共享ticketNum
- @Override
- public void run() {
- while (true) {
- if (ticketNum <= 0) {
- System.out.println("售票结束");
- break;
- }
- //休眠50ms
- try {
- Thread.sleep(50);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("窗口 " + Thread.currentThread().getName() + "售出一张票" +
- " 剩余票数:" + (--ticketNum));
- }
- }
- }
复制代码
对于函数式接口,我们可以通过Lambda表达式来创建该接口的对象
推导lambda表达式:
- public class SellTicket02 {
- public static void main(String[] args) {
- SellTicketWindow sellTicketWindow=new SellTicketWindow();
- new Thread(sellTicketWindow).start();
- new Thread(sellTicketWindow).start();
- new Thread(sellTicketWindow).start();
- }
- }
- class SellTicketWindow implements Runnable {
- private int ticketNum = 100;
- @Override
- public void run() {
- while (true) {
- if (ticketNum <= 0) {
- System.out.println("end...");
- break;
- }
- try {
- Thread.sleep(50);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("窗口 " + Thread.currentThread().getName() + "售出一张票" +
- " 剩余票数:" + (--ticketNum));
- }
- }
- }
复制代码 lambda简化:- public class ThreadDemo05 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(1);
- } 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;
- } else if (steps >= 100) {
- winner = Thread.currentThread().getName();
- System.out.println("winner is" + winner);
- return true;
- } else {
- return false;
- }
- }
- public static void main(String[] args) {
- ThreadDemo05 threadDemo05 = new ThreadDemo05();
- new Thread(threadDemo05, "小兔子").start();
- new Thread(threadDemo05, "老乌龟").start();
- }
- }
复制代码 3. 线程状态
线程方法:
方法说明void setName(String name)将此线程的名称更改为等于参数 namevoid getName()返回此线程的名称void start()导致此线程开始执行; Java虚拟机调用此线程的run方法void run()调用该Runnable对象的run方法void setPriority(int newPeiority)更改此线程的优先级void getPriority()返回此线程的优先级void join()等待这个线程死亡。static void sleep(long millis)导致正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停void interrupt()中断这个线程boolean isAilve()测试这个线程是否活着static void yield()暂停当前正在执行的线程对象,并执行其他线程3.1 停止线程
- 不推荐使用JDK提供的stop()、destory()方法(@Deprecated(since="1.2"):已废弃)
- 推荐线程自己停止下来:将线程体写到循环内,利用次数终止循环,不建议死循环
- 建议用一个标志位进行终止变量。当flag=false,终止线程运行:通知方式
- package com.ThreadCallable;
- import org.apache.commons.io.FileUtils;
- import java.io.File;
- import java.io.IOException;
- import java.net.URL;
- import java.util.concurrent.*;
- /**
- * @ClassName ThreadCallableDemo
- * @Description TODO 线程创建方式三:实现Callable接口
- * @Author Mark
- * @Date 2022/6/23 22:06
- * @Version 1.0
- */
- //①实现Callable接口,需要返回值类型
- public class ThreadCallableDemo implements Callable {
- private String url;//网络地址
- private String name;//保存的文件名
- public ThreadCallableDemo(String url, String name) {
- this.url = url;
- this.name = name;
- }
- //②重写call方法,下载图片线程的执行体
- @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 {
- //③创建目标对象
- ThreadCallableDemo thread1 = new ThreadCallableDemo("https://pics0.baidu.com/feed/1b4c510fd9f9d72a803a1a357e5dae3e349bbb3a.jpeg", "fpx.jpeg");
- ThreadCallableDemo thread2 = new ThreadCallableDemo("https://pics0.baidu.com/feed/0d338744ebf81a4c46cbca834bc6e653272da6f1.png", "mgn.jpeg");
- ThreadCallableDemo thread3 = new ThreadCallableDemo("https://pic.rmb.bdstatic.com/bjh/down/e2cbad3b771358fec7de7727ca450426.png", "edg.png");
- //④创建执行服务:
- ExecutorService ser= Executors.newFixedThreadPool(1);
- //⑤提交执行:
- Future<Boolean> result1= ser.submit(thread1);
- Future<Boolean> result2= ser.submit(thread2);
- Future<Boolean> result3= ser.submit(thread3);
- //⑥获取结果:
- boolean rs1=result1.get();
- boolean rs2=result2.get();
- boolean rs3=result3.get();
- //⑦关闭服务:
- ser.shutdown();
- }
- }
- //下载器
- class WebDownloader {
- //下载方法
- public void downloader(String url, String name) {
- try {
- FileUtils.copyURLToFile(new URL(url), new File("myThread\\src\\com\" + name));
- } catch (IOException e) {
- e.printStackTrace();
- System.out.println("IO异常,downloader方法出现异常");
- }
- }
- }
复制代码 3.2 线程中断
- public interface Runnable{
- public abstract void run();
- }
复制代码 3.3 线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException;
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延迟、倒计时等
- 每个对象都有一个锁,sleep不会释放锁
[code]package com.ThreadStudy;import java.text.SimpleDateFormat;import java.util.Date;/** * @ClassName SleepThread2 * @Description TODO 倒计时+打印系统时间 * @Author Mark * @Date 2022/6/24 21:16 * @Version 1.0 */public class SleepThread2 { public static void main(String[] args) {// try {// timeDown();// } catch (InterruptedException e) {// e.printStackTrace();// } Date startTime =new Date(System.currentTimeMillis()); while(true){ try { System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime)); startTime =new Date(System.currentTimeMillis());//更新当前时间 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } //计时器// public static void timeDown() throws InterruptedException {// int num=10;// while(true){// Thread.sleep(1000);// System.out.println(num--);// if (num |