多线程
并发与并行
- 并发:指两个或多个事件在同一个时间段内发生。(交替发生)
- 并行:指两个或多个事件在同一时刻发生(同时发生)。
线程与进程
- 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间;进程也是程序的一次执行过程,是系统运行程序的基本单位。
- 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
线程调度
- 分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
- 抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
创建线程类
- .定义Thread类的子类(可以使用匿名内部类创建),并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
- 创建Thread子类的实例,即创建了线程对象。
- 调用线程对象的start()方法来启动该线程。(多次启动一个线程是非法的,特别是当线程已经结束执行后,不能重新启动。)
创建线程方式二
- 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
- 创建一个Runnable接口的实现类对象。(可以使用匿名内部类创建)
- 创建Thread类对象,通过构造方法中传递Runnable接口的实现类对象。
- 调用Thread类中的start()方法来启动该线程。(实际的线程对象依然是Thread实例,只是该Thread线程负责执行其Runnable的run()方法。)
实现Runnable接口比继承Thread类所具有的优势
- 避免了单继承的局限性。(实现Runnable接口,还可以继续继承其他的类,实现其他接口)
- 增强了程序的扩展性,降低了程序的耦合性。(Runnable接口方式,把设置线程任务和启动新县城进行了分离)
线程安全
多线程访问了共享的数据,且多个线程中对资源有写的操作,就会产生线程安全问题。(全局变量及静态变量)
同步代码块
synchronized(同步锁){
需要同步操作的代码
}
- 通过代码块中的锁对象可以使用任意对象。(对象监视器)
- 必须保证多个线程使用的锁对象是同一个。(定义在run方法外)
- 锁对象作用:把同步代码块锁住,只让一个线程在同步代码块执行。
同步方法
修饰符 synchronized 返回值类型 方法名 (参数列表){
// 可能出现线程安全的代码(访问了共享数据的代码)
}
- 同步锁,对于非static方法,同步锁就是this。对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
Lock锁
- 在成员位置创建一个ReentrantLock对象。
- 在可能出现安全问题的代码前调用Lock接口中的方法lock()获取锁。
- 在可能出现安全问题的代码后调用Lock接口中的方法unlock()释放锁。(优化代码会写在finally中,无论是否报错都会释放锁)
线程状态
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
TimedWaiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
等待唤醒机制
线程间通信
多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
如何保证线程间通信有效利用资源
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就
是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效
的利用资源。而这种手段即—— 等待唤醒机制。
等待唤醒机制
- 是多个线程间的一种协作机制。
- wait/notify 就是线程间的一种协作机制。
等待唤醒中的方法
- wait:线程不再活动(进入 wait set 中),不再参与调度。
- notify:选取所通知对象的 wait set 中的一个线程释放,重新进入到调度队列(ready queue)中。
- notifyAll:释放所通知对象的 wait set 上的全部线程。
- 注意:哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。(如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态)
调用wait和notify方法注意细节
- wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
- wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
- .wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
线程池
是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
好处
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下。
方法
- JDK1.5之后提供线程池的顶级接口Executor,也是线程池的工厂类,用来生成线程池。
- 静态方法:public static ExecutorService newFixedThreadPool(int nThreads) 返回线程池对象。(创建的是有界可重用线程池,也就是池中的线程个数可以指定最大数量)
- ExecutorService线程池接口,其中submit(Runnable task) :方法获取线程池中某一个线程对象,并执行;void shutdown() 方法关闭/销毁线程池。
线程池使用步骤
- 使用线程池工厂类Executor里提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池。
- 创建一个类,实现Runnable接口,重写run方法,设置线程任务。
- 调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法。(线程池会一直开启,使用完线程,会把线程归还线程池,供下次使用)
- 调用ExecutorService中的方法shutdown销毁线程池。(不建议执行)