- 1. 创建多线程
- 1.1 创建多线程的3种方式
- 1.1.1 继承Thread类
- 1.1.2 实现Runnable接口
- 1.1.3 实现Callable接口
- 1.1.4 基于线程池
- 1.2 为什么要提出3种方式?
- 1.2.1 Thread类的start()与run()
- 1.2.2 3种创建方式的区别
- 1.3 线程的声明周期
- 2. Thread类源码解析
- 2.1 Thread方法
- 2.2 native方法
- 2.3 其他方法
- 3. 终止线程的4种方式
- 3.1 正常退出
- 3.2 使用标志位
- 3.3 使用线程中断Interrupt
- 3.4 stop方法(线程不安全)
- 4. 常见问题
- 4.1 虚假(过早)唤醒
在Java中,实现线程的方式大体上分为三种,通过继承Thread类、实现Runnable接口,实现Callable接口。简单的示例代码分别如下所示。
1.1 创建多线程的3种方式在了解此3种方式之前,我可以告诉你们任何方式都绕不开Thread类。
1.1.1 继承Thread类// **测试类:**
public class MyThread extends Thread {
public void run(){
for(int i = 0; i < 5; i++){
System.out.println(Thread.currentThread() + ":" + i);
}
}
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
MyThread myThread3 = new MyThread();
myThread1.start();
myThread2.start();
myThread3.start();
}
}
// Thread类
public
class Thread implements Runnable {
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0(); // 调用start0(),系统创建新线程
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
// 注意,这里并不是没有实现方法,native关键字表示此方法在c/c++中实现,java源码中没有
private native void start0();
}
疑问:
- 为什么继承Thread必须要重写run方法
- 为什么Thread类中start()并没有调用run(),而实际上却调用了呢?
public class MyThread implements Runnable{
public static int count = 20;
@Override
public void run() {
while(count > 0){
try {
Thread.sleep(200);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-当前剩余票数:"+count--);
}
}
public static void main(String[] args) {
MyThread Thread1=new MyThread();
// 实际上还是将实现Runnable的实现类传入Thread初始化,与继承Thread类并重写run方法一样
Thread mThread1=new Thread(Thread1,"线程1");
Thread mThread2=new Thread(Thread1,"线程2");
Thread mThread3=new Thread(Thread1,"线程3");
mThread1.start();
mThread2.start();
mThread3.start();
}
}
1.1.3 实现Callable接口
此方式基于FutureTask类和Callable接口,通过实现Callable接口重写run方法,然后生成FutureTask对象,由于FutureTask继承了Runnable接口,所以通过传入FutureTask对象来初始化Thread类,从而实现线程创建。
// 重写Callable接口 public class MyThread implements Callable1.1.4 基于线程池{ private int count = 20; @Override public String call() throws Exception { for (int i = count; i > 0; i--) { // Thread.yield(); System.out.println(Thread.currentThread().getName()+"当前票数:" + i); } return "sale out"; } public static void main(String[] args) throws InterruptedException, ExecutionException { Callable callable =new MyThread(); FutureTask futureTask=new FutureTask<>(callable); Thread mThread=new Thread(futureTask); Thread mThread2=new Thread(futureTask); Thread mThread3=new Thread(futureTask); // mThread.setName("hhh"); mThread.start(); mThread2.start(); mThread3.start(); System.out.println(futureTask.get()); } }
此方法是将实现Runnable接口的类注入到线程池,线程池根据任务自动创建线程并执行,内部还是通过Thread类。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 创建线程池
public class MyThread{
public static void main(String[] args) {
ExecutorService ex= Executors.newFixedThreadPool(5);
for(int i=0;i<5;i++) {
ex.submit(new Runnable() {
@Override
public void run() {
for(int j=0;j<10;j++) {
System.out.println(Thread.currentThread().getName()+j);
}
}
});
}
ex.shutdown();
}
}
// AbstractExecutorService接口
public Future> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
// ThreadPoolExecutor类
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))// 添加任务
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
private boolean addWorker(Runnable firstTask, boolean core) {
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask); // 根据任务创建执行者
final Thread t = w.thread; // 获得线程
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// ...
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); // 执行线程的start方法
workerStarted = true;
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this); // 创建线程
}
1.2 为什么要提出3种方式?
通过上面的3种方法,我们了解了创建线程的3种方法,那么为什么要3种呢?一种不行吗?可以肯定这3中方式肯定在性能或适应场景上有差别。
还有Thread方法中并没有调用run方法,为什么还是执行了呢?
1.2.1 Thread类的start()与run()关键在于那个native方法,这条语句表示:这个start0方法是在DLL库中定义的,由非java语言(如c/c++)书写的,调用由JVM底层完成,所以本方法在本地没有实现。
private native void start0();
其实Java平台有个用户和本地C代码进行交互操作的API,称为Java Native Interface (Java本地接口)。我们知道了这个方法是java调用底层c的接口。该过程由JVM底层来完成。
JNI如何与C语言进行通信呢?
public
class Thread implements Runnable {
private static native void registerNatives();
static {
// 此方法将本地一些方法(DLL库)注册供当前类使用
registerNatives();
}
//...
}
我们继续往下看,registerNatives 是定义在 Thread.c 文件中的。Thread.c 是个很小的文件,定义了各个操作系统平台都要用到的关于线程的公用数据和操作。
JNIEXPORT void JNICALL
Java_Java_lang_Thread_registerNatives (JNIEnv *env, jclass cls){
(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}
static JNINativeMethod methods[] = {
{"start0", "()V",(void *)&JVM_StartThread},
{"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
{"isAlive","()Z",(void *)&JVM_IsThreadAlive},
{"suspend0","()V",(void *)&JVM_SuspendThread},
{"resume0","()V",(void *)&JVM_ResumeThread},
{"setPriority0","(I)V",(void *)&JVM_SetThreadPriority},
{"yield", "()V",(void *)&JVM_Yield},
{"sleep","(J)V",(void *)&JVM_Sleep},
{"currentThread","()" THD,(void *)&JVM_CurrentThread},
{"countStackframes","()I",(void *)&JVM_CountStackframes},
{"interrupt0","()V",(void *)&JVM_Interrupt},
{"isInterrupted","(Z)Z",(void *)&JVM_IsInterrupted},
{"holdsLock","(" OBJ ")Z",(void *)&JVM_HoldsLock},
{"getThreads","()[" THD,(void *)&JVM_GetAllThreads},
{"dumpThreads","([" THD ")[[" STE, (void *)&JVM_DumpThreads},
};
我们找到了start(0),Java 线程调用 start 方法,实际上会调用到 JVM_StartThread 方法,那这个方法又是怎样的逻辑呢?
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) … native_thread = new JavaThread(&thread_entry, sz); …
这里JVM_ENTRY是一个宏,用来定义JVM_StartThread 函数,可以看到函数内创建了真正的平台相关的本地线程,其线程函数是 thread_entry
static void thread_entry(JavaThread* thread, TRAPS) {
HandleMark hm(THREAD);
Handle obj(THREAD, thread->threadObj());
JavaValue result(T_VOID);
JavaCalls::call_virtual(&result,obj,
KlassHandle(THREAD,SystemDictionary::Thread_klass()),
vmSymbolHandles::run_method_name(),
vmSymbolHandles::void_method_signature(),THREAD);
}
可以看到调用了 vmSymbolHandles::run_method_name 方法,这是在 vmSymbols.hpp 用宏定义的:
class vmSymbolHandles: AllStatic {
…
template(run_method_name,"run")
…
}
至此,start调用run方法的过程一目了然,简单描述一下就是 Thread.start() —— JVM_StartThread—— JVM_ENTRY —— thread_entry —— Thread.run()
为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
JVM执行start方法,会另起一条线程执行thread的run方法,这才起到多线程的效果~ 「为什么我们不能直接调用run()方法?」 如果直接调用Thread的run()方法,其方法还是运行在主线程中,没有起到多线程效果。
1.2.2 3种创建方式的区别继承Thread类与实现Runnable接口:
两者最本质的区别就是继承类与实现接口区别,Java中只能单继承,可以多实现,
因此实现Runnable接口的方式有以下优点:
- 扩展性更好,避免了继承带来的局限性,
- 增强了程序的健壮性,代码能够被多个线程共享,代码与数据时独立
- 适合多个相同代码的线程去处理同一个资源的情况。
继承Thread的优点
- 封装了大部分常用代码,更好的重用了代码
因此,将Thread的run方法单独抽象成一种行为,也就是Runnable接口,以此提供更好的扩展性。
线程池方式与其他方式的区别:
不同于其他方式,我们直接将任务交给线程池,有线程池负责创建、管理、执行线程资源。这样的优点是:
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗
- 提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,监控和调优。
Callable与其他方式的区别:
在前面几种方式中,都是使用Thread类与Runnable接口实现多线程,他们在处理并发问题中有一个缺陷就是执行结束后没有返回值,因此JDK1.5中提供了FutureTaks,Future 和 Callable 类。
和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。
Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。
1.3 线程的声明周期一个线程从创建,到最终的消亡,需要经历多种不同的状态,而这些不同的线程状态,由始至终也构成了线程生命周期的不同阶段。线程的生命周期可以总结为下图。
其中,几个重要的状态如下所示。
-
NEW:初始状态,线程被构建,但是还没有调用start()方法。
-
RUNNABLE:可运行状态,可运行状态可以包括:运行中状态和就绪状态。
-
BLOCKED:阻塞状态,处于这个状态的线程需要等待其他线程释放锁或者等待进入synchronized。
-
WAITING:表示等待状态,处于该状态的线程需要等待其他线程对其进行通知或中断等操作,进而进入下一个状态。
-
TIME_WAITING:超时等待状态。可以在一定的时间自行返回。
-
TERMINATED:终止状态,当前线程执行完毕。
2. Thread类源码解析
首先是几个构造函数,不用看。我们的重点关注其提供的几个静态方法和几个本地方法上
2.1 Thread方法Thread中提供了很多静态方法,其中大部分是本地native方法,供多线程调度的,少部分用于线程对象属性设置
public
class Thread implements Runnable
{
public static native Thread currentThread(); // 获取当前线程
// 设置优先级
public final void setPriority(int newPriority) {}
public final int getPriority() {
return priority;
}
// 设置名称等
}
2.2 native方法
native是由其他语言书写的本地方法,由JVM调用。
{
// 线程让步(运行态->就绪态),只是让出CPU资源,而不会释放锁资源
// 线程让步是给优先级不低的线程竞争机会,可能让出后由立即重回执行态
public static native void yield();
// 线程睡眠(运行态->就绪态并保持一段时间),只是让出CPU资源,也不会释放锁资源
public static native void sleep(long millis) throws InterruptedException;
// 线程准备(初始态->就绪态),此时系统分配资源,但不会立即执行
private native void start0();
// 线程执行(就绪态->运行态),只有到这步,线程才真正开始执行任务
public void run() {
if (target != null) {
target.run();
}
}
// 线程中断,注意,这里的中断是应用进程的中断而不是操作系统的中断,这里只是给线程一个中断信号,从而改变线程内部的中断标识位,线程并不会立即停止改变状态
private native void interrupt0();
// 线程休眠(**->初始态),会让出CPU资源并释放锁资源,只有其他线程的通知或中断才会唤醒,定义在Object类中
// 注意:threadObj.wait()方法并不是让当前线程进入等待,而是让当前正在执行的线程进入threadObj的等待队列,即让出CPU时间片和锁资源。
public final native void wait(long timeout) throws InterruptedException;
// 抢占执行(**->初始态),在线程A执行时调用B.join(),会使A放弃CPU资源和锁资源从而执行线程B
// 注意:wait(0)与wait()是无限等待直到线程执行完毕,而wait(10)则是等待10ms
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) { // isAlive方法是native方法,查看当前线程是否就绪
wait(0); // 将当前执行的线程放入此对象的等待队列上,而不是使当前线程进入等待
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
// 休眠唤醒(初始态->就绪态),唤醒在某对象监视器上等待的单个线程,此线程等待锁竞争和CPU时间片
public final native void notify();
// notify的加强版,唤醒在某对象监视器上等待的所有线程
public final native void notifyAll();
// 线程停止,使当前执行的run方法立刻返回并释放对象的所有锁
// 线程不安全,可能造成数据不一致从而抛出异常
private native void stop0(Object o);
}
2.3 其他方法
+ sleep:强迫某线程睡眠N毫秒 + isAlive(): 判断一个线程是否存活。 + activeCount(): 程序中活跃的线程数。 + enumerate(): 枚举程序中的线程。 + currentThread(): 得到当前线程。 + isDaemon(): 判断一个线程是否为守护线程。 + setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束) + setName(): 为线程设置一个名称。 + wait(): 强迫一个线程等待 + notify(): 通知一个线程继续运行。 + setPriority(): 设置一个线程的优先级。 + getPriority()::获得一个线程的优先级。
3. 终止线程的4种方式 3.1 正常退出 3.2 使用标志位
这种也是最常用的方法,就是定义一个boolean型的标志位,在线程的run方法中根据这个标志位是true还是false来判断是否退出,这种情况一般是将任务放在run方法中的一个while循环中执行的。
public class MyThread extends Thread {
public volatile boolean exit = false;
public void run(){
int i = 0;
while(!exit){
System.out.println(i++);
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread1 = new MyThread();
myThread1.start();
sleep(100);
myThread1.exit = true;
myThread1.join();
System.out.println("线程退出");
}
}
3.3 使用线程中断Interrupt
线程处于阻塞状态:
当调用interrupt()方法时,会抛出InterruptException异常,通过捕获该异常,然后进行其他操作(结束线程)。
public class MyThread extends Thread {
public void run() {
try {
sleep(50000); // 延迟50秒
}
catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
public static void main(String[] args) throws Exception {
Thread thread = new MyThread();
thread.start();
System.out.println("在50秒之内按任意键中断线程!");
System.in.read();
thread.interrupt();
thread.join();
System.out.println("线程已经退出!");
}
}
线程处于运行状态:
使用 isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志就会置 true。
public class MyThread extends Thread {
public void run() {
int i = 0;
while (!isInterrupted()) {
//do something
System.out.println(i++);
}
}
public static void main(String[] args) throws Exception {
Thread thread = new MyThread();
thread.start();
thread.interrupt();
thread.join();
System.out.println("线程已经退出!");
}
}
3.4 stop方法(线程不安全)
thread.stop()调用之后,创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子线程所持有的所有锁,锁被破坏时,锁保护的数据有可能呈现不一致性。因此不推荐使用。
public class MyThread extends Thread {
public volatile boolean exit = false;
public void run(){
int i = 0;
while(!exit){
System.out.println(i++);
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread1 = new MyThread();
myThread1.start();
sleep(100);
//myThread1.exit = true;
myThread1.stop();
System.out.println("线程退出");
}
}
4. 常见问题 4.1 虚假(过早)唤醒
我们常看见,wait的竞态条件为什么要放入死循环呢?普通的条件判断不行吗?
当然不行,如果不使用while,在线程被唤醒后可能出现错误操作,这里一个关键点是线程wait之后,被唤醒时会继续之前的上下文,如果之前在if内部进行的线程休眠,则唤醒之后直接出if执行下列操作,导致出现异常。
public class Test_1 {
//元素列表
private List list;
//日期格式器
private static final DateFormat format = new SimpleDateFormat("HH:mm:ss");
//计数器
private AtomicLong number = new AtomicLong();
public Test_1() {
list = new ArrayList<>();
}
//对list执行删除的元素
public void remove() throws InterruptedException {
synchronized (list){
if (list.isEmpty()){
//只要list为空,那么调用此方法的线程必须等待
list.wait();
}
//如果执行到这里,说明list已经不为空了
//这样执行元素的删除操作才不会出错
String item = list.remove(0);
System.out.println(Thread.currentThread().getName() + ": remove element " + item + "! "
+ format.format(new Date()));
}
}
//对list执行添加操作
public void add(){
synchronized (list){
long currentTime = System.currentTimeMillis();
//添加元素不要进行判断
list.add(""+ number.incrementAndGet());
System.out.println(Thread.currentThread().getName() + ": add item " + number.get()
+ " " +format.format(new Date()));
list.notifyAll();
while (System.currentTimeMillis() - currentTime <= 1000){
//assume do something
}
}
}
static class AddThread implements Runnable{
private Test_1 es;
public AddThread(Test_1 es) {
this.es = es;
}
@Override
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(600);
es.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class RemoveThread implements Runnable{
private Test_1 es;
public RemoveThread(Test_1 es) {
this.es = es;
}
@Override
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(100);
es.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args){
Test_1 es = new Test_1();
for (int i = 0; i < 3; i++){
new Thread(new RemoveThread(es),"RemoveThread" + i).start();
}
new Thread(new AddThread(es),"AddThread").start();
}
}



