进程和线程

联想一下现实生活中的例子--烧开水,烧开水时是不是不需要在旁边守着,交给热水机完成,烧开水这段时间可以去干一点其他的事情,例如将衣服丢到洗衣机中洗衣服。这样开水烧完,衣服洗的也差不多了。这样既能喝到热水,衣服也差不多了。

操作系统中将多个程序(例如上面的热水机进程和洗衣机进程)同时进行、交替执行的称之为进程

那么操作系统为什么需要进程。

通常,操作系统进行IO处理相比计算慢得多

通过Java 进行IO 和 运算测试,IO 的速率大概比运算慢200-400倍

而IO是不需要使用CPU的,这由IO设备完成。而正在运行的程序只能等待IO设备,这样会造成性能瓶颈。所以可以通过切换到其他任务来提高CPU的吞吐率。

以下仅考虑单核情况

那么当遇到IO操作时,操作系统需要切换进程。提醒OS切换的可以是中断,定时器,进程退出等外部事件。

如果有多个任务,应该选择哪一个来运行,这需要调度器来抉择。

以及如果两个进程需要进行合作例如需要读取检索当前目录并对该目录的文件名进行排序,这称之为进程间的通信(Inter Process Communication ,IPC)问题。这里只提及一下不做深入研究。

线程的概念和进程的概念很相似,那么有了进程为什么还要线程。

  • 首先,每个进程都使用的是不同的地址空间,有了多进程之后可以使得多个并行的实体拥有了同一个地址空间。
  • 其次,线程比进程更加轻量,它们比进程更快,更容易创建也更容易撤销。
  • 最后,如果多个线程都是CPU密集型的(需要持续计算),那么不能获得性能上的增强,如果存在这大量的计算和 I/O处理,拥有多线程会加快程序执行的速度。

类比生活中,将一个面包店看作一个进程。那么里面的打蛋器,揉面机,烤炉等,这些看作它的线程。这些“线程”都在一个地址空间下(面包店),并且这些“线程”容易增删(相比于面包店,这些机器可以随时移走)。

揉面机到烤炉是需要等待的,那么这时可以切换另外一个烤炉,这样多线程的优势就出来了。

那么如何知道烤炉什么时候完成,这时候就需要知道烤炉的状态,有了状态就能够管理到每个进程。

OS中使用了状态来管理进程,同样的线程也有状态:

Java 中的多线程

Thread 是Java程序中的可执行线程,JVM允许多线程并发运行。每个线程都有一个优先级,具有较高优先级的线程优先执行,线程的优先级和创建该线程的线程相同。线程可以分为守护线程和普通线程,守护线程需要再运行之前指定,并且创建该线程的也必须是守护线程。

Thread具有生命周期(或者成为状态),可以通过一些方法来控制线程。

线程状态

Java 中的线程相应的也具有状态,和OS中的比较类似。Thread.State 表示了Thread的六种状态

public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
状态名称 说明
NEW 新建状态,线程还未开始运行
RUNNABLE 可运行状态,操作系统中的就绪和运行两种状态
BLOCKED 阻塞状态,线程阻塞于锁
WAITING 等待状态,无期限等待
TIME_WAITING 超时等待状态,有限制时间的等待,等待一定时间之后可以自行返回
TERMINATED 终止状态,标识线程已经执行完毕

线程优先级

优先级高的线程会被先调度,Thread中定义了priority字段来表示线程的优先级:

private int priority;

// 以及priority的范围

// 最小优先级
public static final int MIN_PRIORITY = 1;
// 默认的优先级
public static final int NORM_PRIORITY = 5;
// 最大的优先级
public static final int MAX_PRIORITY = 10;
但需要注意,当前线程优先级是随着父线程优先级改变的,如果父线程优先级为1,子线程也为1。
根据OS的不同,优先级高的线程不一定比优先级底的线程优先调度。
真正的调度顺序是由OS和线程调度算法来决定的

线程的创建

线程有以下几个常用的构造方法:

Thread(Runnable target){...}
Thread(Runnable target, String name){...}
Thread(ThreadGroup group, Runnable target, String name){...}

可以指定要执行的代码,线程名称,线程组

下面是一个Demo

Thread thread = new Thread(() -> {
// ...
});
thread.setName("thread-1");
// 设置线程为守护线程
// thread.setDaemon(true);
thread.start ();

如果要将线程设置为守护线程,必须在start之前就设置好。

线程和线程组

通过线程组可以对线程进行批量控制,Java 中使用 ThreadGroup来表示线程组。

每个线程组中可以嵌套线程组,是一种包含关系。

如果在执行线程时没有指定线程组,那么默认将夫线程的线程组设置为自己的线程组。

ThreadGroup的结构是树形结构:

public
class ThreadGroup implements Thread.UncaughtExceptionHandler {
private final ThreadGroup parent;
Thread threads[];
ThreadGroup groups[];
}

parent 为上一级线程组,线程组可以包含线程和线程组。

ThreadGroup 可以中断、摧毁所有活跃的线程

// 通过调用destroy来摧毁所有线程
threadGroup.destroy() // 通过调用interrupt来中断线程组中的所有线程
threadGroup.interrupt();

线程之间的通信

wait+notify

如果需要对多个线程进行同步操作,那么需要synchronized

那么当线程A获取锁后又释放锁,这些消息如何传达给其他线程。可以通过Object提供的以下方法:

notify 会随机唤醒一个等待的线程

notifyAll会唤醒所有正在等待的线程。

wait 会进入等待状态,直到被唤醒。

如果想让多个线程合作,可以通过wait+notify的形式

public static void main(String[] args) {
Object lock = new Object();
Runnable task = () -> {
for (int i = 0; i < 3; i++) {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " lock");
lock.notifyAll();
try {
lock.wait();
System.out.println(Thread.currentThread().getName() + " unlock"); k'|IOL
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
Thread t1 = new Thread(task,"t1");
Thread t2 = new Thread(task,"t2");
t1.start();
t2.start();
}

运行结果:

t1 lock
t2 lock
t1 unlock
t1 lock
t2 unlock
t2 lock
t1 unlock
t1 lock
t2 unlock
t2 lock
t1 unlock

waitnotifynotifyAll 都是native

public final native void wait(long timeoutMillis) throws InterruptedException;
public final native void notify ();
public final native void notifyAll ();

信号量

信号量,使用一个整形变量来累计唤醒次数,信号量可以保证操作是原子的。

信号量类似于红绿灯,控制者线程什么时候可以工作,什么时候不可以工作。

如下代码所示,定义了信号量为2来表示同一时间只能唤醒两个线程。

但是创建了三个线程,所以有一个线程需要在其他线程睡眠或结束时才能被唤醒

public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
Semaphore semaphore = new Semaphore(2);
Runnable r = ()->{
String name = Thread.currentThread().getName();
try{
System.out.println(name +" try to wake");
if(semaphore.tryAcquire(2, TimeUnit.SECONDS)){
System.out.println(name + " is awakened");
// do something
TimeUnit.MILLISECONDS.sleep(1000);
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
semaphore.release();
System.out.println(name+" waiting or terminated");
}
}; for (int i = 0; i < 3; i++) {
pool.execute(r);
} pool.shutdown(); }

结果

pool-1-thread-1 try to wake
pool-1-thread-3 try to wake
pool-1-thread-1 is awakened
pool-1-thread-2 try to wake
pool-1-thread-3 is awakened
pool-1-thread-2 is awakened
pool-1-thread-1 waiting or terminated
pool-1-thread-3 waiting or terminated
pool-1-thread-2 waiting or terminated

join

join是Thread类的一个实例方法。作用是让当前线程等待,等目标线程完成自己再运行。

例如当前线程需要等待目标线程的结果,就可以使用join

下面是一个小测试 -- 主线程需要使用到子线程的结果,需要等待子线程计算的结果,这个时候就可以用join

private static volatile int data = 0;
public static void main(String[] args) throws InterruptedException { Runnable r = ()->{
// to calculate
data = 1;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" is calculated");
}; Thread t1 = new Thread(r, "T1"); t1.start();
t1.join();
System.out.println("use "+ data);
}

下面是Thrad.join的实现,是一个同步的方法,使其能够保证线程安全,改变线程状态的还是使用的wait

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;
}
}
}

sleep

sleepThread类的一个静态方法。作用是让线程睡眠一会。

sleepwait不同的是,前者不会释放锁而后者会。

sleep必须指定时间,而wait不用

sleepwait都让出时间片,但是前者不释放锁,后者会,所以sleep更加容易造成死锁的情况。

线程异常处理

测试一段代码:

public static void main(String[] args) {
try {
new Thread(()->{
try {
int num = 1/0;
} catch (Exception e) {
e.printStackTrace();
}
}).start();
} catch (Exception e) {
System.out.println("catch by zero exception");
}
}

创建一个任务并在主线程中捕获异常,当运行后,并不能处理该异常。控制台并未打印出输出语句:

java.lang.ArithmeticException: / by zero

对线程A不能捕获线程B的异常做出的解释 https://www.zhihu.com/question/67790293

简答来说就是,线程之间是相互独立的。线程A不可能对线程B时时关照,因为线程A也有自己的事情需要做,如果B出问题了,那么A也会收到干扰,这对多线程来说是很糟糕的。

可以通过设置线程异常处理器来处理:

public static void main(String[] args) {
Thread thread = new Thread(() -> {
int num = 1 / 0;
});
thread.setUncaughtExceptionHandler((t, e) -> {
e.printStackTrace();
System.out.println("catch by zero exception");
});
thread.start();
}

这样就能够正常的处理异常了

java.lang.ArithmeticException: / by zero
...
catch by zero exception

UncaughtExceptionHandlerThread的内置注释接口,所有需要处理未被捕获异常的都需要实现改接口:

@FunctionalInterface
public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
}

创建线程只需要通过setUncaughtExceptionHandler去设置

public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
checkAccess();
uncaughtExceptionHandler = eh;
}

【Java中的线程】java.lang.Thread 类分析的更多相关文章

  1. Java基础之线程——派生自Thread类的子类(TryThread)

    控制台程序. 程序总是至少有一个线程,程序开始执行时就会创建这个线程.在普通的Java应用程序中,这个线程从mian()方法的开头启动. 要开始执行线程,可以调用Thread对象的start()方法. ...

  2. Java中守护线程的总结 thread.setDaemon(true)

    https://www.cnblogs.com/ziq711/p/8228255.html 在Java中有两类线程:User Thread(用户线程).Daemon Thread(守护线程) 用个比较 ...

  3. Java中创建线程的三种方式以及区别

    在java中如果要创建线程的话,一般有3种方法: 继承Thread类: 实现Runnable接口: 使用Callable和Future创建线程. 1. 继承Thread类 继承Thread类的话,必须 ...

  4. Java 线程--继承java.lang.Thread类实现线程

    现实生活中的很多事情是同时进行的,Java中为了模拟这种状态,引入了线程机制.先来看线程的基本概念. 线程是指进程中的一个执行场景,也就是执行流程,进程和线程的区别: 1.每个进程是一个应用程序,都有 ...

  5. java.lang.Thread类详解

    java.lang.Thread类详解 一.前言 位于java.lang包下的Thread类是非常重要的线程类,它实现了Runnable接口,今天我们来学习一下Thread类,在学习Thread类之前 ...

  6. Java中的线程Thread总结

    首先来看一张图,下面这张图很清晰的说明了线程的状态与Thread中的各个方法之间的关系,很经典的! 在Java中创建线程有两种方法:使用Thread类和使用Runnable接口. 要注意的是Threa ...

  7. Java中的线程

    http://hi.baidu.com/ochzqvztdbabcir/item/ab9758f9cfab6a5ac9f337d4 相濡以沫 Java语法总结 - 线程 一 提到线程好像是件很麻烦很复 ...

  8. Java并发编程(三)Thread类的使用

    一.线程的状态 线程从创建到最终的消亡,要经历若干个状态.一般来说,线程包括以下这几个状态:创建(new).就绪(runnable).运行(running).阻塞(blocked).time wait ...

  9. Java基础-进程与线程之Thread类详解

    Java基础-进程与线程之Thread类详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.进程与线程的区别 简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程 ...

随机推荐

  1. 一键解决Win10 LTSC 2021官方镜像存在的问题

    一键解决Win10 LTSC 2021官方镜像存在的问题 由于适用了win10 ltsc 2021之后,发现官方镜像存在一些致命的bug.但是本人又喜欢这个官方精简的系统,所以进行了一些修复.并将搜集 ...

  2. leetcode 643. Maximum Average Subarray I 子数组最大平均数 I

    一.题目大意 https://leetcode.cn/problems/maximum-average-subarray-i/ 给你一个由 n 个元素组成的整数数组 nums 和一个整数 k . 请你 ...

  3. 130_传析阅管理系统accdb64位版本

    博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 几年前笔者针对家居门店的进销存.人员管理.工资管理.任务系统.门店经营盈亏管理.销售分析.考勤请假等息息相关的业务基于Ac ...

  4. layui数据表格搜索

    简单介绍 我是通过Servlet传递json给layui数据表格模块,实现遍历操作的,不过数据量大的话还是需要搜索功能的.这是我参考网上大佬代码写出的搜索功能. 实现原理 要实现搜索功能,肯定需要链接 ...

  5. 缓存&PWA实践

    缓存&PWA 实践 一.背景 从上一篇<前端动画实现与原理分析>,我们从 Performance 进行动画的性能分析,并根据 Performance 分析来优化动画.但,前端不仅仅 ...

  6. 详解SQL操作的窗口函数

    摘要:窗口函数是聚集函数的延伸,是更高级的SQL语言操作,主要用于AP场景下对数据进行一些分析.汇总.排序的功能. 本文分享自华为云社区<GaussDB(DWS) SQL进阶之SQL操作之窗口函 ...

  7. poj1475 -- Pushing Boxes

    这道题其实挺有趣 的,这让我想起小时候诺基亚手机上的推箱子游戏(虽然一点也不好玩) (英文不好-->)  题意翻译: 初始人(S),箱子(B),目的地(T)用人把箱子推到 T最小步数及其路径(满 ...

  8. 讲一个linux服务启动报错问题排查

    例子 首先我们在/usr/lib/systemd/system目录下创建一个服务文件,写下服务启动任务配置.下面我以prometheus的node_exporter为例 vim /usr/lib/sy ...

  9. 南京大学 静态软件分析(static program analyzes)-- introduction 学习笔记

    一.Programming Languages体系 静态程序分析是编程语言中应用层面下的一个细分领域,它是一个非常重要的核心内容. 在理论部分,考虑的是如何设计一个语言的语法和语义,如何设计语言的类型 ...

  10. 配置svn,httpd启动报错 Job for httpd.service failed because the control process exited with error code. See "systemctl status httpd.service" and "journalctl -xe" for details.

    查看httpd的状态,发现80端口被占用,因为我的nginx的80端口. systemctl status httpd.service  解决: 把Apache的端口该成别的端口 vi /etc/ht ...