Java
基础知识
01概述
02变量
03运算符
04程序控制语句
05数组
06面向对象编程
07代码练习
08重载
09作用域
10构造方法&构造器
11this
12包
13修饰符
14封装
15继承
16super
17覆盖&重写
18多态
19零钱通项目
20类变量&类方法
21抽象类
22接口
23内部类
24枚举
25泛型
26常用API
27lambda表达式
28正则表达式
29异常
30File&IO流
31日志技术
32多线程
33网络编程
-
+
首页
32多线程
## 概述(thread) 线程是程序内部的一条执行流程,如果只有一条执行流程,那么就是单线程的程序 多线程指的是从软硬件上实现的多条执行流程的技术(多线程由CPU负责调度执行) ## 创建多线程 Java是通过java.lang.Thread类的对象来表示线程 > 方式一 继承Thread类 - 定义一个子类MyThread继承类Java.lang.Thread,重写run方法 - 创建MyThread类的对象 - 调用线程对象的start()方法启动线程(启动后还是执行run方法) **优缺点** - 优点:编码简单 - 缺点:线程类已经继承了Thread,无法继承其他类,不利于功能的扩展 **注意事项** - 启动线程必须是调用start方法,不是调用run方法 - 不要把主线程任务放在启动子线程之前 - 直接调用run方法会当成普通方法执行,此时相当于还是单线程执行 - 只有调用start方法时,才是启动一个新的线程执行 **demo** ```java package com.xbxaq.thread_; public class Test { public static void main(String[] args) { Thread thread = new MyThread(); thread.start(); for (int i = 0; i < 5; i++) { System.out.println("MainThread Output:" + i); } } } package com.xbxaq.thread_; public class MyThread extends Thread{ //必须重写run方法 @Override public void run() { //描述线程执行的任务 for (int i = 0; i < 5; i++) { System.out.println("MyThread Output:" + i); } } } ```  > 方式二 实现Runnable接口 - 定义一个线程任务类MyRunnable实现Runnable接口 - 创建MyRunnable任务给对象 - 将MyRunnable任务对象交给Thread进行处理 - 调用线程对象的start()方法启动线程 | **Thread**类提供的构造器 | **说明** | | ------------------------------ | ---------------------------- | | public Thread(Runnable target) | 封装Runnable对象成为线程对象 | **优缺点** - 优点:任务类只是实现了接口,可以继续继承其他类i、实现其他接口,扩展性强 - 缺点:需要多一个Runnable对象 **demo** ```java package com.xbxaq.thread_; public class Test { public static void main(String[] args) { Runnable target= new MyRunnable(); new Thread(target).start(); for (int i = 0; i < 5; i++) { System.out.println("MainRunnable Output:" + i); } } } package com.xbxaq.thread_; public class MyRunnable implements Runnable{ //必须重写run方法 @Override public void run() { //描述线程执行的任务 for (int i = 0; i < 5; i++) { System.out.println("MyRunnable Output:" + i); } } } ```  > 方式三 实现Callable接口 以上两种方式重写的run方法均不能直接返回结果 JDK 5.0提供了Callable接口和FutureTask类来实现,这种方式最大的优点:可以返回线程执行完毕后的结果 **创建步骤** - 创建任务对象,定义一个类实现callable接口,重写call方法,封装要做的事情和要返回的数据。将callable类型的对象封装成FutureTask(线程任务对象) - 将线程任务对象交给thread对象 - 调用thread对象的start方法启动线程 - 线程执行完毕之后,通过futureTask对象的get方法去获取线程任务执行的结果 **FutureTask的API** | **FutureTask提供的构造器** | **说明** | | ---------------------------------- | ------------------------------------ | | public FutureTask<>(Callable call) | 把Callable对象封装成FutureTask对象。 | | **FutureTask提供的方法** | **说明** | | ------------------------------- | -------------------------------- | | public V get() throws Exception | 获取线程执行call方法返回的结果。 | **优缺点** - 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强,可以在线程执行完毕之后获取线程执行的结果 - 缺点:编码复杂 **demo** ```java package com.xbxaq.thread_; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class Test { public static void main(String[] args) throws Exception { // 3、创建一个Callable的对象 Callable<String> call = new MyCallable(100); // 4、把Callable的对象封装成一个FutureTask对象(任务对象) // 未来任务对象的作用? // 1、是一个任务对象,实现了Runnable对象. // 2、可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后的结果。 FutureTask<String> f1 = new FutureTask<>(call); // 5、把任务对象交给一个Thread对象 new Thread(f1).start(); Callable<String> call2 = new MyCallable(200); FutureTask<String> f2 = new FutureTask<>(call2); new Thread(f2).start(); // 6、获取线程执行完毕后返回的结果。 // 注意:如果执行到这儿,假如上面的线程还没有执行完毕 // 这里的代码会暂停,等待上面线程执行完毕后才会获取结果。 String rs = f1.get(); System.out.println(rs); String rs2 = f2.get(); System.out.println(rs2); } } package com.xbxaq.thread_; import java.util.concurrent.Callable; public class MyCallable implements Callable<String> { private int n; public MyCallable(int n) { this.n = n; } // 2、重写call方法 @Override public String call() throws Exception { // 描述线程的任务,返回线程执行返回后的结果。 // 需求:求1-n的和返回。 int sum = 0; for (int i = 1; i <= n; i++) { sum += i; } return "线程求出了1-" + n + "的和是:" + sum; } } ```  > 三种方法对比 | 方式 | 优点 | 缺点 | | ---------------- | ------------------------------------------------------------ | ------------------------------------------------------ | | 继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 扩展性较差,不能再继承其他的类,不能返回线程执行的结果 | | 实现Runnable接口 | 扩展性强,实现该接口的同时还可以继承其他的类。 | 编程相对复杂,不能返回线程执行的结果 | | 实现Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类。可以得到线程执行的结果 | 编程相对复杂 | ## 常用方法 | **Thread提供的常用方法** | **说明** | | --------------------------------------------- | --------------------------------------------- | | public void run() | 线程的任务方法 | | public void start() | 启动线程 | | public String **getName()** | 获取当前线程的名称,线程名称默认是Thread-索引 | | public void **setName(String name)** | 为线程设置名称 | | public **static** Thread **currentThread()** | 获取当前执行的线程对象 | | public **static** void **sleep(long time)** | 让当前执行的线程休眠多少毫秒后,再继续执行 | | public final void join()... | 让调用当前这个方法的线程先执行完! | | **Thread提供的常见构造器** | **说明** | | -------------------------------------------- | -------------------------------------------- | | public Thread(String name) | 可以为当前线程指定名称 | | public Thread(Runnable target) | 封装Runnable对象成为线程对象 | | public Thread(Runnable target, String name) | 封装Runnable对象成为线程对象,并指定线程名称 | Thread类还提供了诸如:yield、interrupt、守护线程、线程优先级等线程的控制方法,在开发中很少使用,这些方法会后续需要用到的时候再讲解。 ## 线程安全 - 当多个线程同时操作同一个资源的时候,可能会出现业务安全问题 > 线程安全问题的原因 - 存在多个线程在同时执行 - 同时访问一个共享资源 - 存在修改该共享资源 > 业务场景 - 小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,如果小明和小红同时来取钱,并且2人各自都在取钱10万元,可能会出现什么问题呢?  > 场景分析 - 需要提供一个账户类,接着创建一个账户对象代表两个人的共享账户 - 需要定义一个线程类(用于创建两个线程,分别代表小明和小红) - 创建两个线程,传入同一个账户对象给两个线程处理 - 启动两个线程,同时去同一个账户对象进去取钱10w **demo** test ```java package com.xbxaq.thread_; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class Test { public static void main(String[] args) throws Exception { Account acc = new Account("ICBC-110", 100000); new DrawThread(acc, "小明").start(); // 小明 new DrawThread(acc, "小红").start(); // 小红 } } ``` account ```java package com.xbxaq.thread_; public class Account { private String cardId; // 卡号 private double money; // 余额。 public Account() { } public Account(String cardId, double money) { this.cardId = cardId; this.money = money; } // 小明 小红同时过来的 public void drawMoney(double money) { // 先搞清楚是谁来取钱? String name = Thread.currentThread().getName(); // 1、判断余额是否足够 if(this.money >= money){ System.out.println(name + "来取钱" + money + "成功!"); this.money -= money; System.out.println(name + "来取钱后,余额剩余:" + this.money); }else { System.out.println(name + "来取钱:余额不足~"); } } public String getCardId() { return cardId; } public void setCardId(String cardId) { this.cardId = cardId; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } } ``` drawthread ```java package com.xbxaq.thread_; public class DrawThread extends Thread{ private Account acc; public DrawThread(Account acc, String name){ super(name); this.acc = acc; } @Override public void run() { // 取钱(小明,小红) acc.drawMoney(100000); } } ```  ## 线程同步 - 让多个线程实现先后依次访问共享资源 常见的方案为加锁,每次只允许一个线程加锁,加锁后才能进行访问,访问完毕之后自动解锁,然后其他线程才能再加锁进来  > 方法一:同步代码块 - 作用:把访问共享资源的核心代码进行上锁,保证线程的安全 - 原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进行执行 ``` synchronized(同步锁) { 访问共享资源的核心代码 } ``` 对于实例方法建议使用this作为锁对象,对于静态方法建议使用字节码(类名.class)对象作为锁对象 注意事项:对于当前同时执行的线程来说,同步锁必须是同一个对象,否则会出bug!!! **demo** test ```java package com.xbxaq.thread_; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class Test { public static void main(String[] args) throws Exception { Account acc = new Account("ICBC-110", 100000); new DrawThread(acc, "小明").start(); // 小明 new DrawThread(acc, "小红").start(); // 小红 } } ``` account ```java package com.xbxaq.thread_; public class Account { private String cardId; // 卡号 private double money; // 余额 public Account() { } public Account(String cardId, double money) { this.cardId = cardId; this.money = money; } public static void test() { synchronized(Account.class){ } } // 小明 小红同时过来的 public void drawMoney(double money) { // 先搞清楚是谁来取钱? String name = Thread.currentThread().getName(); // 1、判断余额是否足够 synchronized (this){ if (this.money >= money) { System.out.println(name + "来取钱" + money + "成功!"); this.money -= money; System.out.println(name + "来取钱后,余额剩余:" + this.money); } else { System.out.println(name + "来取钱:余额不足~"); } } } public String getCardId() { return cardId; } public void setCardId(String cardId) { this.cardId = cardId; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } } ``` drawthread ```java package com.xbxaq.thread_; public class DrawThread extends Thread{ private Account acc; public DrawThread(Account acc, String name){ super(name); this.acc = acc; } @Override public void run() { // 取钱(小明,小红) acc.drawMoney(100000); } } ```  > 方法二 同步方法 - 作用:把访问共享资源的核心方法进行上锁,以此保证线程的安全 - 原理:每次只能一个线程进入,执行完毕之后自动解锁,其他线程进行执行 ``` 修饰符 synchronized 返回值类型 方法名称(形参列表) {操作共享资源的代码} ``` **同步方法底层原理** - 同步方法其实底层也有隐式锁对象,只是锁的范围是整个方法代码 - 如果方法是实例方法,同步方法默认用this作为锁的对象 - 如果方法是静态方法,同步方法默认用类名.class作为锁的对象 **demo** test ```java package com.xbxaq.thread_; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class Test { public static void main(String[] args) throws Exception { Account acc = new Account("ICBC-110", 100000); new DrawThread(acc, "小明").start(); // 小明 new DrawThread(acc, "小红").start(); // 小红 } } ``` account ```java package com.xbxaq.thread_; public class Account { private String cardId; // 卡号 private double money; // 余额 public Account() { } public Account(String cardId, double money) { this.cardId = cardId; this.money = money; } public static void test() { synchronized(Account.class){ } } // 小明 小红同时过来的 //同步方法 public synchronized void drawMoney(double money) { // public void drawMoney(double money) { // 先搞清楚是谁来取钱? String name = Thread.currentThread().getName(); // 1、判断余额是否足够 if (this.money >= money) { System.out.println(name + "来取钱" + money + "成功!"); this.money -= money; System.out.println(name + "来取钱后,余额剩余:" + this.money); } else { System.out.println(name + "来取钱:余额不足~"); } } public String getCardId() { return cardId; } public void setCardId(String cardId) { this.cardId = cardId; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } } ``` drawthread ```java package com.xbxaq.thread_; public class DrawThread extends Thread{ private Account acc; public DrawThread(Account acc, String name){ super(name); this.acc = acc; } @Override public void run() { // 取钱(小明,小红) acc.drawMoney(100000); } } ```  > 方法三 lock锁 - Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大 - Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象 | **构造器** | **说明** | | ----------------------- | ---------------------- | | public ReentrantLock() | 获得Lock锁的实现类对象 | **常用方法** | **方法名称** | **说明** | | ------------- | -------- | | void lock() | 获得锁 | | void unlock() | 释放锁 | **demo** test ```java package com.xbxaq.thread_; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class Test { public static void main(String[] args) throws Exception { Account acc = new Account("ICBC-110", 100000); new DrawThread(acc, "小明").start(); // 小明 new DrawThread(acc, "小红").start(); // 小红 } } ``` account ```java package com.xbxaq.thread_; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Account { private String cardId; // 卡号 private double money; // 余额 private final Lock lk = new ReentrantLock(); public Account() { } public Account(String cardId, double money) { this.cardId = cardId; this.money = money; } // 小明 小红同时过来的 public void drawMoney(double money) { // 先搞清楚是谁来取钱? String name = Thread.currentThread().getName(); // 1、判断余额是否足够 try { lk.lock(); if (this.money >= money) { System.out.println(name + "来取钱" + money + "成功!"); this.money -= money; System.out.println(name + "来取钱后,余额剩余:" + this.money); } else { System.out.println(name + "来取钱:余额不足~"); } }catch (Exception e){ e.printStackTrace(); }finally { lk.unlock(); } } public String getCardId() { return cardId; } public void setCardId(String cardId) { this.cardId = cardId; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } } ``` drawthread ```java package com.xbxaq.thread_; public class DrawThread extends Thread{ private Account acc; public DrawThread(Account acc, String name){ super(name); this.acc = acc; } @Override public void run() { // 取钱(小明,小红) acc.drawMoney(100000); } } ```  ## 线程通信 - 当多个线程共同操作共享资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺 **Object类的等待和唤醒方法** | 方法名称 | 说明 | | ---------------- | ------------------------------------------------------------ | | void wait() | 让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或 notifyAll()方法 | | void notify() | 唤醒正在等待的单个线程 | | void notifyAll() | 唤醒正在等待的所有线程 | 注意:上述方法应该使用当前同步锁对象进行调用。 **线程通信的三个常见方法** | **方法名称** | **说明** | | ----------------- | ------------------------------------------------------------ | | void wait() | 当前线程等待,直到另一个线程调用notify() 或 notifyAll()唤醒自己 | | void notify() | 唤醒正在等待对象监视器(锁对象)的单个线程 | | void notifyAll() | 唤醒正在等待对象监视器(锁对象)的所有线程 | 注意:上述方法应该使用当前同步锁对象进行调用 ## 线程池 - 线程池就是一个可以复用线程的技术 不使用线程池的问题: - 用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创新新线程来处理。 - 而创建新线程的开销是很大的,并且请求过多时,会产生大量的线程出来,从而严重影响系统的性能。  > 创建线程池 JDK 5.0起提供了代表线程池的接口:ExecutorService。 **如何得到线程池对象** 方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象 方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象 > **ThreadPoolExecutor构造器**   注意事项: - 临时线程的创建时间:当新任务提交时发现核心线程都在忙,任务队列排满了,并且可以创建临时线程,才会创建临时线程 - 什么时候开始拒绝新任务:核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务 > **ExecutorService的常用方法** | 方法名称 | 说明 | | ------------------------------------- | ------------------------------------------------------------ | | void execute(Runnable command) | 执行 Runnable 任务 | | Future\<T> submit(Callable\<T> task) | 执行 Callable 任务,返回未来任务对象,用于获取线程返回的结果 | | void shutdown() | 等全部任务执行完毕后,再关闭线程池! | | List\<Runnable> shutdownNow() | 立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务 | > 新任务拒绝策略 | **策略** | **详解** | | -------------------------------------- | ------------------------------------------------------------ | | ThreadPoolExecutor.AbortPolicy | 丢弃任务并抛出RejectedExecutionException异常。**是默认的策略** | | ThreadPoolExecutor.DiscardPolicy: | 丢弃任务,但是不抛出异常 这是不推荐的做法 | | ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务 然后把当前任务加入队列中 | | ThreadPoolExecutor.CallerRunsPolicy | 由主线程负责调用任务的run()方法从而绕过线程池直接执行 | > 线程如何处理Runnable任务 - 使用executorservice的方法 - void execute (Runnable target) > 线程如何处理Callable任务,并得到任务执行完后返回的结果 - 使用ExecuteService的方法 - Future\<T> submit(callable\<T> command) > **Executors** 一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象 | **方法名称** | **说明** | | ------------------------------------------------------------ | ------------------------------------------------------------ | | public static ExecutorService newFixedThreadPool(int nThreads) | 创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。 | | public static ExecutorServicenewSingleThreadExecutor() | 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。 | | public static ExecutorService newCachedThreadPool() | 线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了60s则会被回收掉。 | | public static ScheduledExecutorServicenewScheduledThreadPool(int corePoolSize) | 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。 | 注意:**这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象** **Executors使用可能存在的陷阱** 大型并发系统环境中使用Executors如果不注意可能会出现系统风险  ## 并发与并行 > 进程 - 正在运行的程序(软件)就是一个独立的进程 - 线程是属于进程的,一个进程中可以同时运行很多个线程 - 进程中的多个线程其实是并发和并行执行的 > 并发 - 进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发  > 并行 - 在同一个时刻上,同时有多个线程在被CPU调度执行  **多线程执行时,并发和并行是同时进行的** ## 线程的生命周期 > Java的线程状态 - Java共有6中状态  > 线程的 6种状态互相转换  > 线程的6种状态总结 | **线程状态** | **说明** | | --------------------------- | ------------------------------------------------------------ | | **NEW(新建)** | 线程刚被创建,但是并未启动。 | | **Runnable(可运行)** | 线程已经调用了start(),等待CPU调度 | | **Blocked(锁阻塞)** | 线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态 | | **Waiting(无限等待)** | 一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒 | | **Timed Waiting(计时等待)** | 同waiting状态,有几个方法(sleep,wait)有超时参数,调用他们将进入Timed Waiting状态 | | **Teminated(被终止)** | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡 |
毛林
2025年9月7日 12:13
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码