2022.8.20 线程简介与三种创建方式
1.多任务

现实中太多这样同时做多件事情的例子了,看起来是多个任务都在做,其实本质上我们的大脑在同一时间依旧只做了一件事情。

原来是一条路,慢慢因为车太多了,道路阻塞,效率极低。为了提高使用的效率,能够充分利用道路,于是加了多个车道。从此,妈妈再也不用担心道路阻塞了。

3.程序.进程.线程

4.Process与Thread
-
说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
-
而进程则是执行程序的依次执行过程,它是一个动态的概念。是系统资源分配的单位。
-
通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
注意: 很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错局。
5.核心概念
-
线程就是独立的执行路径
-
在程序运行时,即使没有自己创建线程,后台也会有多个线程,比如主线程,gc线程
-
main()称之为主线程,为系统的入口,用于执行整个程序
-
在一个进程中,如果开辟了多个线程,线程的运行是由调度器(cpu)安排调度的,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的
-
对同一份资源操作时mm会存在资源抢夺的问题,需要加入并发控制
-
线程会带来额外的开销,如CPU调度时间,并发控制开销
-
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
2、线程创建(三种方法)
2.1、继承Thread类(重要)
自定义线程类继承Thread类
重写run()方法,编写线程执行体
创建线程对象,调用start()方法启动线程
1 package com.xing.demo01; 2 3 /** 4 * @program: 多线程 5 * @Date: 2022/08/14 6 * @author: 16159 7 * @description: 8 * @Modified By: 9 **/ 10 public class TestThread extends Thread { 11 12 @Override 13 public void run() { 14 //run方法线程体 15 for (int i = 0; i < 20; i++) { 16 System.out.println("我在看代码----" + i); 17 } 18 } 19 20 public static void main(String[] args) { 21 //main线程,主线程 22 //创建一个线程对象 23 TestThread testThread = new TestThread(); 24 25 //调用start()开启线程, 26 // 如果这里是testThread.run();那么先执行run方法,在执行下面的代码 27 // 如果这里是testThread.start();线程不一定立即执行,CPU安排调度 28 testThread.start(); 29 30 31 for (int i = 0; i < 2000; i++) { 32 System.out.println("我在学习多线程----" + i); 33 } 34 } 35 }


总结:线程不一定立即执行,CPU安排调度
案例
下载commons-io-2.11.0.jar包
创建lib目录,将包复制进去


导包成功
1 package com.xing.demo01; 2 3 import org.apache.commons.io.FileUtils; 4 5 import java.io.File; 6 import java.io.IOException; 7 import java.net.URL; 8 9 /** 10 * @program: 多线程 11 * @Date: 2022/08/14 12 * @author: 16159 13 * @description: 14 * @Modified By: 15 **/ 16 public class TestThread2 extends Thread { 17 18 private String url;//网络图片地址 19 private String name;//保存的文件名 20 21 //有参构造 22 public TestThread2(String url, String name) { 23 this.url = url; 24 this.name = name; 25 } 26 27 //下载图片线程的执行体 28 @Override 29 public void run() { 30 WebDownloader webDownloader = new WebDownloader(); 31 webDownloader.downloader(url, name); 32 System.out.println("下载了文件名为:" + name); 33 } 34 35 public static void main(String[] args) { 36 TestThread2 t = new TestThread2("https://img-home.csdnimg.cn/images/20201124032511.png", "1.png"); 37 TestThread2 t1 = new TestThread2("https://img-home.csdnimg.cn/images/20201124032511.png", "2.png"); 38 TestThread2 t2 = new TestThread2("https://img-home.csdnimg.cn/images/20201124032511.png", "3.png"); 39 t.start(); 40 t1.start(); 41 t2.start(); 42 } 43 } 44 45 //下载器 46 class WebDownloader { 47 //下载方法 48 public void downloader(String url, String name) { 49 try { 50 // 将一个url变成一个文件 51 FileUtils.copyURLToFile(new URL(url), new File(name)); 52 } catch (IOException e) { 53 e.printStackTrace(); 54 System.out.println("IO异常,downloader方法出现问题"); 55 } 56 } 57 }


线程"同时"执行,不是按照顺序
2.2、实现Runnable接口
推荐使用Runnable对象,因为Java单继承的局限性
自定义线程类实现Runnable接口
实现run()方法,编写线程执行体
创建线程对象,调用start()方法启动对象
相较于方法1,多了一个代理
1 package com.xing.demo01; 2 3 /** 4 * @program: 多线程 5 * @Date: 2022/08/14 6 * @author: 16159 7 * @description: 8 * @Modified By: 9 **/ 10 //创建线程方式2﹔实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类.调用start方法. 11 //这就是一个线程 12 public class TestThread3 implements Runnable { 13 14 @Override 15 public void run() { 16 //run方法线程体 17 for (int i = 0; i < 20; i++) { 18 System.out.println("我在看代码----" + i); 19 } 20 } 21 22 public static void main(String[] args) { 23 //创建runnable接口的实现类 24 TestThread3 testThread = new TestThread3(); 25 26 Thread thread = new Thread(testThread); 27 //调用start()开启线程 28 thread.start(); 29 30 //new Thread(testThread).start(); 31 for (int i = 0; i < 2000; i++) { 32 System.out.println("我在学习多线程----" + i); 33 } 34 } 35 }

案例
火车票:
1 package com.xing.demo01; 2 3 /** 4 * @program: 多线程 5 * @Date: 2022/08/14 6 * @author: 16159 7 * @description:多个线程同时操作同一个对象 买火车票案例 8 * @Modified By: 9 **/ 10 //发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱 11 public class TestThread4 implements Runnable { 12 13 //票数 14 private int ticketNums = 10; 15 16 @Override 17 public void run() { 18 while (true) { 19 if (ticketNums <= 0) { 20 break; 21 } 22 //模拟延时 23 try { 24 Thread.sleep(200); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 // 当前执行线程的名字 29 System.out.println(Thread.currentThread().getName() + "--->拿到了第" + ticketNums-- + "张票"); 30 } 31 } 32 33 public static void main(String[] args) { 34 //创建runnable接口的实现类 35 TestThread4 ticket = new TestThread4(); 36 37 //创建线程对象,通过线程对象来开启我们的线程,代理 38 new Thread(ticket, "小红").start(); 39 new Thread(ticket, "老师").start(); 40 new Thread(ticket, "黄牛1").start(); 41 new Thread(ticket, "黄牛2").start(); 42 } 43 }

有问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
龟兔赛跑:

1 package com.xing.demo01; 2 3 /** 4 * 模拟龟兔赛跑 5 */ 6 public class Race implements Runnable { 7 //胜利者 8 private static String winner; 9 10 @Override 11 public void run() { 12 //长度100m 13 for (int i = 0; i <= 100; i++) { 14 //模拟兔子休息 每10步兔子休息 15 if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) { 16 try { 17 Thread.sleep(1); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 } 22 //判断比赛是否结束 23 boolean flag = gameOver(i); 24 //如果比赛结束,停止程序 25 if (flag) { 26 break; 27 } 28 System.out.println(Thread.currentThread().getName() + "--->跑了" + i + "步"); 29 } 30 } 31 32 //判断是否完成比赛 33 private boolean gameOver(int steps) { 34 if (winner != null) { 35 return true; 36 } else { 37 if (steps >= 100) { 38 winner = Thread.currentThread().getName(); 39 System.out.println("winner is " + winner); 40 return true; 41 } 42 } 43 return false; 44 } 45 46 public static void main(String[] args) { 47 Race race = new Race(); 48 new Thread(race, "兔子").start(); 49 new Thread(race, "乌龟").start(); 50 } 51 }

2.3、实现Callable接口(了解)
实现Callable接口,需要返回值类型
重写call方法,需要抛出异常
创建目标对象
创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
提交执行:Future result1 = ser.submit(11);
获取结果:boolean r1 = result1.get()
关闭服务:ser.shutdownNow();
实现
1 package com.xing.demo02; 2 3 import org.apache.commons.io.FileUtils; 4 5 import java.io.File; 6 import java.io.IOException; 7 import java.net.URL; 8 import java.util.concurrent.*; 9 10 /** 11 * 实现Callable接口 有返回值 12 */ 13 public class TestCallable implements Callable<Boolean> { 14 15 private String url;//网络图片地址 16 private String name;//报错扥文件名 17 18 //有参构造 19 public TestCallable(String url, String name) { 20 this.url = url; 21 this.name = name; 22 } 23 24 //下载图片线程的执行体 25 public Boolean call(){ 26 WebDownloader webDownloader = new WebDownloader(); 27 webDownloader.downloader(url, name); 28 System.out.println("下载了文件名为:" + name); 29 return true; 30 } 31 32 public static void main(String[] args) throws ExecutionException, InterruptedException { 33 TestCallable c = new TestCallable("https://img-home.csdnimg.cn/images/20201124032511.png", "1.png"); 34 TestCallable c1 = new TestCallable("https://img-home.csdnimg.cn/images/20201124032511.png", "2.png"); 35 TestCallable c2 = new TestCallable("https://img-home.csdnimg.cn/images/20201124032511.png", "3.png"); 36 37 //创建执行服务 38 ExecutorService ser = Executors.newFixedThreadPool(3); 39 40 //提交执行 41 Future<Boolean> r = ser.submit(c); 42 Future<Boolean> r1 = ser.submit(c1); 43 Future<Boolean> r2 = ser.submit(c2); 44 45 //获取结果 46 boolean res = r.get(); 47 boolean res1 = r1.get(); 48 boolean res2 = r2.get(); 49 50 //关闭服务 51 ser.shutdownNow(); 52 } 53 } 54 //下载器 55 class WebDownloader { 56 //下载方法 57 public void downloader(String url, String name) { 58 try { 59 // 将一个url变成一个文件 60 FileUtils.copyURLToFile(new URL(url), new File(name)); 61 } catch (IOException e) { 62 e.printStackTrace(); 63 System.out.println("IO异常,downloader方法出现问题"); 64 } 65 } 66 }
