02.并发编程(2)Thread类源码分析

概述

在说线程之前先说下进程,进程和线程都是一个时间段的描述,是CPU工作时间段的描述。

进程,是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竟争计算机系统资源的基本单位。每一个进程都有一个自己的地址空间,即进程空间或(虚空间)。

线程,在网络或多用户环境下,一个服务器通常需要接收大量且不确定数量用户的并发请求,为每一个请求都创建一个进程显然是行不通的,——无论是从系统资源开销方面或是响应用户请求的效率方面来看。因此,操作系统中线程的概念便被引进了。线程,是进程的一部分,一个没有线程的进程可以被看作是单线程的。线程有时又被称为轻权进程或轻量级进程,也是 CPU 调度的一个基本单位。

创建方式

线程的创建有三种方式:继承Thread,实现Runnable接口,利用Callable跟Future

继承Thread

(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。 (2)创建Thread子类的实例,即创建了线程对象。 (3)调用线程对象的start()方法来启动该线程。

    public class FirstMethod extends Thread {@Overridepublic void run() {super.run();}}FirstMethod firstMethod = new FirstMethod();firstMethod.start();
复制代码

实现Runnable接口

  • (1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  • (2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
  • (3)调用线程对象的start()方法来启动该线程。
public class SecondMethod implements Runnable{@Overridepublic void run() {}}
SecondMethod secondMethod=new SecondMethod();
new Thread(secondMethod).start();
复制代码

通过Callable跟FutureTask创建线程

1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。 (2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。 (3)使用FutureTask对象作为Thread对象的target创建并启动新线程。 (4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

  public class ThirdMethod implements Callable<String> {@Overridepublic String call() throws Exception {return Thread.currentThread().getName();}}ThirdMethod thirdMethod=new ThirdMethod();FutureTask<String> futureTask=new FutureTask<String>(thirdMethod);try {String threadName = futureTask.get();} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();} 
复制代码

对比分析

实现Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值,后者线程执行体run()方法无返回值,因此可以把这两种方式归为一种这种方式与继承Thread类的方法之间的差别如下:

1、接口创建线程可以实现资源共享,比如多个线程可以共享一个Runnable资源 2、但是编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。 3、接口创建线可以避免由于Java的单继承特性而带来的局限。

现在通过一个程序员改Bug的例子来描述一下,一共有15个bug,现在安排3个程序员去Debug:

通过Thread来实现

public class BugThread extends Thread {private volatile int bugNumber = 5;@Overridepublic void run() {for (int i = 0; i < bugNumber; i++) {System.out.println("bugNumber--->" + bugNumber--);}}
}public class Main {public static void main(String[] args) {new BugThread().start();new BugThread().start();new BugThread().start();}
}
复制代码

运行结果:

Thread-0-----5
Thread-1-----5
Thread-2-----5
Thread-0-----4
Thread-2-----4
Thread-1-----4
Thread-2-----3
Thread-0-----3
Thread-2-----2
Thread-1-----3
Thread-2-----1
Thread-0-----2
Thread-0-----1
Thread-1-----2
Thread-1-----1
复制代码

通过Runnable来实现

public class BugRunnable implements Runnable {private volatile int bugNumber = 15;@Overridepublic void run() {while (bugNumber > 0)System.out.println(Thread.currentThread().getName() + "-----" + bugNumber--);}
}public static void main(String[] args) {BugRunnable bugRunnable = new BugRunnable();new Thread(bugRunnable).start();new Thread(bugRunnable).start();new Thread(bugRunnable).start();}
复制代码

运行结果

Thread-0-----15
Thread-0-----14
Thread-0-----13
Thread-0-----12
Thread-1-----11
Thread-0-----10
Thread-1-----9
Thread-0-----8
Thread-1-----7
Thread-0-----6
Thread-1-----5
Thread-0-----4
Thread-1-----3
Thread-0-----2
Thread-1-----1
复制代码

源码分析

成员变量

	private volatile char  name[];//线程名称的字节数组private int    priority;//线程优先级private boolean single_step;  //线程是否单步private boolean daemon = false;  //是否是守护线程private boolean stillborn = false; //JVM stateprivate Runnable target;  //从构造方法传过来的Runnableprivate ThreadGroup group; //线程组private ClassLoader contextClassLoader;  //类加载器private static int threadInitNumber;  //线程编号private volatile int threadStatus = 0; //初始状态public final static int MIN_PRIORITY = 1; //最低优先级public final static int NORM_PRIORITY = 5; //默认优先级public final static int MAX_PRIORITY = 10; //最高优先级复制代码

线程状态

 public enum State {//Thread state for a thread which has not yet started.NEW,//Thread state for a runnable thread. RUNNABLE,//Thread state for a thread blocked waiting for a monitor lock.BLOCKED,// Thread state for a waiting thread.WAITING,//Thread state for a waiting thread with a specified waiting time.TIMED_WAITING,//Thread state for a terminated threadTERMINATED;}
复制代码

线程的状态有NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED,可以整理成如下表格

线程状态解释
New还未调用 start() 方法
RUNNABLE调用了 start() ,此时线程已经准备好被执行,处于就绪队列
BLOCKED线程阻塞于锁或者调用了 sleep
WAITING线程由于某种原因等待其他线程
TIME_WAITING与 WAITING 的区别是可以在特定时间后自动返回
TERMINATED执行完毕或者被其他线程杀死

构造方法

Thread有很多构造方法,但是通过观察最终调用了如下方法:

  /*** Initializes a Thread.** @param g //线程组* @param target //构造方法传过来的Runnable* @param name //线程名称* @param stackSize //给线程分配的栈的深度* @param acc //上下文加载器*/private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc) {if (name == null) {throw new NullPointerException("name cannot be null");}this.name = name.toCharArray();Thread parent = currentThread();SecurityManager security = System.getSecurityManager();//判断线程组参数是否为空if (g == null) {if (security != null) {g = security.getThreadGroup();}if (g == null) {g = parent.getThreadGroup();}}g.checkAccess();if (security != null) {if (isCCLOverridden(getClass())) {security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);}}g.addUnstarted();this.group = g;//初始化线程组this.daemon = parent.isDaemon();//定义是否为守护线程this.priority = parent.getPriority();//设置优先级if (security == null || isCCLOverridden(parent.getClass()))this.contextClassLoader = parent.getContextClassLoader();elsethis.contextClassLoader = parent.contextClassLoader;this.inheritedAccessControlContext =acc != null ? acc : AccessController.getContext();this.target = target;//初始化targetsetPriority(priority);//设置优先级if (parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/* Stash the specified stack size in case the VM cares */this.stackSize = stackSize;//设置栈深度/* Set thread ID */tid = nextThreadID();//设置线程ID}
复制代码

start方法

    public synchronized void start() {if (threadStatus != 0)//判断线程是否准备好group.add(this);//将启动的线程线程组boolean started = false;try {start0();//本地方法,JVM调用target的run方法started = true;//更改启动标志} finally {try {if (!started)group.threadStartFailed(this);//通知线程组启动失败}} catch (Throwable ignore) {/* do nothing. If start0 threw a Throwable thenit will be passed up the call stack */}}}复制代码
@Overridepublic void run() {if (target != null) {target.run();}}
复制代码

synchronized 关键字说明start方法是同步的,并且是启动这个线程进行执行,JVM将会调用这个线程的run方法,这样产生的结果是,两个线程执行着,其中一个是调用start()方法的线程执行,另一个线程是执行run方法的线程。

sleep()方法

 public static void sleep(long millis, int nanos)throws InterruptedException {if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (nanos < 0 || nanos > 999999) {throw new IllegalArgumentException("nanosecond timeout value out of range");}if (nanos >= 500000 || (nanos != 0 && millis == 0)) {millis++;}sleep(millis);//调用本地方法}
复制代码

线程休眠一段时间,让其他线程有机会继续执行,需要捕捉异常。

yield()方法

  public static native void yield();
复制代码
  • yield是一个静态的原生(native)方法
  • yield告诉当前正在执行的线程把运行机会交给线程池中拥有相同优先级的线程。
  • yield不能保证使得当前正在运行的线程迅速转换到可运行的状态 它仅能使一个线程从运行状态转到可运行状态,而不是等待或阻塞状态

join()方法

  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()) {wait(0);}} else {while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}}
复制代码

join方法是等待该线程执行,直到超时或者终止,可以作为线程通信的一种方式,A线程调用B线程的join(阻塞),等待B完成后再往下执行。

yieldjoin

  • join方法用线程对象调用,如果在一个线程A中调用另一个线程B的join方法,线程A将会等待线程B执行完毕后再执行。
  • yield可以直接用Thread类调用,yield让出CPU执行权给同等级的线程,如果没有相同级别的线程在等待CPU的执行权,则该线程继续执行。

interrupt()方法

    public void interrupt() {if (this != Thread.currentThread())checkAccess();//检查权限
synchronized (blockerLock) {Interruptible b = blocker;if (b != null) {interrupt0();        b.interrupt(this);return;}}interrupt0();}
复制代码

interrupt()方法是中断当前的线程, 此外还有isInterrupt,以及interrupted方法

  • interrupt():将线程置为中断状态
  • isInterrupt():线程是否中断
  • interrupted():返回线程的上次的中断状态,并清除中断状态。 一般来说,阻塞函数:如sleep()、join()、wait()等在检查到线程的中断状态的时候,会抛出InteruptedExeption, 同时会清除线程的中断状态。

线程间通信

前面说过,Java中的线程在底层是通过共享内存进行通信的,在应用层则是通过调用Object对象的wait()方法和notify()方法或notifyAll()方法来实现线程间的通信。 Object是所有类的超类,主要通过:notify()、notifyAll()、wait()、wait(long)和wait(long,int)这几个方法来进行线程间通信。

1、wait()

public final void wait()  throws InterruptedException,IllegalMonitorStateException
复制代码
  • 休眠当前线程,释放锁,直到接到通知或被中断为止
  • 在调用wait()之前,线程必须要获得该对象的对象级别锁

2、notify()

public final native void notify() throws IllegalMonitorStateException
复制代码
  • 通知那些调用了wait()方法的线程。
  • 每次只能通知单个线程,单个线程等待,则通知当前线程,如果有多个,则随机抽取一个来进行通知
  • 必须等到当前线程释放锁后,wait所在的线程也才可以获取该对象锁,但不惊动其他同样在等待被该对象notify的线程们。
  • wait()等待的是被notify或notifyAll,而不是锁。

3、notifyAll()

public final native void notifyAll() throws IllegalMonitorStateException
复制代码
  • 使所有原来在该对象上wait的线程统统退出wait的状态
  • 所有被通知的线程都会去竞争对象锁。
  • 获得锁的线程,会继续往下执行,释放锁后,wait中的线程继续竞争对象锁

wait()和sleep()的区别

  • sleep()方法是线程类Thread的静态方法,导致此线程暂停执行指定时间,将执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复(线程回到就绪(ready)状态),因为调用sleep 不会释放对象锁。
  • wait()是Object 类的方法,对此对象调用wait()方法导致本线程放弃对象锁(线程暂停执行),进入等待此对象的等待锁定池,只有针对此对象发出notify 方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入就绪状态。

总结

通过对线程源码的简单分析,可以看出线程也是有自己的生命周期的,但是由于源码中有很多native方法,导致了很难追踪源码,所以只能大致理解一下线程的各种状态以及通信过程,下面可以通过一副流程图来总结一下:

参考资料

Java编程思想

wangchangchung.github.io

www.jianshu.com/p/5b9fdae43…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/278945.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安装SQLserver2008

双击点击setup&#xff0c;以管理员身份运行&#xff1b; 点击安装-》全新SQLServer独立安装或向现有安装添加功能 选择下一步&#xff0c;然后点击具有高级服务的express版本&#xff0c;点击下一步&#xff1b; 点击选择我接受许可条款&#xff0c;然后继续点击下一步&#x…

如何在没有Word的情况下打开Microsoft Word文档

Microsoft Word is part of Microsoft Office and requires an up-front purchase or a Microsoft 365 subscription. If you’re using a computer without Word installed, there are other ways to view that DOCX or DOC file. Microsoft Word是Microsoft Office的一部分&a…

redhat9Linux解压gz,linux (redhat9)下subversion 的安装

搞了一个subversion 花费了我两天的时间&#xff0c;其间虽然有干其他的事情&#xff0c;但是来来回回的装&#xff0c;搞的我是一点脾气都没有了&#xff0c;俗话说不经历风雨真的见不到彩虹。就是这个意思. 原本本的下来一.准备好安装包打算使用apache来浏览subversion &…

数组去重的4种方法(Which one is the fastest???嘻嘻嘻....)

<!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <title>Document</title> </head> <body> <input type"button" value"数组去重1" οnclick"show()"&g…

flask中的模型

1.什么是模型   模型&#xff0c;是根据数据库中表的结构而创建出来的class。每一张表对应到编程语言中&#xff0c;就是一个class表中的每一个列对应到编程语言中就class中的一个属性。 2.ORM的三大特征   1.数据表(table)到编程类(class)的映射     数据库中的每一张…

windows复制文件路径_如何在Windows 10上复制文件的完整路径

windows复制文件路径Sometimes, it’s handy to copy the full path of a file or folder in Windows 10 to the clipboard. That way, you can paste the path into an open or upload dialog quickly without having to browse for it the file. Luckily, there’s an easy w…

用c语言复制字符串的元音字母,急求:编写程序,将一个字符串中的元音字母复制到另一个字符串,然后输出。...

#include#includevoid str(char a[100],char b[100]){int i0, j0;while(a[i]!\0)//\0代表ASCLL码0的字符&#xff0c;即是一个空操作符也就是是结束符;{if(a[i]a||a[i]e||a[i]i||a[i]o||a[i]u){b[j]a[i];j;}i;}}int main(){char a[100];char b[100];gets(a);str(a,b);puts(b);r…

05 替换空格

题目描述&#xff1a; 请实现一个函数&#xff0c;将一个字符串中的每个空格替换成“%20”。例如&#xff0c;当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 解题思路有&#xff1a; #判断字符串是否为空&#xff0c;判断length是否大于0。 #记录空格的数…

超链接禁用_在Microsoft Word 2003和2007中禁用自动超链接

超链接禁用If you can’t stand the automatic hyperlinking in Microsoft Word, you might be hard-pressed to find the right place to disable it in Office 2007, since all the settings are hidden so well compared to previous versions. 如果您无法在Microsoft Word中…

c语言如何创建虚拟串口,模拟串口的C语言源程序代码

本程序是模拟串口硬件机制写的&#xff0c;使用时可设一定时中断&#xff0c;时间间隔为1/4波特率&#xff0c;每中断一次调用一次接收函数&#xff0c; 每中断4次调用一次发送函数,不过.对单片机来说时钟并须要快.要知道9600的波特率的每个BIT的时间间隔是104us.而单片机中断一…

webjars管理静态资源

webjars用途简单解释 : 利用Servlet3协议规范中,包含在JAR文件/META-INF/resources/路径下的资源可以直接被web访问到这一原理&#xff0c;将前端静态资源打成jar包方便管理 静态资源打jar包 新建maven工程&#xff0c; 将需要打包的静态资源放入src/main/resources中 2. ma…

Windows Intellij环境下Gradle的 “Could not determine Java version from ‘9.0.1’”的解决方式...

当我导入Gradle项目初试Java spring的时候&#xff0c;遇到下面报错: Gradle complete project refresh failed Error:Could not determine java version from 9.0.1. 参考这篇 http://www.ddiinnxx.com/solving-not-determine-java-version-9-0-1-gradle-intellij-macosx/ 进行…

如何计算iPhone和Apple Watch上的步数

Khamosh PathakKhamosh PathakAccording to conventional wisdom, 10,000 steps a day equals a healthy life. No matter what your target is, though, you’ll need a reliable way to count your steps. The good news is you can do so on your iPhone or Apple Watch! 根…

在c语言中load,一道题理清Objective-C中的load和initialize

Objective-C中有两个方法比较特殊&#xff0c;他们会在Runtime时根据情况自动调用&#xff0c;下面我们简单分析一下调用时机以及使用场景~一般在iOS初中级面试时偶尔会被问到load和initialize方法&#xff0c;我出了一道题&#xff0c;估计会搞晕很多人。大家来看一下下面的程…

018.Zabbix维护时间和模板导入

一 维护时间 在某些正常业务维护期间&#xff0c;不需要进行告警&#xff0c;可添加维护时间。二 维护时间添加 2.1 维护 参数描述Name维护名称Maintenance type两种维护类型可选:With data collection - 依旧收集数据No data collection - 暂停收集数据Active since维护周期开…

本地服务器下的局域网安全吗_本地安全认证服务器

本地服务器下的局域网安全吗Today a reader had a very good question about lsass.exe is the Microsoft security management process for domain access and local security policies. Simply put it manages who logs on to your PC and/or Server. There are a few viru…

Query-digest-UI监控慢查询,以及此工具的改进版

本文主要描述基于pt-query-digest工具对慢查询日志进行监控的工具Query-digest-UI。(安装、使用、介绍以及benren提供的改进版。) 本文中描述的内容与其他网站上对Query-digest-UI的安装和使用稍有不同&#xff0c;因为本人对此工具稍做了调整。欢迎转载&#xff0c;请注明作者…

电热水器工作过程 c语言,热水器工作流程图

燃气热水器做为热水供应设备&#xff0c;被很多家庭所采用&#xff0c;然而&#xff0c;恒温作为燃气热水器的一个痛点&#xff0c;一次次被击中&#xff0c;那么到底为什么燃气热水器实现恒温这么难呢&#xff1f;我们将从原理讲起&#xff0c;带您认识真正的燃气热水器。燃气…

es6 模块化

test.js var vm"321321"; export { vm }; ------------------------------------------------------ export var name"李四"; a.vue import {vm} from /test console.log(vm); ------------------------------------------------------ console.log(name);…

linux上tail命令_如何在Linux上使用tail命令

linux上tail命令Fatmawati Achmad Zaenuri/ShutterstockFatmawati Achmad Zaenuri / ShutterstockThe Linux tail command displays data from the end of a file. It can even display updates that are added to a file in real-time. We show you how to use it. Linux tail…