Java并发编程(2) —— 线程创建的方式与原理

一、Java线程创建的三种方式

1. 继承Thread类并重写run()方法

 ///方法一:使用匿名内部类重写Thread的run()方法
 Thread t1 = new Thread() {
     @Override
     public void run() {
         try {
             sleep(10000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         log.debug("子线程打印");
     }
 };
 t1.setName("t1");
 t1.start();
 log.debug("主线程打印");

线程start()后被调度运行时就会执行run()方法中的代码

2. 使用Runnable配合Thread

使用Runnable配合Thread,将【线程】与【任务】(要执行的代码)分开,解耦

 //方法二:使用Runnable配合Thread
 Runnable runnable = new Runnable() {
     @Override
     public void run() {
         //要执行的任务
     }
 };
 Thread t2 = new Thread(runnable);
 t2.start();
 
 //可使用lambda表达式简写创建Runnable对象
 Runnable runnable2 = () -> {
     //要执行的任务
 };
//或者如下。当我们将Lambda表达式作为参数传递给方法时,编译器会为我们隐式地将其转换为创建一个实现函数式接口的匿名类对象,在这里就是创建了一个实现了Runnable接口的匿名类对象
Thread t2 = new Thread(() -> {
	//要执行的任务
});

Thead类中有一个Runnable成员变量target,当通过构造器Thread(Runnable target)创建对象时,会将传入的Runnable对象赋值给target,并且Thread类实现了Runnable接口并重写了run()方法,如下
在这里插入图片描述
因此在线程start()后执行run()方法时就是执行Runnable对象中的任务代码。

可以直接调用 Thread 对象的 run 方法吗?
调用 start()方法,会启动一个系统线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 但是,直接执行 run() 方法,会把 run() 方法当成一个主线程下的普通方法去执行,并不会启动一个新的线程中执行它,所以这并不是多线程工作。
总结: 调用 start() 方法方可启动一个新的线程并使线程进入就绪状态,直接调用执行 run() 方法只是在当前线程中顺序执行。

Thread本身也实现了Runnable接口,故一个Thread对象也可以当做【任务】交给其他线程去执行它run方法中的逻辑

3. 使用FutureTask配合Thread

FutureTask是用于需要获取线程任务执行结果的情况,其内部除了Runnable接口外,还实现了一个Future接口,这个接口定义了一系列可感知任务当前执行状态的方法。
FutureTask能够接收函数式接口Callable 类型的参数,在FutureTask重写的run方法中会执行Callable对象的方法并将返回结果保存到FutureTask对象的outcome变量中。在主线程中调用FutureTask的get()方法时,会将当前线程加入FutureTask对象内部的阻塞队列阻塞等待,FutureTask的run方法执行完毕后,会唤醒对象内部阻塞队列中所有阻塞的线程,从get方法返回执行结果。
在这里插入图片描述
在这里插入图片描述

	//方法三:使用FutureTask配合Thread
	FutureTask<Integer> task3 = new FutureTask<>(new Callable<Integer>() {
	    @Override
	    public Integer call() throws Exception {
	        log.debug("hello");
	        return 100;
	    }
	});
	//构造器参数1:任务对象 参数2:线程名字
	new Thread(task3, "t3").start();
	//task3.get()方法会阻塞当前线程,等待任务执行完毕获取返回结果
	Integer result = task3.get();
	log.debug("任务运行结果是:{}", result);

二、线程创建的原理

以上创建线程的方法看起来有三种,实际上内部都是通过调用start()方法去创建并关联一个内核线程然后执行run()方法中的任务代码,步骤如下:

  1. 在start方法中调用本地方法start0(),
    在这里插入图片描述

  2. 在方法中首先创建了一个JavaThread对象,在其构造方法中将要执行的任务保存为属性,并调用os创建线程的方法,这个方法会根据底层操作系统的不同 调用系统提供的API创建关联线程
    在这里插入图片描述
    在这里插入图片描述

  3. 以Linux系统为例,首先创建一个OSThread对象,并且关联上前面创建的JavaThread对象,最后调用Linux提供的创建线程的方法pthread_create()来创建一个内核线程,
    在这里插入图片描述

  4. 到这里父线程和子线程才真正分道扬镳,后续在子线程中会将OSThread对象与内核线程关联起来然后初始化,在父线程中会继续帮助子线程做一些prepare工作,将JavaThread对象与上层的子线程java对象关联起来,然后修改子线程状态为RUNNABLE,并唤醒子线程,子线程将属性中保存的任务取出来并进行执行(c中调用JavaCalls::call_virtual()方法访问执行Java代码)

因此,Java线程对象与内核线程的关联如下:

在这里插入图片描述

参考:https://www.bilibili.com/video/BV15Y411e7jm?p=2

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值