1、线程简介
1.1 多任务理解
我们举例来理解下多任务;
开车 + 打电话
吃饭 + 玩手机
这些动作都可以抽象为任务,虽然看起来一心二用,但人只有一个大脑,在一个时间片刻只能处理一个任务。
CPU 也是一样,面对多个任务,只能在一个时间片刻处理一个任务。
1.2 多线程理解
我们举例来理解下多线程;
多条线路同时跑起来;有多个分支共同去执行;
主线程调用 run 方法和调用 start 方法开启子线程的区别如下图所示。
1.3 线程与进程
执行程序的过程,叫进程;一个进程中有多个线程;
核心概念;
- 线程就是独立的执行路径;
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,GC 线程;
- main()称之为主线程,为系统的入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
- 线程会带来额外的开销,如 CPU 调度时间,并发控制开销。
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
2、线程实现
线程的三种实现方式:(线程实现在实际工作中优先使用Runnable接口和Callable接口方式)
2.1 第一种:继承 Thread 类,重写 run 方法
继承 Thread 类,重写 run 方法。创建这个类的对象,再调用 start() 即可
package com.example.democrud.democurd.test01; /** * @author 闫文超 */ //多线程调用 先继承Thred 方法 重写run方法 最后再用start 开启线程 //总结:线程的开启不一定立即开始执行,由CPU调度执行 public class testThread01 extends Thread { @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("我在学习多线程---" + i); } } public static void main(String[] args) { //创建一个线程对象 testThread01 thread01 = new testThread01(); //先执行run的方法在执行下面的内容 // thread01.run(); //start的方法和main同步执行;顺序同步 // 调用start 开启线程 thread01.start(); for (int i = 0; i < 200; i++) { System.out.println("我在学习thread--" + i); } } }
下载文件需要在 pom.xml 中 commons io 包。
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-io</artifactId> <version>1.3.2</version> </dependency>
package com.example.democrud.democurd.test01; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; public class testThred02 extends Thread { public String url;//图片路径 public String name;//图片的名字 public testThred02(String url, String name) { this.name = name; this.url = url; } @Override //代表重写 public void run() { downloadpng down = new downloadpng(); down.download(url, name); System.out.println("下载的文件名为" + name); } //主线程 main方法 public static void main(String[] args) { testThred02 thred01 = new testThred02("https://img1.baidu.com/it/u=413643897,2296924942&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=26be472b80013591100109eb63c7c5ec", "test01.jpg"); testThred02 thred02 = new testThred02("https://img1.baidu.com/it/u=307074048,654359288&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=554384a7bbd003adba3de4aaa73365d4", "test02.jpg"); testThred02 thred03 = new testThred02("https://img1.baidu.com/it/u=307074048,654359288&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=554384a7bbd003adba3de4aaa73365d4", "test03.jpg"); //开启线程 thred01.start(); thred02.start(); thred03.start(); } //先写一个下载器 class downloadpng { //引用地址和名字进行下载 public void download(String url, String name) { try { FileUtils.copyURLToFile(new URL(url), new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,downloader 方法出现问题"); } } } }
使用该方法下载网络图片。
2.2 第二种:
继承 Runnable 接口,创建 Tread 对象
或者使用Runnable接口通过线程池
下面演示的是继承 Runnable 接口,创建 Tread 对象:
继承 Runnable 接口,创建 Tread 对象,传入实现类,开启 start 方法
package com.example.democrud.democurd.test01; //1.实现Runnable接口 重写run方法 执行线程需要丢人Runnable接口实线路调用start方法启动线程 public class testThread03 implements Runnable { @Override public void run() { //run方法线程体 for (int i = 0; i < 20; i++) { System.out.println("闫文超在摸鱼啊---" + i); } } public static void main(String[] args) { //创建runnable接口的实现类对象 testThread03 thread03 = new testThread03(); //创建线程对象,通过线程对象来开启我们的线程;代理 Thread thread = new Thread(thread03); thread.start(); // 或者 // new Thread(thread03).start(); for (int i = 0; i < 20; i++) { System.out.println("我在学习当当中" + i); } } }
以上两种方式的比较:
继承 Thread 类
- 子类继承 Thread 类具备多线程能力
- 启动线程:子类对象 .start()
- 不建议使用:避免 OOP 单继承局限性
实现 Runnable 接口
- 实现接口 Runnable 具有多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,方便同一个对象被多个线程使用。
火车抢票实例:
Runnable 实现多线程,创造一个实列 ticketRunnable ,可共享给多个线程。
package com.example.democrud.democurd.test01; //实现Runnnable的方式调用多线程 //多个线程调用一个对象 // 买火车票的例子 // 发现问题:多个线程操作同一个资源,线程不安全,数据紊乱! public class testThread04 implements Runnable{ public int ticketNums=30; @Override public void run() { while (true){ if (ticketNums<=10){ break; } try { //线程模拟休眠2s Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票"); } } public static void main(String[] args) { testThread04 thread04 = new testThread04(); new Thread(thread04,"小王").start(); new Thread(thread04,"小龙").start(); new Thread(thread04,"小李").start(); new Thread(thread04,"黄牛").start(); } }
龟兔赛跑案例:
package com.example.democrud.democurd.test01; //实现runnable的接口 public class testThred05 implements Runnable { //胜利者 public static String winner; @Override public void run() { for (int i = 0; i < 1001; i++) { if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) { //每跑10步 兔子休眠0.5毫秒 try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } Boolean gameover = gameover(i); if (gameover) break; System.out.println(Thread.currentThread().getName() + "-跑了" + i); } } public Boolean gameover(int steps) { if (winner != null) { return true; } else { if (steps >= 1000) { winner = Thread.currentThread().getName(); System.out.println("winner is" + winner); return true; } } return false; } public static void main(String[] args) { testThred05 thred05 = new testThred05(); new Thread(thred05, "兔子").start(); new Thread(thred05, "乌龟").start(); } }