第5章 多线程 一、进程和线程1、进程和线程的相关概念进程Process是计算机中的程序关于某数据集合上的一次运行活动 是系统进行资源分配的基本单位 简单理解程序的执行过程1.独立性每一个进程都有自己的空间在没有经过进程本身允 许的情况下一个进程不可以直接访问其它的的进程空间 2.动态性进程是动态产生动态消亡的 3.并发性任何进程都可以同其它进程一起并发执行并行在同一时刻有多个指令在多个CPU上【同时】执行 并发在同一时刻有多个指令在单个CPU上【交替】执行线程Thread进程可以同时执行多个任务每个任务就是线程多线程的意义随着处理器上的核心数量越来越多现在大多数计算机都比以往更加擅长并行计算 一个线程在一个时刻只能运行在一个处理器核心上提高效率 可以同时处理多个任务2、Java程序默认是多线程的package com.itheima.thread; ​ public class ThreadDemo1 { /* Java程序默认是多线程的. ​ 1. main线程 2. 垃圾回收线程 */ public static void main(String[] args) { for(int i 1; i 500000; i){ new Demo();//当产生一份地址但没有交给任何变量就会被当作垃圾 } } } ​ ​ class Demo { Override protected void finalize() throws Throwable {//当前这个类的对象被识别为垃圾对象且被当作垃圾处理了 System.out.println(垃圾被清理了...); } }二、线程相关方法1、继承Thread类开启线程的第一种方式 - 继承Thread类编写一个类继承Thread类重写run方法将线程任务代码写在run方法中创建线程对象调用start方法开启线程注意事项: 调用start方法的时候, 会自动调用run方法.- 只有是调用了start()方法, 才是开启了新的线程.package com.itheima.thread; ​ public class ThreadDemo2 { /* 开启线程的第一种方式 - 继承Thread类 ​ 1. 编写一个类继承Thread类 2. 重写run方法 3. 将线程任务代码写在run方法中 4. 创建线程对象 5. 调用start方法开启线程 ​ 注意事项: 调用start方法的时候, 会自动调用run方法. ​ - 只有是调用了start()方法, 才是开启了新的线程. */ public static void main(String[] args) { // 4. 创建线程对象 MyThread mt new MyThread(); // 5. 调用start方法开启线程 mt.start(); ​ //会在控制台看到主线程和线程交替执行的现象 for (int i 1; i 500; i) { System.out.println(main线程执行任务 i); } } } ​ // 1. 编写一个类继承Thread类 class MyThread extends Thread { // 2. 重写run方法 Override public void run() { // 3. 将线程任务代码写在run方法中 for (int i 1; i 500; i) { System.out.println(线程任务执行了 i); } } }2、实现Runnable接口开启线程第二种方式 - 实现Runnable接口编写一个类实现Runnable接口重写run方法将线程任务代码, 写在run方法中创建Runnable接口的实现类对象创建线程对象, 并将Runnable接口的实现类对象传入使用线程对象调用start方法开启线程package com.itheima.thread; ​ import java.util.concurrent.FutureTask; ​ public class ThreadDemo3 { /* 开启线程第二种方式 - 实现Runnable接口 ​ 1. 编写一个类实现Runnable接口 2. 重写run方法 3. 将线程任务代码, 写在run方法中 4. 创建Runnable接口的实现类对象 5. 创建线程对象, 并将Runnable接口的实现类对象传入 6. 使用线程对象调用start方法开启线程 */ public static void main(String[] args) { // 4. 创建Runnable接口的实现类对象 MyRunnable mr new MyRunnable(); //尝试调用start方法失败因为他不是线程对象仅仅是一个封装了线程任务的对象 ​ // 5. 创建线程对象, 并将Runnable接口的实现类对象传入 //自己可以开启多条线程 Thread t1 new Thread(mr); Thread t2 new Thread(mr); // 6. 使用线程对象调用start方法开启线程 t1.start(); t2.start(); ​ ​ } } ​ // 1. 编写一个类实现Runnable接口 //第一种实现方式是继承Thread第二种实现方式是实现Runnable接口 //因为只能单继承可以多实现所以第二种使用更广泛 class MyRunnable implements Runnable { // 2. 重写run方法 Override public void run() { // 3. 将线程任务代码, 写在run方法中 for (int i 1; i 500; i) { System.out.println(自己的线程任务 i); } } }3、实现Callable接口需要有结果返回package com.itheima.thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class ThreadDemo4 { /* 开启线程的第三种方式 - 实现 Callable 接口 1. 编写一个类实现Callable接口 2. 重写call方法 (此方法存在返回值) 3. 将线程任务代码写在call方法中 4. 创建线程资源对象 5. 创建线程任务对象, 封装线程资源 6. 创建线程对象, 传入线程任务 7. 使用线程对象调用start开启线程 */ public static void main(String[] args) throws ExecutionException, InterruptedException { // 4. 创建线程资源对象 MyCallable mc new MyCallable(); // 5. 创建线程任务对象, 封装线程资源 FutureTaskInteger task1 new FutureTask(mc); FutureTaskInteger task2 new FutureTask(mc); // 6. 创建线程对象, 传入线程任务 Thread t1 new Thread(task1); Thread t2 new Thread(task2); // 7. 使用线程对象调用start开启线程 t1.start(); t2.start(); //通过task的get方法来接收线程的返回值 Integer result1 task1.get(); System.out.println(task1 返回的结果为: result1); Integer result2 task2.get(); System.out.println(task2 返回的结果为: result2); } } // 1. 编写一个类实现Callable接口 class MyCallable implements CallableInteger { // 2. 重写call方法 (此方法存在返回值) Override public Integer call() throws Exception { // 3. 将线程任务代码写在call方法中 int sum 0; for (int i 1; i 100; i) { sum i; } return sum; } }4、Thread类的常见方法1线程设置名字和获取名字Thread类的方法:public String getName() : 获取线程名字 public void setName() : 设置线程名字 public static Thread currentThread() : 获取当前线程的对象继承Thread类set、get线程名package com.itheima.thread_method; ​ public class ThreadNameDemo1 { /* 线程设置名字和获取名字 ​ Thread类的方法: ​ public String getName() : 获取线程名字 public void setName() : 设置线程名字 public static Thread currentThread() : 获取当前线程的对象 ​ */ public static void main(String[] args) { MyThread mt1 new MyThread(线程A: ); MyThread mt2 new MyThread(线程B: ); ​ // mt1.setName(线程A: ); // mt2.setName(线程B: ); ​ mt1.start(); mt2.start(); } } ​ class MyThread extends Thread { ​ public MyThread() {//重写空参带参构造 } ​ public MyThread(String name) { super(name); } ​ Override public void run() { for (int i 1; i 200; i) { //因为父类是Thread所以可以用父类来getName System.out.println(super.getName() 线程任务执行了 i); } } }Runnable接口set、get线程名package com.itheima.thread_method; ​ public class ThreadNameDemo2 { /* 线程设置名字和获取名字 ​ Thread类的方法: ​ public String getName() : 获取线程名字 public void setName() : 设置线程名字 public static Thread currentThread() : 获取当前线程的对象 ​ */ public static void main(String[] args) { MyRunnable mr new MyRunnable(); Thread t new Thread(mr, 线程A);//传入线程对象并且指定名称 t.start(); ​ for (int i 1; i 200; i) { System.out.println(Thread.currentThread().getName() 线程执行了 i); } } } ​ class MyRunnable implements Runnable { Override public void run() { for (int i 1; i 200; i) { //因为父类不是Thread是Object所以不能用super.getName System.out.println(Thread.currentThread().getName() 线程任务执行了 i); } } }Callable接口set、get线程名package com.itheima.thread_method; ​ import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; ​ public class ThreadNameDemo3 { /* 线程设置名字和获取名字 ​ Thread类的方法: ​ public String getName() : 获取线程名字 public void setName() : 设置线程名字 public static Thread currentThread() : 获取当前线程的对象 ​ */ public static void main(String[] args) throws Exception { MyCallable mc new MyCallable(); ​ FutureTaskInteger task1 new FutureTask(mc); FutureTaskInteger task2 new FutureTask(mc); ​ Thread t1 new Thread(task1, 线程A: ); Thread t2 new Thread(task2, 线程B: ); ​ t1.start(); t2.start(); ​ Integer result1 task1.get(); Integer result2 task2.get(); ​ //用线程对象获取名字 System.out.println(t1.getName() 获取到的结果为: result1); System.out.println(t2.getName() 获取到的结果为: result2); ​ } } ​ class MyCallable implements CallableInteger { Override public Integer call() throws Exception { int sum 0; for (int i 1; i 100; i) { sum i; } return sum; } }线程休眠方法package com.itheima.thread_method; ​ public class ThreadMethodDemo1 { /* 休眠线程的方法 ​ public static void sleep(long time) : 让线程休眠指定的时间单位为毫秒 */ public static void main(String[] args) throws InterruptedException { for(int i 5; i 1; i--){ System.out.println(倒计时: i 秒); // 项目经理要求此处代码运行缓慢, 让甲方加钱优化. Thread.sleep(1000); } } }5、Thread类的常见方法2线程调度方式抢占式调度、非抢占式调度线程优先级1~10默认为5越大优先级越高线程优先级越高抢占到CPU的概率也就越高并不是必定抢到CPU的线程优先级的方法public setPriority(int newPriority) : 设置线程优先级 public final int getPriority() : 获取线程优先级package com.itheima.thread_method; ​ public class ThreadMethodDemo2 { /* 线程优先级的方法: ​ public setPriority(int newPriority) : 设置线程优先级 public final int getPriority() : 获取线程优先级 */ public static void main(String[] args) { Thread t1 new Thread(new Runnable() { Override public void run() { for (int i 1; i 200; i) { System.out.println(Thread.currentThread().getName() --- 线程任务执行 i); } } }, 线程A: ); ​ Thread t2 new Thread(new Runnable() { Override public void run() { for (int i 1; i 200; i) { System.out.println(Thread.currentThread().getName() --- 线程任务执行 i); } } }, 线程B: ); ​ t1.setPriority(1); t2.setPriority(10); ​ // System.out.println(t1.getPriority()); // System.out.println(t2.getPriority()); ​ t1.start(); t2.start(); } }守护线程package com.itheima.thread_method; ​ public class ThreadMethodDemo3 { /* public final void setDaemon(boolean on) : 设置为守护线程 */ public static void main(String[] args) { Thread t1 new Thread(new Runnable(){ Override public void run() { for(int i 1; i 20; i){ System.out.println(Thread.currentThread().getName() --- 任务执行了 i); } } }, 线程A); ​ Thread t2 new Thread(new Runnable(){ Override public void run() { for(int i 1; i 200; i){ System.out.println(Thread.currentThread().getName() --- 任务执行了 i); } } }, 线程B); ​ t2.setDaemon(true);//当其他线程结束时线程B也会结束 ​ t1.start(); t2.start(); } }三、线程安全和同步package com.itheima.test; ​ import java.util.ArrayList; import java.util.List; ​ public class ThreadTest1 { /* 需求某电影院目前正在上映国产大片共有100张票而它有3个窗口卖票请设计一个程序模拟该电影院卖票 - 多条线程共享操作同一份资源 */ public static void main(String[] args) { TicketTask task new TicketTask();//一个锁对象 /*List String threads new ArrayList(); threads.add(窗口A); threads.add(窗口B); threads.add(窗口C); threads.add(窗口D); System.out.println(threads);*/ ​ ​ Thread t1 new Thread(task, 窗口A); Thread t2 new Thread(task, 窗口B); Thread t3 new Thread(task, 窗口C); ​ t1.start(); t2.start(); t3.start(); } } ​ class TicketTask implements Runnable { ​ int tickets 1000; ​ Override public void run() { while (true) { synchronized (TicketTask.class) {//用字节码对象当锁对象 if (tickets 0) { break; } ​ System.out.println(Thread.currentThread().getName() 卖出了第 tickets 号票); tickets--; } } } }1、同步方法方法分为静态和非静态 静态方法的锁对象是字节码对象非静态方法的锁对象是thispackage com.itheima.test; ​ public class ThreadTest2 { /* 同步方法: 在方法的返回值类型前面加入 synchronized 关键字 ​ 方法分为静态和非静态 静态方法的锁对象是字节码对象非静态方法的锁对象是 this */ public static void main(String[] args) { TicketTask2 task new TicketTask2(); ​ Thread t1 new Thread(task, 窗口A); Thread t2 new Thread(task, 窗口B); ​ t1.start(); t2.start(); } } ​ class TicketTask2 implements Runnable { ​ static int tickets 1000; ​ Override public void run() { while (true) { ​ String name Thread.currentThread().getName(); ​ if (窗口A.equals(name)) { if (method()) { break; } } else if (窗口B.equals(name)) { synchronized (TicketTask2.class) { if (tickets 0) { break; } System.out.println(Thread.currentThread().getName() 卖出了第 tickets 号票); tickets--; } } ​ ​ } } ​ private static synchronized boolean method() { if (tickets 0) { return true; } System.out.println(Thread.currentThread().getName() 卖出了第 tickets 号票); tickets--; return false; } }2、Lock锁package com.itheima.test; ​ import java.util.concurrent.locks.ReentrantLock; ​ public class ThreadTest3 { /* 使用 Lock 锁, 我们可以更清晰的看到哪里加了锁哪里释放了锁 Lock 是接口, 无法直接创建对象, 需要创建 ReentrantLock */ public static void main(String[] args) { TicketTask3 task new TicketTask3(); ​ Thread t1 new Thread(task, 窗口A); Thread t2 new Thread(task, 窗口B); ​ t1.start(); t2.start(); } } ​ class TicketTask3 implements Runnable { ​ int tickets 1000; ReentrantLock lock new ReentrantLock(); ​ Override public void run() { while (true) { try { lock.lock(); if (tickets 0) { break; } System.out.println(Thread.currentThread().getName() 卖出了第 tickets 号票); tickets--; } finally {//保证锁对象一定会被释放 lock.unlock(); } } ​ } ​ }四、线程池介绍线程池介绍 系统创建一个线程的成本是比较高的因为它涉及到与操作系统交互 当程序中需要创建大量生存期很短暂的线程时频繁的创建和销毁线程就会严重浪费系统资源将线程对象 交给线程池维护 可以降低系统成本 从而提升程序的性能 【强制】线程资源必须通过线程池提供不允许在应用中自行显式创建线程。 说明线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销解决资源不足的问题。 如果不使用线程池有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。【强制】线程池不允许使用Executors去创建而是通过ThreadPoolExecutor的方式这样的处理方式让写的同学更加明确线程池的运行规则规避资源耗尽的风险。 说明Executors返回的线程池对象的弊端如下1FixedThreadPool 和 SingleThreadPool : 允许的请求队列长度为Integer.MAXVALUE可能会堆积大量的请求从而导致OOM。2CachedThreadPool : 允许的创建线程数量为Integer.MAXVALUE可能会创建大量的线程从而导致OOM。五、JDK提供的线程池JDK自带的线程池 (了解)Executors 中提供静态方法来创建线程池 ​ 1. static ExecutorService newCachedThreadPool() 创建一个默认的线程池 2. static newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池package com.itheima.pool; ​ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; ​ public class ThreadPoolDemo1 { /* JDK自带的线程池 (了解) ​ Executors 中提供静态方法来创建线程池 ​ 1. static ExecutorService newCachedThreadPool() 创建一个默认的线程池 2. static newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池 */ public static void main(String[] args) { // 获取线程池对象 ExecutorService pool Executors.newFixedThreadPool(10); ​ // 提交线程任务到线程池 for (int i 1; i 100; i) { pool.submit(new Runnable() { Override public void run() { System.out.println(Thread.currentThread().getName() 提交了线程任务); } }); } ​ pool.shutdown(); } }六、自定义线程池临时线程什么时候创建 线程任务数核心线程数任务队列的数量 什么时候会开启拒绝策略线程任务数最大线程数任务队列的数量ThreadPoolExecutor(int corePoolsize,int maximumPoolsize, long keepAliveTime,TimeUnit unit, BlockingQueueRunnable workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) /* 参数1 核心线程数量正式员工不能小于0 参数2 最大线程数量正式员工临时工不能小于等于0最大数量核心线程数量 参数3 空闲时间不能小于0 参数4 时间单位 参数5 任务队列指定排队人数不能为null 参数6 线程对象工厂不能为null 参数7 拒绝策略不能为null */拒绝策略最常用拒绝策略 ThreadPoolExecutor.AbortPolicy 丢弃任务并抛出RejectedExecutionException异常默认 ThreadPoolExecutor.DiscardPolicy 丢弃任务但是不抛出异常这是不推荐的做法 ThreadPoolExecutor.DiscardoldestPolicy 抛弃队列中等待最久的任务然后把当前任务加入队列中 ThreadPoolExecutor.CallerRunsPolicy 调用任务的run()方法主方法绕过线程池直接执行自定义线程池package com.itheima.pool; ​ import java.util.concurrent.*; ​ public class ThreadPoolDemo2 { /* 自定义线程池 ​ 1. 有界队列 new ArrayBlockingQueue(10) 2. 无界队列 new LinkedBlockingQueue() */ public static void main(String[] args) { ThreadPoolExecutor pool new ThreadPoolExecutor( 2, 5, 60, TimeUnit.SECONDS, new ArrayBlockingQueue(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); ​ for(int i 1; i 16; i){//线程数量核心线程数量任务队列数量时开辟临时线程 pool.submit(new Runnable() { Override public void run() { System.out.println(Thread.currentThread().getName() --- 提交了任务); } }); } ​ } }