Java多线程编程
java给多线程编程提供了内置的支持。一个线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程 ,每条线程并行执行不同的任务。
一个进程包括由操作系统分配的内存空间,包括一个或多个线程。一个线程不能独立的存在,他必须是进程的一部分。一个进程一直运行,直到所有的非收回线程都结束运行后才能结束。
一个线程的生命周期
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
新建状态
使用new关键字和Thread类或器子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序
start()
线程。就绪状态
当线程对象调用了
start()
方法之后,该线程就进入了就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。运行状态
如果就绪状态的线程以获取CPU资源,就可以执行
run()
,此时线程便处于运行状态。处于运行状态的线程最为复杂,他可以变为阻塞状态,就绪状态和死亡状态。阻塞状态
如果一个线程执行了
sleep(睡眠)
、suspend(挂起)
等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:- 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
- 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
- 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
死亡状态
一个运行状态的线程完成任务或者其他终止条发生时,该线程就切换到终止状态
线程的优先级
每一个java线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
java线程的优先级是一个整数,其取值范围 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
Java 中多线程的实现
主要有三种方案:
Thread class 继承Thread类
Runnable接口 实现Runnable接口
Callable接口 实现Callable接口
Thread类
- 继承
Thread
类, 并重写run() 方法 - Thread类底层也是实现Runnable接口,并重写了
run()
方法 - 调用
start()
方法后,JVM为创建一个新的线程,并将该线程设置为可运行态Runnable,但并没有直接运行 - 当线程第一次得到时间片时,
run()
方法得以运行
1 | public class TestThread { |
start() 方法不可被多次调用,否则会抛出异常。
Thread类的常用方法
方法 | 描述 |
---|---|
public void start() | 开始线程,将新线程设置为就绪态; Java虚拟机调用该线程的run()方法 |
public void run() | 线程获得时间片时,自动被异步调用,真正开始执行该线程; 该线程实现自Runnable 接口 |
public final void setName(String name) | 改变线程名称,使之与参数name相同 |
public final void setPriority(int priority) | 更改线程的优先级 |
public final void setDaemon(boolean on) | 将该线程标记为收回线程或用户线程 |
public final void join(long millisec) | 让当前线程等待另一个线程执行完毕后再继续执行; 底层是使用的Object 类的wait() 方法 |
public void interrupt() | 中断线程 |
public final boolean isAlive() | 判断线程是否处于活动状态 |
上述方法是被Thread对象调用的,线面的方法是Thread类的静态方法。
方法 | 描述 |
---|---|
public static void yield() | 让出当前的CPU时间片,并重现变成就绪状态,重新竞争CPU。让出后,可能当前CPU使用权还会被该线程获取到。 |
public static void sleep(long millisec) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 |
public static Thread currentThread() | 返回对当前正在执行的线程对象的引用。 |
public static boolean holdsLock(Object x) | 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。 |
public static void dumpStack() | 将当前线程的堆栈跟踪打印至标准错误流。 |
线程sleep和yield的区别
sleep()
方法暂停当前线程后,会给其他线程执行机会,线程优先级对此没有影响。yield()
方法会给优先级相同或更高的线程更高的执行机会sleep()
方法会将线程转入阻塞状态,知道阻塞时间结束,才会转入就绪状态。yield()
方法会将当前线程直接转入就绪状态。sleep()
方法生命抛出InterruptedException异常,所以调用sleep()方法时要么捕捉该异常,要么显示声明抛出该异常。yield()
方法则没有声明抛出任何异常。sleep()
方法比yield()方法有更好的移植性,通常不建议使用yield()方法来控制并发线程的执行。
实现Runnable接口
如果一个子类已经继承了一个类,就无法在继承Thread类,此时可以通过Runnable接口创建线程。
通过实现Runnable接口创建RunnableDemo线程,对其进行实例化
创建Thread类的实例并传入RunnableDemo线程的实例
调用线程的start方法
1 | public class TestRunnable { |
通过 Runnable
接口来创建并启动线程,有两种方式:
- 通过 普通类 的方式或 函数式编程匿名类 的方式来创建
Runnable
接口的实现类,并实现run()
方法。 - 传入
Runnable
的实现类,实例化Thread
类对象。 - 调用
start()
方法来启动该线程。
1 | public class Demo { |
通过 Callable 和 ExecutorService创建线程
有时,我们需要在主线程中开启多个子线程并发执行一个任务,然后收集各个线程执行返回的结果并将最终结果汇总起来,这时就要用到Callable接口。
创建callable接口的实现类,并实现call方法,该call()方法将作为线程执行体,并且有返回值。
创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该Future List对象封装了该Callable对象的call()方法的返回值
使用Future List 对象作为Thread对象的target创建并启动新线程
调用Future List对象的get()方法来获得子线程执行结束后的返回值
1 | public class TestCallable { |
创建线程的三种方式的对比
- 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
- 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
面试: 线程无线次创建的时候会拖垮CPU么 ?
不会拓宽CPU, 只会占满内存,造成out of memery