您的位置 首页 java

Java中开启线程的方式有哪几种?

聊到 多线程 ,我们优先谈一下什么是进程?什么又是 线程 呢?教科书式的说法是:进程是计算机操作系统进行 内存 分配的最小单元;线程是计算机操作系统进行 任务 分配的最小单元

咱们用一个小故事,讲一下什么是线程?什么是进程?

我们可以将你们村比作一个操作系统,每家每户都可以比作一个应用程序,村长家比作 QQ ,二狗家比作360,胖丫家比作Office,这样我们就可以理解为:每家每户其实是运行在操作系统中的程序,这里的每个程序都是单独的一个进程,每个进程相互之间都是独门独户,平日里风平浪静,偶尔也会出现村长和二狗家干仗的情况.

每个家庭中的每一个人,都可以理解为不同的线程,每个线程可能干相同的事,也可能干不同的事情,但是他们可以共用一部分区域(内存),比如看电视,上厕所,这些都是公用区域.

当然也会出现线程之间抢资源的情况,有一天,姐姐和弟弟有一天心血来潮突然都想看电视,弟弟想看蜡笔小新,姐姐想看小猪佩奇,恰巧他爸多买了一个遥控器,弟弟选择CCTV-少儿频道,姐姐选择山东少儿频道,频道换来换去,电视一会是这个一会是那个,这就是所说的多个线程同时写同一个数据,且读到的数据也不相同,这样线程是不安全的.

恰巧他老爹有一天通过偷拍摄像头看到了这一幕,于是决定,将其中一个遥控器给藏起来,并对姐弟俩说,以后遥控器谁先拿到谁先看,没抢到的那个后面等着,谁在抢来抢去打屁屁,这就相当于加了一把synchronized锁,第一线程未释放锁之前,后面的线程只能等到第一个线程将锁释放后才能持有.

故事先讲到这里,今天主要是聊的是如何开启线程,后面再讲如何控制线程安全,以及公平锁非公平锁,递归锁等

如果你是window电脑,可以打开任务管理器,可以看到”进程”一列.如图,可以通过状态识别当前进程的执行状态, CPU使用率 ,内存的使用情况,对磁盘的IO使用以及当前进程对网络的的吞吐量等,这里就是我们的进程,由计算机为其分配的内存,如果你本地运行这 Java 程序,或是启动着 Tomcat 可以来这里看一下你内存的使用情况.

Java中开启线程的方式有哪几种?

如果是 Linux 或者是Mac,则可以通过top命令查看当前运行中的进程,如图

Java中开启线程的方式有哪几种?

继承Thread,重写run方法

那接下来我们讲一下如何启动线程,启动线程的方式有很多,比较传统的方式也就那两种,继承Thread,重写run方法,调用start方法;实现Runnable接口,实现run方法,我们先看下代码,比较传统的的方式也得看一下,继承 thread ,重写run()方法,调用start方法,这里尤其注意,是调用start方法,而不是重写的run方法

这时候有的同学会问了,为什么重写了Thread类的run方法,却要调用start来启动线程呢?那我们可以进入start方法中看下实际上调用的是哪个方法,通过以下截图可以看到,其实调用的是start0方法,图中也将start0方法进行了标记,其实是一个native方法,那start0种具体做了什么事情呢? 那我们需要看下 jvm 底层的源码

Java中开启线程的方式有哪几种?

感兴趣的同学可以去github上面搜一下openjdk的源码,以下片段截取于:Thread.c,我们能看到基于Thread的native 方法还不少呢,我们现在讲下start0至于其他的native方法,有兴趣的同学可以根据我们分析start0的方式去分析其他的方法.

通过下面的截图我们可以看到 start0是一个不需要入参且不需要返回值的一个方法,我们看下JVM_StartThread中的逻辑实现,这个方法在jvm.cpp中,

Java中开启线程的方式有哪几种?

以下代码截取自jvm.cpp 2867行 (截止2021年12月16日)

JVM_ENTRY( void , JVM_StartThread(JNIEnv* env, jobject jthread))

JavaThread *native_thread = NULL;

// We cannot hold the Threads_lock when we throw an exception ,

// due to rank ordering issues. Example: we might need to grab the

// Heap_lock while we construct the exception.

bool throw_illegal_thread_ State = false ;

// We must release the Threads_lock before we can post a jvmti event

// in Thread::start.

{

// Ensure that the C++ Thread and OSThread structures aren’t freed before

// we operate.

MutexLocker mu(Threads_lock);

// Since JDK 5 the java.lang.Thread threadStatus is used to prevent

// re-starting an already started thread, so we should usually find

// that the JavaThread is null. However for a JNI attached thread

// there is a small window between the Thread object being created

// (with its JavaThread set) and the update to its threadStatus, so we

// have to check for this

if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {

throw_illegal_thread_state = true ;

} else {

// We could also check the stillborn flag to see if this thread was already stopped, but

// for historical reasons we let the thread detect that itself when it starts running

jlong size =

java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));

// Allocate the C++ Thread structure and create the native thread. The

// stack size retrieved from java is 64-bit signed, but the constructor takes

// size_t (an unsigned type), which may be 32 or 64-bit depending on the platform.

// – Avoid truncating on 32-bit platforms if size is greater than UINT_MAX.

// – Avoid passing negative values which would result in really large stacks.

NOT_LP64( if (size > SIZE_MAX) size = SIZE_MAX;)

size_t sz = size > 0 ? (size_t) size : 0 ;

// 看我中文是不是特别醒目 注意thread_entry的使用,看后面的代码片段

native_thread = new JavaThread(&thread_entry, sz);

// At this point it may be possible that no osthread was created for the

// JavaThread due to lack of memory . Check for this situation and throw

// an exception if necessary. Eventually we may want to change this so

// that we only grab the lock if the thread was created successfully –

// then we can also do this check and throw the exception in the

// JavaThread constructor.

if (native_thread -> osthread() != NULL) {

// Note: the current thread is not being used within “prepare”.

native_thread -> prepare(jthread);

}

}

}

if (throw_illegal_thread_state) {

THROW(vmSymbols::java_lang_IllegalThreadStateException());

}

assert(native_thread != NULL, “Starting null thread?” );

if (native_thread -> osthread() == NULL) {

ResourceMark rm(thread);

log_warning(os, thread)( “Failed to start the native thread for java.lang .Thread %s ,

JavaThread::name_for(JNIHandles::resolve_non_null(jthread)));

// No one should hold a reference to the ‘native_thread’.

native_thread -> smr_delete();

if (Jvmti Export ::should_post_resource_exhausted()) {

JvmtiExport::post_resource_exhausted(

JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,

os::native_thread_creation_failed_msg());

}

THROW_MSG(vmSymbols::java_lang_ OutOfMemory Error(),

os::native_thread_creation_failed_msg());

}

#if INCLUDE_JFR

if (Jfr::is_recording() && EventThreadStart::is_enabled() &&

EventThreadStart::is_stacktrace_enabled()) {

Jfr ThreadLocal * tl = native_thread -> jfr_thread_local();

// skip Thread.start() and Thread.start0()

tl -> set_cached_stack_trace_id(JfrStackTraceRepository::record(thread, 2 ));

}

#endif

Thread::start(native_thread);

JVM_END


注意这里的run_method_name ,这说明还有一部分代码需要贴出来,篇幅有点大了,有的没的说了一堆,这里注意下 run_method_name 实际已经不再jvm.cpp中了,而是在vmSymbols.hpp中,后面的不做细讲了,可能有的同学对C语言忘得差不多了,我们姑且跳过这个片段,继续聊我们Java相关的内容

static void thread_entry(JavaThread* thread, TRAPS) {

HandleMark hm(THREAD);

Handle obj(THREAD, thread -> threadObj());

JavaValue result(T_VOID);

JavaCalls::call_virtual(&result,

obj,

vmClasses::Thread_klass(),

vmSymbols::run_method_name(),

vmSymbols::void_method_signature(),

THREAD);

}

Java中开启线程的方式有哪几种?

实现Runnable接口,实现run()方法

/**

* desc:

* created by cuiyongxu on 2021/12/15 12:34 上午

*/

public class RunnableTask {

public static void main ( String [] args ) {

System . out .println ( RunnableTask . class .getName () + “-” + Thread . currentThread () .getId ()) ;

new Thread ( new RunnableItem ()) .start () ;

}

}

class RunnableItem implements Runnable {

@Override

public void run () {

System . out .println ( this .getClass () .getName () + “-” + Thread . currentThread () .getId ()) ;

}

}

//输出结果为:

//RunnableTask-1

//RunnableItem-10

有的同学就要问了 Thread和Runnable到底有什么区别,目前鄙人经验之谈,两者其实没有本质的区别,一个是接口,一个是实现类,并且实现类对接口做了更多功能的扩充,要是有人较真还是有区别的,那估么可以聊下Thread是类,只能单继承;Runnable是接口,可以多实现吧,但是真正生产环境下,一般很少有这样用的,我见过的唯一这样使用的是我的世界源码,如图.如果有时候的不对的,各位看官可赐教

Java中开启线程的方式有哪几种?

实现Callable接口,实现call方法

通过FeatureTask启动创建一个线程,就能获取到线程执行的返回值,当然编写代码的方式有很多,以下 编码 方式只是其中一种`

import com.google.common.collect.Lists ;

import java.util.List ;

import java.util.concurrent.Callable ;

import java.util.concurrent.Executors ;

import java.util.concurrent.FutureTask ;

/**

* desc:

* created by cuiyongxu on 2021/12/15 12:36 上午

*/

public class CallableTask {

public static void main ( String [] args ) throws Exception {

System . out .println ( CallableTask . class .getName () + “-” + Thread . currentThread () .getId ()) ;

FutureTask < List < String >> listFutureTask = new FutureTask <>( new CallableItem ()) ;

Thread thread = new Thread ( listFutureTask ) ;

thread .start () ;

System . out .println ( listFutureTask .get ()) ;

}

}

class CallableItem implements Callable < List < String >> {

@Override

public List < String > call () throws Exception {

String msg = this .getClass () .getName () + “-” + Thread . currentThread () .getId () ;

return Lists . newArrayList ( msg ) ;

}

}

//返回结果为:

//CallableTask-1

//[CallableItem-10]

Executors.newCachedThreadPool

创建一个可缓存线程池,如果以前构建的线程可复用,则直接复用之前创建的线程了;如果线程不可复用,则会根据需要,创建新的线程,并将它添加到线程池中,如果线程在60s内未被使用,则会被终止并从线程池中移除;此种方式线程池个数无上限,虽然这么说,也是有最大限度的,但理论上不会达到,即最大线程数为: 2^31-1. 即Interger.MAX_VALUE,如图

Java中开启线程的方式有哪几种?

使用此种线程池需要注意本身操作系统user processes最大进程值 我mac 默认2784 ;可以通过 sudo ulimit -a 查询

Java中开启线程的方式有哪几种?

如果使用线程池不当,则会报出异常: Exception in thread “main” java.lang.OutOfMemoryError: unable to create new native thread,例如以下程序:

@Test

public void cachedThreadPool () {

ExecutorService executorService = Executors . newCachedThreadPool ();

for ( int i = 0 ; i < 10000 ; i++) {

executorService .submit(() -> {

try {

Thread . sleep ( 1000 * 5 );

System . out .println( Thread . currentThread ().getId());

} catch ( InterruptedException e) {

e.printStackTrace();

}

});

}

executorService .shutdown();

}

Executors.newFixedThreadPool(2)

创建一个定长线程池,不管在何时,最多可存在n个活动的线程数,当线程池中线程处于激活状态,则后续提交的任务,将被迫处于等待状态,如果线程中的线程执行过程中由于故障被终止,则会创建一个新的线程来替代它,池中的线程会一直存在

示例代码如下:

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors ;

/**

* desc:

* created by cuiyongxu on 2021/12/16 10:45 下午

*/

public class NewFixedThreadPoolTask {

public static void main ( String [] args ) {

ExecutorService executorService = Executors . newFixedThreadPool ( 2 ) ;

long startTime = System . currentTimeMillis () ;

for ( int i = 0 ; i < 10 ; i++ ) {

int finalI = i;

executorService .submit (() -> {

System . out .println ( finalI + “|” + ( System . currentTimeMillis () startTime )) ;

try {

Thread . sleep ( 2000 ) ;

} catch ( InterruptedException e ) {

}

}) ;

}

executorService .shutdown () ;

}

}

/*

0|113

1|113

2|2119

3|2119

4|4120

5|4120

6|6121

7|6121

8|8125

9|8125

*/

以上实例中设置最大线程数为2,在执行过程中,每个线程堵塞2s,stateTime只有第一次会初始化,故没两个相邻的线程打印出的耗时是完全相同的,以此证明,在同一时间内,最多只有两个线程可用.输出结果如下:

Executors . newScheduledThreadPool ( 2 )

创建一个定长的线程池,该线程池支持延迟及周期性执行,以下代码为延迟执行,3s后开始执行.

import java.util.concurrent.Executors ;

import java.util.concurrent.ScheduledExecutorService ;

import java.util.concurrent.TimeUnit ;

/**

* desc:

* created by cuiyongxu on 2021/12/16 11:41 下午

*/

public class NewScheduledThreadPoolTask {

public static void main ( String [] args ) {

ScheduledExecutorService executorService = Executors . newScheduledThreadPool ( 10 ) ;

long startTime = System . currentTimeMillis () ;

for ( int i = 0 ; i < 10 ; i++ ) {

int finalI = i;

executorService .schedule (() ->

System . out .println ( finalI + “|” + ( System . currentTimeMillis () startTime ))

, 3 , TimeUnit . SECONDS) ;

}

executorService .shutdown () ;

}

}

以下代码逻辑为,延迟0秒后开始执行,每3s执行一次

import java.util.concurrent.Executors ;

import java.util.concurrent.ScheduledExecutorService ;

import java.util.concurrent.TimeUnit ;

/**

* desc:

* created by cuiyongxu on 2021/12/16 11:41 下午

*/

public class NewScheduledThreadPoolTask {

public static void main ( String [] args ) {

ScheduledExecutorService executorService = Executors . newScheduledThreadPool ( 10 ) ;

long startTime = System . currentTimeMillis () ;

for ( int i = 0 ; i < 10 ; i++ ) {

int finalI = i;

executorService .scheduleAtFixedRate (() ->

System . out .println ( finalI + “|” + ( System . currentTimeMillis () startTime ))

, 0 , 3 , TimeUnit . SECONDS) ;

}

}

}

Executors . newSingleThreadExecutor ()

单一的线程池,该线程池中每时每刻只有一个线程能运行。后续线程必须等待当前执行的线程执行完了后,才能执行,需要遵循(FIFO),示例代码:

import java.util.concurrent.ExecutorService ;

import java.util.concurrent.Executors ;

/**

* @Description :

* @Author : cuiyongxu

* @Date : 2021/12/19-12:58 上午

**/

public class NewSingleThreadExecutorTask {

public static void main ( String [] args) {

ExecutorService executorService = Executors . newSingleThreadExecutor ();

for ( int i = 0 ; i < 10 ; i++) {

int finalI = i;

executorService .execute(() -> {

try {

System . out .println( finalI );

Thread . sleep ( 1000 );

} catch ( InterruptedException e) {

e.printStackTrace();

}

});

}

}

}

/*

输出结果为:

0

1

2

3

4

5

6

7

8

9

*/

Executors.newWorkStealingPool()

优先说一下,WorkStealingPool是JDK8中新引进的一种并发线程池,它同以上4中通过 Executors创建出来的线程池有所不同,以上4中线程池是通过对 ThreadPoolExecutor的初始化扩展,而 WorkStealingPool则是对ForkJoinPool的扩展,源码如下:

Java中开启线程的方式有哪几种?

可以通过以上代码可以看出,默认情况下,WorkStealingPool最大线程数与当前java虚拟机可用的处理器数,且在执行过程中,是无法保证执行顺序的,例如文件下载功能,多个线程同时下载的场景,示例代码如下:

import java.util.concurrent.ExecutorService ;

import java.util.concurrent.Executors ;

/**

* @Description :

* @Author : cuiyongxu

* @Date : 2021/12/19-12:58 上午

**/

public class NewSingleThreadExecutorTask {

public static void main ( String [] args) {

ExecutorService executorService = Executors . newSingleThreadExecutor ();

for ( int i = 0 ; i < 10 ; i++) {

int finalI = i;

executorService .execute(() -> {

try {

System . out .println( finalI );

Thread . sleep ( 1000 );

} catch ( InterruptedException e) {

e.printStackTrace();

}

});

}

}

}

/*

执行结果:

0

2

1

3

4

5

8

9

6

7

*/

期待您的关注,欢迎访问

文章来源:智云一二三科技

文章标题:Java中开启线程的方式有哪几种?

文章地址:https://www.zhihuclub.com/187533.shtml

关于作者: 智云科技

热门文章

网站地图