概述
- Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
- 多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。
- 这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
- 多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
大白话:
- 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程式CPU调度和执行的单位
- 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
- 而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位
- 注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉
知识点:
- 线程就是独立的执行路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程main(),gc()垃圾回收线程;
- main()称之为主线程,为系统的入口,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排执行,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
- 线程会带来额外的开销,如cpu调度事件,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
一个线程的生命周期
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
下图显示了一个线程完整的生命周期。
- 新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
- 就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
- 运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
- 阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
- 终止状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
线程的优先级
- 每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
- 线程开启不一定立即执行,由cpu调度
- Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
- 默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
- 具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
- 在下面的线程状态节点具体讲述
创建一个线程
Java 提供了三种创建线程的方法:
- 通过继承 Thread 类本身;(重点)
- 通过实现 Runnable 接口;(重点)
- 通过 Callable 和 Future 创建线程;(重点)
继承 Thread 类创建线程
- 创建一个线程的第一种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。
- 继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。
- 该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。
1 //创建线程方式一:继承Thread类,重写run()方法,调用start开启线程 2 public class TestThread01 extends Thread { 3 @Override 4 public void run() { 5 //run方法线程体 6 for (int i = 0; i < 3; i++) { 7 System.out.println("好好学习"); 8 } 9 } 10 11 public static void main(String[] args) { 12 //main线程,主线程 13 14 //创建一个线程对象 15 TestThread01 testThread01 = new TestThread01(); 16 17 //testThread01.run();执行run方法,没有开启线程,依旧是自上而下执行代码 18 19 //调用start()方法,开启线程,run方法体线程和main()主线程根据调度交叉执行 20 testThread01.start(); 21 for (int i = 0; i <3 ; i++) { 22 System.out.println("天天向上"); 23 } 24 } 25 }Thread线程练习-网图下载
前置条件:
- 下载Commons IO包,下载地址:http://commons.apache.org/proper/commons-io/download_io.cgi
- 下载好之后放到项目的lib目录下,没有lib目录的,自己新增一个。
- 右键lib目录,选择Add as Library,点击OK即可
1 //练习Thread,实现多线程下载图片 2 public class TestThread2 extends Thread{ 3 private String url; 4 private String name; 5 6 public TestThread2(String url,String name){ 7 this.url=url; 8 this.name=name; 9 } 10 11 //下载图片线程的执行体 12 @Override 13 public void run() { 14 WebDownloader webDownloader = new WebDownloader(); 15 webDownloader.downloader(url,name); 16 System.out.println("下载了文件名为:"+name); 17 } 18 19 public static void main(String[] args) { 20 TestThread2 t1 = new TestThread2("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-1.jpg"); 21 TestThread2 t2 = new TestThread2("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-2.jpg"); 22 TestThread2 t3 = new TestThread2("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-3.jpg"); 23 24 t1.start(); 25 t2.start(); 26 t3.start(); 27 } 28 } 29 30 //下载器 31 class WebDownloader{ 32 //下载方法 33 public void downloader(String url,String name){ 34 try { 35 FileUtils.copyURLToFile(new URL(url),new File(name)); 36 } catch (IOException e) { 37 e.printStackTrace(); 38 System.out.println("IO异常,download方法出现问题"); 39 } 40 } 41 }
实现 Runnable 接口创建线程
创建一个线程,最简单的方法是创建一个实现 Runnable 接口的类。
为了实现 Runnable,一个类只需要执行一个方法调用 run(),声明如下:
public void run()可以重写该方法,重要的是理解的 run() 可以调用其他方法,使用其他类,并声明变量,就像主线程一样。
在创建一个实现 Runnable 接口的类之后,你可以在类中实例化一个线程对象。
Thread 定义了几个构造方法,下面的这个是我们经常使用的:
Thread(Runnable threadOb,String threadName);这里,threadOb 是一个实现 Runnable 接口的类的实例,并且 threadName 指定新线程的名字。
新线程创建之后,你调用它的 start() 方法它才会运行。
void start();
1 //创建线程方式二:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法启动线程 2 public class TestThread03 implements Runnable{ 3 @Override 4 public void run() { 5 for (int i = 0; i < 200; i++) { 6 System.out.println("好好学习"+i); 7 } 8 } 9 10 public static void main(String[] args) { 11 //创建runnable接口的实现类对象 12 TestThread03 testThread03 = new TestThread03(); 13 //创建线程对象,通过线程对象来开启我们的线程,代理 14 new Thread(testThread03).start(); 15 16 for (int i = 0; i < 200; i++) { 17 System.out.println("天天向上"+i); 18 } 19 } 20 }Runnable 接口创建线程练习-下载网图
1 public class TestThread04 implements Runnable{ 2 3 private String url; 4 private String name; 5 6 public TestThread04(String url,String name){ 7 this.url=url; 8 this.name= name; 9 } 10 11 @Override 12 public void run() { 13 new WebDownLoad01(url,name); 14 System.out.println("下载了文件名:"+name); 15 } 16 17 public static void main(String[] args) { 18 TestThread04 t1 = new TestThread04("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-1.jpg"); 19 TestThread04 t2 = new TestThread04("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-2.jpg"); 20 TestThread04 t3 = new TestThread04("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-3.jpg"); 21 22 new Thread(t1).start(); 23 new Thread(t2).start(); 24 new Thread(t3).start(); 25 } 26 } 27 //下载器 28 class WebDownLoad01{ 29 public WebDownLoad01(String url,String name){ 30 try { 31 FileUtils.copyURLToFile(new URL(url),new File(name)); 32 } catch (IOException e) { 33 e.printStackTrace(); 34 System.out.println("IO异常,下载失败"); 35 } 36 } 37 }Runnable 接口创建线程练习-多个线程同时操作同一个对象
买火车票的例子
1 //多个线程同时操作同一个对象 2 //买火车票的例子 3 public class TestThread05 implements Runnable{ 4 5 //站台总票数 6 private int ticketNums=10; 7 8 @Override 9 public void run() { 10 while (true){ 11 //没有票时退出 12 if(ticketNums<=0){ 13 break; 14 } 15 16 //模拟延时 17 try { 18 Thread.sleep(200); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 23 System.out.println(Thread.currentThread().getName()+"-->拿到了第:"+ticketNums--+"票"); //Thread.currentThread().getName()获得当前执行线程的名字 24 } 25 } 26 27 public static void main(String[] args) { 28 TestThread05 testThread05 = new TestThread05(); 29 30 new Thread(testThread05,"小明").start();//小明:线程的名字 31 new Thread(testThread05,"小张").start(); 32 new Thread(testThread05,"黄牛").start(); 33 } 34 }输出结果,发现同一张票被两个人买走了,发现了线程的问题:多个线程操作同一个资源的时候,线程不安全,数据紊乱,后面线程几个重要概念中的线程安全会有具体讲述
Runnable 接口创建线程练习-龟兔赛跑
1 //模拟龟兔赛跑 2 public class Race implements Runnable{ 3 4 // 胜利者 5 private static String winner; 6 7 //线程运行体 8 @Override 9 public void run() { 10 for (int i = 0; i <= 100; i++) { 11 //判断比赛是否结束 12 boolean b = gameOver(i); 13 //如果有胜利者了,就停止程序 14 if (b){ 15 break; 16 } 17 System.out.println(Thread.currentThread().getName()+"跑了"+i+"米"); 18 } 19 } 20 21 //判断是否完成比赛 22 private boolean gameOver(int steps){ 23 //判断是否有胜利者 24 if (winner!=null){ 25 return true; 26 }{ 27 if (steps>=100) { 28 winner=Thread.currentThread().getName(); 29 System.out.println("winner is :"+winner); 30 return true; 31 } 32 } 33 return false; 34 } 35 36 public static void main(String[] args) { 37 Race race = new Race(); 38 39 new Thread(race,"乌龟").start(); 40 new Thread(race,"兔子").start(); 41 } 42 }
实现Callable接口创建线程
了解即可
- 实现Callable接口,需要返回值类型;
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务ExecutorService ser = Executors.newFixedThreadPool();
- 提交执行Future<Boolean> r1 = ser.submit(t1);
- 获得结果Boolean rs1 = r1.get();
- 关闭服务ser.shutdown();
1 //线程创建方式三:实现Callable接口 2 public class TestCallable implements Callable { 3 private String url; 4 private String name; 5 6 public TestCallable(String url,String name){ 7 this.url=url; 8 this.name=name; 9 } 10 11 @Override 12 public Object call() throws Exception { 13 WebDownload webDownload = new WebDownload(url,name); 14 System.out.println(name+"文件已下载"); 15 return true; 16 } 17 18 public static void main(String[] args) throws ExecutionException, InterruptedException { 19 TestCallable t1 = new TestCallable("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-1.jpg"); 20 TestCallable t2 = new TestCallable("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-2.jpg"); 21 TestCallable t3 = new TestCallable("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-3.jpg"); 22 23 //创建执行服务 24 ExecutorService ser = Executors.newFixedThreadPool(3); 25 26 //提交执行 27 Future<Boolean> r1 = ser.submit(t1); 28 Future<Boolean> r2 = ser.submit(t2); 29 Future<Boolean> r3 = ser.submit(t3); 30 31 //获取结果 32 Boolean rs1 = r1.get(); 33 Boolean rs2 = r2.get(); 34 Boolean rs3 = r3.get(); 35 36 //关闭服务 37 ser.shutdown(); 38 } 39 } 40 41 //下载器 42 class WebDownload{ 43 public WebDownload(String url,String name){ 44 try { 45 FileUtils.copyURLToFile(new URL(url),new File(name)); 46 } catch (IOException e) { 47 e.printStackTrace(); 48 System.out.println("IO异常,下载失败"); 49 } 50 } 51 }
创建线程的三种方式的对比
采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
拓展
静态代理
- 真实对象和代理对象都要实现同一个接口
- 代理对象要代理真实角色
- #好处
- 代理对象可以做很多真实对象做不了的事情
- 真实对象专注做自己的事情
1 //以结婚找婚庆公司举例 2 public class StaticProxy { 3 public static void main(String[] args) { 4 WeddingCompany weddingCompany = new WeddingCompany(new You()); 5 weddingCompany.HappyMarry(); 6 } 7 } 8 9 //同一个接口———结婚这件事情 10 interface Marry{ 11 void HappyMarry(); 12 } 13 14 //真实对象————结婚的主人公 15 class You implements Marry{ 16 @Override 17 public void HappyMarry() { 18 System.out.println("张三要结婚了"); 19 } 20 } 21 22 //代理对象————结婚场景布置找的婚庆公司,代理角色 23 class WeddingCompany implements Marry{ 24 25 //代理的主人公,结婚的主人公,给谁帮忙的 26 private Marry target; 27 28 public WeddingCompany(Marry target){ 29 this.target= target; 30 } 31 32 @Override 33 public void HappyMarry() { 34 before(); 35 this.target.HappyMarry(); 36 after(); 37 } 38 39 private void before() { 40 System.out.println("结婚之前布置现场"); 41 } 42 43 private void after() { 44 System.out.println("结婚之后收尾款"); 45 } 46 47 48 }
- 反观线程Thread和Runable,Thread类其实是实现Runable接口的,Thread类相当于就是一个静态代理,完成Runable许多完成不了的事情,比如使用Runable创建线程,最后运行线程使用的是代理Thread的start方法;Runnabe接口启动线程就是是用了静态代理的方式
- Runnable接口和Thread代理都有run方法,最后调用的是Thread的start方法,但实际执行的还是Runnable中的run方法中的方法体
Lambda表达式
好处:
- 避免匿名内部类定义过多
- 其实质属于函数式编程的概念
- 例子:new Thread(()-> System.out.println("多线程学习")).start();
函数式接口:
- 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口,比如
- 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
推导lambda表达式
正常写法
1 /* 2 推导lambda表达式--正常写法 3 */ 4 public class TestLambda01 { 5 public static void main(String[] args) { 6 ILike like = new Like(); 7 like.lambda(); 8 } 9 } 10 11 //1.定义一个函数式接口 12 interface ILike{ 13 void lambda(); 14 } 15 16 //2.实现类 17 class Like implements ILike{ 18 @Override 19 public void lambda() { 20 System.out.println("i like lambda"); 21 } 22 }静态内部类
1 /* 2 推导lambda表达式--优化方法:静态内部类 3 */ 4 public class TestLambda01 { 5 6 public static void main(String[] args) { 7 ILike like = new Like(); 8 like.lambda(); 9 10 like = new Like1(); 11 like.lambda(); 12 } 13 14 //3.静态内部类 15 static class Like1 implements ILike{ 16 @Override 17 public void lambda() { 18 System.out.println("i like lambda1-静态内部类"); 19 } 20 } 21 } 22 23 //1.定义一个函数式接口 24 interface ILike{ 25 void lambda(); 26 } 27 28 //2.实现类 29 class Like implements ILike{ 30 @Override 31 public void lambda() { 32 System.out.println("i like lambda"); 33 } 34 }局部内部类
1 /* 2 推导lambda表达式--优化方法:局部内部类 3 */ 4 public class TestLambda01 { 5 6 public static void main(String[] args) { 7 ILike like = new Like(); 8 like.lambda(); 9 10 //3.静态内部类 11 like = new Like1(); 12 like.lambda(); 13 14 //4.局部内部类 15 class Like2 implements ILike{ 16 @Override 17 public void lambda() { 18 System.out.println("i like lambda2-局部内部类"); 19 } 20 } 21 22 //4.局部内部类 23 like = new Like2(); 24 like.lambda(); 25 } 26 27 //3.静态内部类 28 static class Like1 implements ILike{ 29 @Override 30 public void lambda() { 31 System.out.println("i like lambda1-静态内部类"); 32 } 33 } 34 } 35 36 //1.定义一个函数式接口 37 interface ILike{ 38 void lambda(); 39 } 40 41 //2.实现类 42 class Like implements ILike{ 43 @Override 44 public void lambda() { 45 System.out.println("i like lambda"); 46 } 47 }匿名内部类
1 /* 2 推导lambda表达式--优化方法:匿名内部类 3 */ 4 public class TestLambda01 { 5 6 public static void main(String[] args) { 7 ILike like = new Like(); 8 like.lambda(); 9 10 //3.静态内部类 11 like = new Like1(); 12 like.lambda(); 13 14 //4.局部内部类 15 class Like2 implements ILike{ 16 @Override 17 public void lambda() { 18 System.out.println("i like lambda2-局部内部类"); 19 } 20 } 21 22 //4.局部内部类 23 like = new Like2(); 24 like.lambda(); 25 26 //5.匿名内部类,没有类的名称,必须借助接口或者父类 27 like = new ILike() { 28 @Override 29 public void lambda() { 30 System.out.println("i like lambda3-匿名内部类"); 31 } 32 }; 33 //5.匿名内部类 34 like.lambda(); 35 } 36 37 //3.静态内部类 38 static class Like1 implements ILike{ 39 @Override 40 public void lambda() { 41 System.out.println("i like lambda1-静态内部类"); 42 } 43 } 44 } 45 46 //1.定义一个函数式接口 47 interface ILike{ 48 void lambda(); 49 } 50 51 //2.实现类 52 class Like implements ILike{ 53 @Override 54 public void lambda() { 55 System.out.println("i like lambda"); 56 } 57 }思考:怎么还能将它再简化呢,于是乎,JDK1.8出了个lambda表达式
1 /* 2 推导lambda表达式--优化方法:lambda表达式 3 */ 4 public class TestLambda01 { 5 6 public static void main(String[] args) { 7 ILike like = new Like(); 8 like.lambda(); 9 10 //3.静态内部类 11 like = new Like1(); 12 like.lambda(); 13 14 //4.局部内部类 15 class Like2 implements ILike{ 16 @Override 17 public void lambda() { 18 System.out.println("i like lambda2-局部内部类"); 19 } 20 } 21 22 //4.局部内部类 23 like = new Like2(); 24 like.lambda(); 25 26 //5.匿名内部类,没有类的名称,必须借助接口或者父类 27 like = new ILike() { 28 @Override 29 public void lambda() { 30 System.out.println("i like lambda3-匿名内部类"); 31 } 32 }; 33 //5.匿名内部类 34 like.lambda(); 35 36 //6.lambda简化 37 like = ()->{ 38 System.out.println("i like lambda4-lambda简化"); 39 }; 40 //6.lambda简化 41 like.lambda(); 42 } 43 44 //3.静态内部类 45 static class Like1 implements ILike{ 46 @Override 47 public void lambda() { 48 System.out.println("i like lambda1-静态内部类"); 49 } 50 } 51 } 52 53 //1.定义一个函数式接口 54 interface ILike{ 55 void lambda(); 56 } 57 58 //2.实现类 59 class Like implements ILike{ 60 @Override 61 public void lambda() { 62 System.out.println("i like lambda"); 63 } 64 }lambda例子
1 //例子-带参数的lambda 2 public class TestLambda02 { 3 public static void main(String[] args) { 4 5 LunchEat lunchEat = (String name)->{ 6 System.out.println("中午吃"+name); 7 }; 8 9 lunchEat.eat("牛肉"); 10 } 11 } 12 13 interface LunchEat { 14 void eat(String name); 15 }疑问:上述的lambda还能不能再简化?可以,请看下面代码