聊到多线程,我们优先谈一下什么是进程?什么又是线程呢?教科书式的说法是:进程是计算机操作系统进行 内存分配的最小单元;线程是计算机操作系统进行 任务分配的最小单元
咱们用一个小故事,讲一下什么是线程?什么是进程?
我们可以将你们村比作一个操作系统,每家每户都可以比作一个应用程序,村长家比作QQ,二狗家比作360,胖丫家比作Office
这样我们就可以理解为:每家每户其实是运行在操作系统中的程序,这里的每个程序都是单独的一个进程,每个进程相互之间都是独门独户,平日里风平浪静,偶尔也会出现村长和二狗家干仗的情况.
每个家庭中的每一个人,都可以理解为不同的线程,每个线程可能干相同的事,也可能干不同的事情,但是他们可以共用一部分区域(内存),比如看电视,上厕所,这些都是公用区域.
当然也会出现线程之间抢资源的情况,有一天,姐姐和弟弟有一天心血来潮突然都想看电视,弟弟想看蜡笔小新,姐姐想看小猪佩奇,恰巧他爸多买了一个遥控器,弟弟选择CCTV-少儿频道,姐姐选择山东少儿频道,频道换来换去,电视一会是这个一会是那个,这就是所说的多个线程同时写同一个数据,且读到的数据也不相同,这样线程是不安全的.
恰巧他老爹有一天通过偷拍摄像头看到了这一幕,于是决定,将其中一个遥控器给藏起来,并对姐弟俩说,以后遥控器谁先拿到谁先看,没抢到的那个后面等着,谁在抢来抢去打屁屁,这就相当于加了一把synchronized锁,第一线程未释放锁之前,后面的线程只能等到第一个线程将锁释放后才能持有.
故事先讲到这里,今天主要是聊的是如何开启线程,后面再讲如何控制线程安全,以及公平锁非公平锁,递归锁等
如果你是window电脑,可以打开任务管理器,可以看到”进程”一列.如图,可以通过状态识别当前进程的执行状态,CPU使用率,内存的使用情况,对磁盘的IO使用以及当前进程对网络的的吞吐量等,这里就是我们的进程,由计算机为其分配的内存,如果你本地运行这Java程序,或是启动着Tomcat可以来这里看一下你内存的使用情况.
如果是Linux或者是Mac,则可以通过top命令查看当前运行中的进程,如图

继承Thread,重写run方法

那接下来我们讲一下如何启动线程,启动线程的方式有很多,比较传统的方式也就那两种,继承Thread,重写run方法,调用start方法;实现Runnable接口,实现run方法,我们先看下代码,比较传统的的方式也得看一下,继承Thread,重写run()方法,调用start方法,这里尤其注意,是调用start方法,而不是重写的run方法
/**
* desc:集成Thread类,重写run方法,调用start方法
* created by cuiyongxu on 2021/12/15 12:33 上午
*/
public class ThreadTask {public static void main(String[] args) {System.out.println(ThreadTask.class.getName() + "-" + Thread.currentThread().getId());ThreadItem threadItem = new ThreadItem();threadItem.start();}
}
class ThreadItem extends Thread {@Overridepublic void run() {System.out.println(this.getClass().getName() + "-" + Thread.currentThread().getId());}
}//执行结果:
//ThreadTask-1
//ThreadItem-10//如果将threadItem.start()换做为thradItem.run() 执行结果为
//ThreadTask-1
//ThreadItem-1
       这时候有的同学会问了,为什么重写了Thread类的run方法,却要调用start来启动线程呢?那我们可以进入start方法中看下实际上调用的是哪个方法,通过以下截图可以看到,其实调用的是start0方法,图中也将start0方法进行了标记,其实是一个native方法,那start0种具体做了什么事情呢? 那我们需要看下jvm底层的源码
感兴趣的同学可以去github上面搜一下openjdk的源码,以下片段截取于:Thread.c,我们能看到基于Thread的native 方法还不少呢,我们现在讲下start0至于其他的native方法,有兴趣的同学可以根据我们分析start0的方式去分析其他的方法.
通过下面的截图我们可以看到 start0是一个不需要入参且不需要返回值的一个方法,我们看下JVM_StartThread中的逻辑实现,这个方法在jvm.cpp中,
以下代码截取自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 thisif (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 runningjlong 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 (JvmtiExport::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_OutOfMemoryError(),os::native_thread_creation_failed_msg());}#if INCLUDE_JFRif (Jfr::is_recording() && EventThreadStart::is_enabled() &&EventThreadStart::is_stacktrace_enabled()) {JfrThreadLocal* tl = native_thread->jfr_thread_local();// skip Thread.start() and Thread.start0()tl->set_cached_stack_trace_id(JfrStackTraceRepository::record(thread, 2));}
#endifThread::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);
}

实现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 {@Overridepublic void run() {System.out.println(this.getClass().getName() + "-" + Thread.currentThread().getId());}
}//输出结果为:
//RunnableTask-1
//RunnableItem-10
有的同学就要问了 Thread和Runnable到底有什么区别,目前鄙人经验之谈,两者其实没有本质的区别,一个是接口,一个是实现类,并且实现类对接口做了更多功能的扩充,要是有人较真还是有区别的,那估么可以聊下Thread是类,只能单继承;Runnable是接口,可以多实现吧,但是真正生产环境下,一般很少有这样用的,我见过的唯一这样使用的是我的世界源码,如图.如果有时候的不对的,各位看官可赐教

实现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>> {@Overridepublic 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,如图
使用此种线程池需要注意本身操作系统user processes最大进程值  我mac 默认2784 ;可以通过 sudo ulimit -a 查询
如果使用线程池不当,则会报出异常: 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的扩展,源码如下:
     
        可以通过以上代码可以看出,默认情况下,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 中的多种线程开启方式相关推荐

  1. java 中的多种判空方式

    前言   如果您觉得有用的话,记得给博主点个赞,评论,收藏一键三连啊,写作不易啊^ _ ^.   而且听说点赞的人每天的运气都不会太差,实在白嫖的话,那欢迎常来啊!!! java 中的多种判空方式 1 ...

  2. 探索 Java 中的 Date, Calendar, TimeZone 和Timestamp

    探索 Java 中的 Date, Calendar, TimeZone 和Timestamp java 2010-12-31 08:56:49 阅读8 评论0  字号:大中小 订阅 对象 宋晟 (sh ...

  3. 《Java并发编程的艺术》——Java中的并发工具类、线程池、Execute框架(笔记)

    文章目录 八.Java中的并发工具类 8.1 等待多线程完成的CountDownLatch 8.2 同步屏障CyclicBarrier 8.2.1 CyclicBarrier简介 8.2.2 Cycl ...

  4. 12月18日云栖精选夜读 | Java 中创建对象的 5 种方式!...

    作为Java开发者,我们每天创建很多对象,但我们通常使用依赖管理系统,比如Spring去创建对象.然而这里有很多创建对象的方法,我们会在这篇文章中学到. Java中有5种创建对象的方式,下面给出它们的 ...

  5. Java中创建对象的几种方式

    Java中创建对象的几种方式 1.使用new创建对象,在堆上创建. 2.克隆 3.反序列化 4.反射创建对象 5.NIO中可以使用本地方法直接分配堆外内存. 转载于:https://www.cnblo ...

  6. Java中反射的三种常用方式

    Java中反射的三种常用方式 package com.xiaohao.test; public class Test{ public static void main(String[] args) t ...

  7. java中的后台线程、前台线程、守护线程区别

    java中的后台线程.前台线程.守护线程区别 区别和联系 区别 联系 区别和联系 区别 后台线程和守护线程是一样的. 后台线程不会阻止进程的终止,而前台线程会, 可以在任何时候将前台线程修改为后台线程 ...

  8. Java中创建对象的四种方式

    为什么80%的码农都做不了架构师?>>>    Java中创建对象的四种方式 (1) 用new语句创建对象,这是最常见的创建对象的方法.    (2) 运用反射手段,调用java.l ...

  9. Java中如何实现线程的超时中断

    转载自  Java中如何实现线程的超时中断 背景 之前在实现熔断降级组件的时候,需要实现接口请求的超时中断.意思是,业务在使用熔断降级功能时,在平台上设置了一个超时时间,如果请求进入熔断器开始计时,接 ...

最新文章

  1. python subprocess库 终端命令行命令
  2. 从寻找可敬的人类开始,扩展未来人类生存的8个维度
  3. 哥哥,请原谅妹妹的自私!妹妹想做你的新娘...超级感人
  4. Microsoft Visual Studio International Pack
  5. 编程题:编写一个函数string_copy()完成strcpy()的作用,并验证。
  6. java+web+415_使用json返回HTTP状态415的Web服务 - 不支持的媒体类型
  7. java中elapseTime设置新时间,Java ApplicationLike.getApplicationStartElapsedTime方法代码示例...
  8. symantec:硝基***针对化工厂商
  9. JSinput上传图片文件转base64
  10. vrep外部控制器力矩控制实例——以matlab脚本控制平面两连杆为例
  11. MatLab 中计算开根号
  12. Ubuntu下安装Master PDF Editor
  13. python考试报名官网安徽_2019年3月安徽宿州学院全国计算机等级考试报名通知
  14. VUE项目引入公共样式的styl文件
  15. 常吃大蒜对人有什么好处与坏处?
  16. 3d可视化产品爆炸图案例
  17. java获取系统dpi_Java DPI介绍
  18. 数据在链路层传播相关时间计算
  19. 如何从视频中提取音频?
  20. 连夜干出来一个自动处理【支付宝交易支付投诉管理系统】,支持多商户

热门文章

  1. UnicodeEncodeError: ‘gbk‘ codec can‘t encode character ‘\u0467‘ in position 0: illegal multibyte解决方案
  2. Android自定义View实现图片放大,平移和显示大图片
  3. 村田 IMU SCC2000系列芯片驱动
  4. 必看-做好自动化测试的7大技能
  5. Moto XT882 android2.3 媒体服务器 莫名耗电的原因
  6. 【Sparkstreaming_01】
  7. 机你太美 | 华为vs三星折叠屏大战,结果王自如赢了?!
  8. 基于卷积神经网络(CNN)的中文垃圾邮件检测
  9. 天馈线测试仪都有什么功能和特点 推荐哪个品牌
  10. jetbrain家的fleet(已获得预览权限)直接对标vscode , fleet有望超过vscode吗?今天我们实际操作下