文章目录
相关概念
并行:在同一时刻,有多个任务在多个CPU上同时执行
并发:在同一时刻,有多个任务在一个CPU上交替执行
进程:正在执行的程序
线程:应用程序中做的事情,是程序运行的基本执行单元
当操作系统执行一个程序时,会在系统中建立一个进程,该进程必须至少建立一个线程(这个线程被称为主线程),作为这个程序运行的入口点。因此,在操作系统中运行的任意程序都至少有一个线程。
多线程
概念
是指从软件或者硬件上实现多个线程并发执行的技术
实现方式
继承Thread类
- 创建一个类继承Thread类
- 在类中重写run方法(线程执行的任务在该方法中)
- 创建线程对象,调用线程的start方法开启线程
实现Runnable接口
- 定义一个类实现Runnable接口,并重写run方法(可以用匿名内部类实现)
- 创建任务对象
- 使用还有Runnable参数的构造方法创建线程对象并指定对象
- 调用线程的start方法,开启线程
利用Callable接口,FutureTask类来实现
比较
- 只能单继承,线程任务和线程功能都在Thread子类中
- 能多实现,线程任务在Runnable接口实现类中或FutureTask对象中,线程功能在Thread类中;有解耦的好处
接口一是用来制定规则,二是用来降低耦合(紧密连接的程度)
常用方法
线程安全
产生的原因
多个线程对于同一个数据,进行读写操作,造成数据错乱
解决思想
把多个线程操作的共享数据,存放到一个安全的环境中
Java语言基于线程安全的问题,提供了同步机制
同步
同步代码块
同步方法
Lock锁机制
死锁
概念
同步代码块的锁进行嵌套使用,就会大概率产生死锁
避免
不使用锁的嵌套
状态
线程间的通讯
介绍
通过等待和唤醒机制,来实现多个线程协调操作完成某一项任务
等待唤醒机制其实就是让线程进入等待状态或者让线程从等待状态中唤醒
方法
- 等待方法
- 特殊之处:会释放掉对象锁
wait() //无限等待,(只能其他线程唤醒)
wait(Long 毫秒) //计时等待(时间到了自动唤醒)
- 唤醒方法
- 特殊之处:不会释放掉对象锁
notify() //唤醒处于"等待状态"的任意一个线程
notifyAll() // 唤醒处于"等待状态"的所以线程
注意:
- 等待和唤醒的方法,都要使用锁对象调用(需要在同步代码块中使用)
- 等待和唤醒方法应该使用相同的锁对象调用
生产者消费者案例
public class Resource {
//共享资源
static int num = 0;
static final Object LOCK = new Object();
}
public class ConsumerTask implements Runnable {
@Override
public void run() {
while (true) {
synchronized (Resource.LOCK) {
if (Resource.num == 0) {
//进入等待状态
System.out.println(Thread.currentThread().getName() + "发现没有食物,进入等待状态。。。");
try {
Resource.LOCK.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
//有食物,吃完并唤醒
System.out.println(Thread.currentThread().getName() + "发现有食物,吃完并唤醒");
Resource.num--;
Resource.LOCK.notify();
}
}
}
}
}
public class ProducerTask implements Runnable {
@Override
public void run() {
while (true) {
synchronized (Resource.LOCK) {
if (Resource.num == 0) {
//生产食物并唤醒消费者
System.out.println(Thread.currentThread().getName() + "生产了食物,唤醒消费者");
Resource.num = 1;
Resource.LOCK.notify();
} else {
//有食物,等待
System.out.println(Thread.currentThread().getName() + "发现有食物,进入等待状态");
try {
Resource.LOCK.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
public class Test {
public static void main(String[] args) {
new Thread(new ConsumerTask(),"消费者").start();
new Thread(new ProducerTask(),"生产者").start();
}
}
线程池
介绍
是一个可以复用线程的技术
原因:用户每次发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理,而创建新线程的开销是很大,并且请求过多时,肯定会有大量的线程被创建出来,这会影响性能,就需要线程池了
创建
ExecutorService接口
方式一
方式二
注意事项:
- 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程
- 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务
常用方法
处理
- 处理Runnable任务
- 处理Callable任务