一、进程与线程
- 进程:进程是处于运行过程中的程序,具有独立的功能,是系统进行资源分配和调度的独立单位
- 独立性:进程是系统中独立存在的实体,它拥有自己独立的资源,每一个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个进程不能直接访问其他进程的地址空间
- 动态性:进程是一个正在系统中活动的指令集合,包含了时间的概念,具有自己的生命周期和状态
- 并发性:在单个处理器上,多个进程可以并发地执行,并且在执行时它们彼此之间不会互相的影响
- 线程
- 线程扩展了进程的概念,使得同一个进程可以同时并发处理多个任务
- 线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程
- 线程不拥有系统资源,它与父进程里的其他线程共享父进程所拥有的全部系统资源
- 总结
- 系统可以同时执行多个任务,每个任务就是进程
- 进程可以同时执行多个任务,每个任务就是线程
- 线程的优点
- 容易共享内存
- 进程之间不能共享内存,但线程之间共享内存非常容易
- 与分隔的进程相比,线程之间隔离程度要小,它们共享内存、文件句柄、其他每个进程应有的状态
- 运行效率更高
- 系统创建进程时要为其分配系统资源,但创建线程时不需要
- 编程方式简单
- Java 内置了多线程功能的支持,并不是简单地对操作系统的底层进行调度,编程更方便
- 容易共享内存
二、并行与并发
- 并行
- 在同一时刻,有多条指令在多个处理器上同时执行
- 并发
- 在同一时刻,某一个处理器只能执行一条指令
- 多个进程的指令可以被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果
- 对于多核计算机,当进程数多于核心数时,也会存在并发的行为,也就是同时存在并行与并发!
三、线程的创建方式
3.1 Java 的线程模型
- Java 使用 Thread 类代表线程,所有线程对象都必须是 Thread 类或其子类的实例
- 线程用于完成一定的任务(执行一段程序流),Java 使用线程执行体来代表这段程序流
- 线程的使用过程:定义线程执行体—->创建线程对象—->调用线程对象的方法以启动线程
3.2 三种方式
3.2.1 继承 Thread 类
```java / 1、定义Thread类的子类,并重写该类的run()方法 2、创建该子类的实例,即创建线程对象 3、调用线程对象的start()方法。即启动这个线程 / public class test_1 { public static void main(String[] args) { // 主线程
} }new Foo().start(); // 子线程
class Foo extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + “—->” + i); } } }
<a name="L0E6N"></a>
### 3.2.2 实现 Runnable 接口
```java
/*
1、定义Runnable接口的实现类,并实现该接口的run()方法(线程体)
2、创建该实现类的实例,并以此作为target来创建Thread对象
3、调用Thread对象的start()方法来启动线程
*/
public class test_2 {
public static void main(String[] args) {
new Thread(new RunnableTest()).start();
}
}
class RunnableTest implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
3.2.3 实现 Callable 接口
/*
1、定义Callable接口的实现类,并重写该接口的call()方法(线程体)
2、创建该实现类的实例,并使用Future接口(使用其实现类FutureTask)来包装Callable对象
最后使用Future对象(线程的返回值)作为target来创建Thread对象
3、调用Thread对象的start()方法来启动线程
4、调用Future对象的get()方法获取线程的返回值
*/
public class tast_3 {
public static void main(String[] args) throws Exception {
FutureTask<String> task = new FutureTask<>(new CallableTest());
new Thread(task).start();
System.out.println(task.get());
}
}
class CallableTest implements Callable<String> {
@Override
public String call() throws Exception {
return Thread.currentThread().getName() + "--->" + "Hello Callable!";
}
}
3.2.4 三种方式的比较
- 继承父类的方式
- 优点:编程比较简单
- 缺点:线程类已经继承了 Thread 类,所以不能再继承其他的父类
- 实现接口的方式
- 优点:线程类只实现了接口,还可以继承于其他的父类
- 缺点:编程比较麻烦
- 建议采用实现 Runnable 接口的方式,若需要获取返回值则采用实现 Callable 接口的方式
四、线程的生命周期
- 线程被创建后,不会立刻进入运行状态,也不会一直处于运行状态
- 线程生命周期中需经历以下5种状态:
- 新建(New)
- 就绪(Ready)
- 运行(Running)
- 阻塞(Blocked)
- 死亡(Dead)

五、控制线程
5.1 线程休眠(sleep)
- sleep( ) 会让线程进入阻塞状态,而 yield( ) 会让线程进入就绪状态
- sleep( ) 给其他线程运行的机会,不理会其他线程的优先级看;yield( ) 考虑线程的优先级,只会给优先级相同或者优先级更高的线程执行机会
5.2 等待线程(join)
例如在 main 线程中 AThread.join( ),意思就是 main 线程会等待 AThread 死亡后才会继续向下执行
public class JoinTest { public static void main(String[] args) throws InterruptedException { testJoinThread(); } private static class JoinThread implements Runnable { @Override public void run() { for (int i = 0; i < 30; i++) { System.out.println(Thread.currentThread().getName() + "--->" + i); } } } public static void testJoinThread() throws InterruptedException { new Thread(new JoinThread()).start(); // 开启第一个线程 for (int i = 0; i < 30; i++) { System.out.println(Thread.currentThread().getName() + "--->" + i); if (i == 10) { Thread thread = new Thread(new JoinThread()); thread.start(); // 开启第二个线程 thread.join(); // 第二个线程会让主线程等待自己死亡 } } } }5.3 后台线程(Daemon)
- 也叫守护线程,或精灵线程
- 它是在后台运行的,任务是为其他线程提供服务
- 如果所有的前台线程死亡,则后台线程会自动死亡
- 线程默认是前台线程,Thread 类提供如下方法来设置后台线程:
void setDaemon(boolean on);boolean isDaemon();
前台线程都死亡后,JVM 会通知后台线程死亡,但从它接收指令到做出响应,需要一定的时间
5.4 线程优先级
线程运行时拥有优先级,优先级高的线程则拥有较多的运行机会
- 线程默认的优先级与它的父线程相同,而主线程具有普通优先级
- Thread 提供了如下成员来处理线程的优先级:
public final static int _MIN_PRIORITY _= 1; // 线程可以拥有的最低优先级public final static int _NORM_PRIORITY _= 5; // 分配给线程的默认优先级public final static int _MAX_PRIORITY _= 10; // 线程可以拥有的最大优先级public final int getPriority() // 返回此线程的优先级public final void setPriority(int newPriority) // 设置此线程的优先级
- 线程的优先级需要操作系统的支持,虽然 Java 支持10种优先级,但操作系统支持的优先级可能少于10种,所以最好不要通过数字设置线程的优先级,而是尽可能采用静态变量来指定
六、线程安全问题
- 多线程访问同一共享变量就可能会产生线程安全问题
- 解决方法(线程同步)
第二步
lock.lock() try{ // 需要同步的代码 }finally{ lock.unlock(); }
<br />
---
<a name="bIk6x"></a>
# 七、死锁
- 死锁的产生:当两个线程互相等待对方释放同步监视器时,会发生死锁
```java
public class DeadTest {
public static void main(String[] args) {
String a = "a";
String b = "b";
new Thread(new FirstThread(a, b)).start();
new Thread(new SecondThread(a, b)).start();
}
private static class FirstThread implements Runnable {
private String a;
private String b;
public FirstThread(String a, String b) {
this.a = a;
this.b = b;
}
@Override
public void run() {
synchronized (a) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println("First");
}
}
}
}
private static class SecondThread implements Runnable {
private String a;
private String b;
public SecondThread(String a, String b) {
this.a = a;
this.b = b;
}
@Override
public void run() {
synchronized (b) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a) {
System.out.println("Second");
}
}
}
}
}
- 如何避免死锁:
- 避免多次锁定
- 尽量避免同一个线程对多个同步监视器进行锁定
- 按相同顺序加锁
- 如果多个线程需要对多个同步监视器加锁,则应该保证它们以相同的顺序请求加锁
- 使用可以超时释放的锁
- 调用 Lock 对象的
tryLock(time,unit)方法,当超过指定时间后它会自定释放锁
- 调用 Lock 对象的
- 避免多次锁定
八、线程通信
- 在某些业务中,A 线程负责修改数据,B 线程负责使用数据
- 在数据修改前,B 线程处于等待状态,直至得到 A 线程的通知
- 在数据修改后,A 线程通过某种机制,通知 B 线程去使用数据
- 线程通信的前提是要支持并发读写数据,所以线程通信前必须要对共享资源加锁
8.1 通过 synchronized 同步的通信
Object.class
| void wait() | 调用该方法的线程进入等待状态(区别于阻塞状态,阻塞状态下线程依旧持有锁),并释放此对象的锁 |
|---|---|
| void wait(long timeout) | 同上,但可设置最长等待时间 |
| void notify() | 通知一个在此对象上等待的线程,使其从 wait( ) 返回 |
| void notifyAll() | 通知所有在此对象上等待的线程,使其从 wait( ) 返回 |
/*
模拟了秒杀业务场景
*/
public class Demo1 {
public static void main(String[] args) {
Product product = new Product(0); // 设置初始库存为0
// 模拟 100 位顾客
for (int i = 1; i <= 100; i++) {
new Thread(new buyTask(product), "顾客" + i).start();
}
// 模拟倒计时
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new sellTask(product), "商家").start();
}
// 商品类
private static class Product {
int amount; // 库存
public Product(int amount) {
this.amount = amount;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
}
// 买家线程
private static class buyTask implements Runnable {
private Product product;
public buyTask(Product product) {
this.product = product;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
synchronized (product) {
while (product.getAmount() == 0) { // 库存不足
try {
System.out.println(name + "已做好准备,等待抢购...");
product.wait(); // 进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 库存充足,开始抢购
product.setAmount(product.getAmount() - 1);
System.out.println(name + "抢购到 1 件商品,库存为:" + product.getAmount());
}
}
}
// 卖家线程
private static class sellTask implements Runnable {
private Product product;
public sellTask(Product product) {
this.product = product;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
synchronized (product) {
product.setAmount(10); // 上架10件商品
System.out.println(name + "上架了10件商品,剩余库存为:" + product.getAmount());
product.notifyAll(); // 通知所有在product上等待的线程从wait()返回,可以开始抢购
}
}
}
}
8.2 通过 Lock 同步的通信
Condition condition = lock.newCondition();Condition 接口
| void await() | 调用该方法的线程进入等待状态 |
|---|---|
| boolean await(long time, TimeUnit unit) | 同上,但 time 为等待的最长时间,unit 为时间单位,如果在方法返回之前可检测到等待时间已经过去,则为 false ,否则为 true |
| void signal() | 唤醒一个在此 Condition 上等待的线程 |
| void signalAll() | 唤醒所有在此 Condition 上等待的线程 |
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo2 {
private static Lock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
public static void main(String[] args) {
Product product = new Product(0); // 设置初始库存为0
// 模拟 100 位顾客
for (int i = 1; i <= 100; i++) {
new Thread(new buyTask(product), "顾客" + i).start();
}
// 模拟倒计时
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new sellTask(product), "商家").start();
}
// 商品类
private static class Product {
int amount; // 库存
public Product(int amount) {
this.amount = amount;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
}
// 买家线程
private static class buyTask implements Runnable {
private Product product;
public buyTask(Product product) {
this.product = product;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
lock.lock();
try {
while (product.getAmount() == 0) { // 库存不足
try {
System.out.println(name + "已做好准备,等待抢购...");
condition.await(); // 进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 库存充足,开始抢购
product.setAmount(product.getAmount() - 1);
System.out.println(name + "抢购到 1 件商品,库存为:" + product.getAmount());
} finally {
lock.unlock();
}
}
}
// 卖家线程
private static class sellTask implements Runnable {
private Product product;
public sellTask(Product product) {
this.product = product;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
lock.lock();
try {
product.setAmount(10); // 上架10件商品
System.out.println(name + "上架了10件商品,剩余库存为:" + product.getAmount());
condition.signalAll(); // 通知所有在product上等待的线程从wait()返回,可以开始抢购
} finally {
lock.unlock();
}
}
}
}
九、阻塞队列
9.1 生产者消费者模式
- 某个模块负责产生数据(生产者),另一个模块负责处理数据(消费者)
- 需要有一个缓冲区位于生产者与消费者之间,作为沟通的桥梁
- 生产者只负责把数据放入缓冲区,而消费者从缓冲区取出数据
9.2 BlockingQueue
- BlockingQueue 是 Queue 的子接口,主要作用是作为线程通信的工具
- BlockingQueue 增加了两个支持阻塞的方法
void put(E e):尝试把元素 e 放入队列中,如果该队列的元素已满,则阻塞该线程E take():尝试从队列头部取出元素,如果该队列的元素已空,则阻塞该线程 ```java import java.util.Random; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue;
public class Demo3 {
public static void main(String[] args) {
BlockingQueue
// 生产者
private static class Producer implements Runnable {
private BlockingQueue<Long> queue;
public Producer(BlockingQueue<Long> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(new Random().nextInt(1000));
queue.put(System.currentTimeMillis());
String name = Thread.currentThread().getName();
System.out.println(name + "生产了1条数据,剩余:" + queue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 消费者
private static class Customer implements Runnable {
private BlockingQueue<Long> queue;
public Customer(BlockingQueue<Long> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(new Random().nextInt(4000));
queue.take();
String name = Thread.currentThread().getName();
System.out.println(name + "消费了1条数据,剩余:" + queue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
---
<a name="Qkf8N"></a>
# 十、线程组
- **ThreadGroup **类代表线程组,它可以包含一批线程,并对这些线程进行统一的管理:
1. 每个线程都有对应的线程组,若程序未显示指定线程的线程组,则该线程属于默认线程组
1. 默认情况下,子线程和它的父线程属于同一个线程组,而 main 线程属于 main 线程组
1. 一旦线程加入某个线程组,则该线程一直属于这个线程组,中途不允许修改其线程组
- ThreadGroup 的构造器:
- `public ThreadGroup(String name)`
- `public ThreadGroup(ThreadGroup parent, String name)`
- Thread 的构造器:
- `public Thread(ThreadGroup group, String name)`
- `public Thread(ThreadGroup group, Runnable target)`
- `public Thread(ThreadGroup group, Runnable target, String name)`
- ThreadGroup 中常用的处理线程的方法包括:
1. 返回线程组的名称
1. 返回当前线程组的父线程组
1. 中断此线程组中所有的线程
1. 设置(返回)线程组的最高优先级
1. 设置(返回)线程组为后台线程组
1. 异常相关
1. UncaughtExceptionHandler 代表异常处理器
1. Thread 类提供了 set 方法来指定该线程的异常处理器
1. ThreadGroup 类默认已经实现了这个异常处理器接口;当一个线程抛出异常时,JVM 会先查找该线程对应的异常处理器,若找到则调用该异常处理器来处理异常,否则 JVM 将调用该线程所属的线程组方法来处理这个异常
```java
public class Demo4 {
public static void main(String[] args) {
// 主线程的线程组
ThreadGroup group = Thread.currentThread().getThreadGroup();
// 线程组名、是否后台线程、线程组中的活跃线程数
System.out.println(group.getName() + "," + group.isDaemon() + "," + group.activeCount());
// 打印线程组中的线程
group.list();
// 子线程的线程组
Thread thread = new Thread(new ThreadTask());
thread.start();
System.out.println(thread);
// 自定义的线程组
group = new ThreadGroup("TEST");
group.setDaemon(true);
thread = new Thread(group, new ThreadTask());
thread.start();
System.out.println(thread);
// 销毁的后台线程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(group.isDestroyed() + "," + group.getName());
// 异常处理器
// 自定义异常处理器
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(t + "--->" + e);
}
});
System.out.println(3 / 0);
}
private static class ThreadTask implements Runnable {
@Override
public void run() {
System.out.println(System.currentTimeMillis());
}
}
}
十一、线程池
- 启动线程的成本是比较高的,通过线程池可以实现线程的复用,从而提高性能
- 线程池可以控制程序中的线程数量,避免超出系统的负荷,导致系统崩溃
创建线程池之后,它会自动创建一批空闲的线程;程序将线程体传给线程池,它就会启动一个空闲的线程来执行该线程体;当线程体执行结束后,该线程并不会死亡,而是变回空闲状态继续使用
11.1 创建并使用线程池
ExecutorService 接口代表线程池
- ScheduledExecutorService 是其子接口,代表可执行定时任务的线程池
- Executors 是一个工厂类,该类中包含了若干静态方法,用来创建线程池 ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit;
public class Demo5 { private static ExecutorService threadPool = Executors.newFixedThreadPool(3); private static ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3);
public static void main(String[] args) {
// 定义线程体
Runnable threadTask = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 执行了一个线程任务");
}
};
// ExecutorService
for (int i = 0; i < 10; i++) {
threadPool.submit(threadTask);
}
// 定义线程体
Runnable scheduledTask = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 执行了一个定时线程任务");
}
};
// ScheduledExecutorService,延迟5秒执行,间隔3秒,单位为秒
for (int i = 0; i < 5; i++) {
scheduledPool.scheduleAtFixedRate(scheduledTask, 5, 3, TimeUnit.SECONDS);
}
}
}
<a name="WRvzS"></a>
## 11.2 ForkJoinPool
- Fork/Join 是一种思想,旨在充分利用多核资源,用来执行并行任务
- ForkJoin 是 ExecutorService 的实现类,是上述思想的实现
- 它的做法是:将一个大的任务分割成若干个小任务,最终汇总每个小任务的结果,从而得到大任务的结果
<br /><br />
```java
import java.util.concurrent.*;
/**
* ForkJoinPool 计算数组 1-100 的和
* 有点像递归
*/
public class Demo6 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
int[] nums = new int[100];
for (int i = 1; i <= 100; i++) {
nums[i - 1] = i;
}
ForkJoinPool joinPool = new ForkJoinPool();
Future<Integer> future = joinPool.submit(new FjTask(nums, 0, nums.length - 1));
System.out.println(future.get());
}
private static class FjTask extends RecursiveTask<Integer> {
// 定义一个阈值,只有大于这个阈值才会拆分
private static final int H = 10;
private int[] nums;
private int start, end; // 数组下标
public FjTask(int[] nums, int start, int end) {
this.nums = nums;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
Integer sum = 0;
if (end - start < H) { // 小于阈值,直接算
for (int i = start; i <= end; i++) {
sum += nums[i];
}
} else { // 否则,拆分
int mid = (end + start) / 2;
FjTask left = new FjTask(nums, start, mid);
FjTask right = new FjTask(nums, mid + 1, end);
// 拆分
left.fork();
right.fork();
// 合并
sum = left.join() + right.join();
}
return sum;
}
}
}
十二、ThreadLocal
ThreadLocal 是一个工具类,可以将数据绑定到当前线程上,从而实现线程间数据的隔离
public class Demo7 {
private static ThreadLocal<Object> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(new ThreadTask(100)).start();
new Thread(new ThreadTask(200)).start();
new Thread(new ThreadTask(300)).start();
}
public static void first(){
System.out.println(Thread.currentThread().getName() + "执行了first()");
second();
}
public static void second() {
System.out.println(Thread.currentThread().getName() + "执行了second()");
third();
}
public static void third() {
System.out.println(Thread.currentThread().getName() + "执行了third()");
System.out.println(Thread.currentThread().getName() + "绑定了" + threadLocal.get());
}
private static class ThreadTask implements Runnable {
private Object value;
public ThreadTask(Object value) {
this.value = value;
}
@Override
public void run() {
threadLocal.set(value); // 放入threadLocal
first();
}
}
}
十三、线程安全的集合
- 包装不安全的集合

- 线程安全的集合

