进程与线程

进程

  • 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的
  • 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程

线程

  • 一个进程之内可以分为一到多个线程。
  • 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行
  • Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作为线程的容器

进程与线程的区别

  • 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
  • 进程拥有共享的资源,如内存空间等,供其内部的线程共享
  • 进程间通信较为复杂
    • 同一台计算机的进程通信称为 IPC(Inter-process communication)
    • 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
  • 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低

并行与并发

单核 cpu 下,线程实际还是 串行执行 的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感觉是 同时运行的 。总结为一句话就是: 微观串行,宏观并行 。一般会将这种 线程轮流使用 CPU 的做法称为并发 (concurrent)

多核 cpu下,每个 核(core) 都可以调度运行线程,这时候线程可以是并行的。

Java 线程

创建和运行线程

  • 直接使用 Thread

    package create;
    
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j(topic = "c.ThreadCre")
    public class ThreadCre {
    public static void main(String[] args) { Thread t = new Thread(){
    @Override
    public void run() {
    log.debug("running");
    }
    }; t.start(); log.debug("running"); }
    }
  • 使用 Runnable 配合 Thread

    package create;
    
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j(topic = "c.RunnableCre")
    public class RunnableCre {
    public static void main(String[] args) {
    Runnable r = new Runnable() {
    @Override
    public void run() {
    log.debug("running");
    }
    }; Thread t = new Thread(r,"t2"); t.start();
    }
    }

    使用 lambda 方式简化

    package create;
    
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j(topic = "c.RunnableCre")
    public class RunnableCre {
    public static void main(String[] args) {
    Runnable r = () -> { log.debug("running"); }; Thread t = new Thread(r,"t2"); t.start();
    }
    }
  • FutureTask 配合 Thread

    package create;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask; @Slf4j(topic = "c.FutureTaskCre")
    public class FutureTaskCre {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
    log.debug("running...");
    Thread.sleep(1000);
    return 100;
    }
    }); Thread t = new Thread(task,"t1");
    t.start(); log.debug("{}",task.get());
    }
    }

Thread 与 Runnable 的关系

  • 用 Runnable 更容易与线程池等高级 API 配合
  • 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活

线程运行的原理

栈与栈帧

每个线程启动后,虚拟机就会为其分配一块栈内存。

  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

线程上下文切换

因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码

  • 线程的 cpu 时间片用完
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法

当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的

  • 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
  • Context Switch 频繁发生会影响性能

常见方法

方法名 static 功能说明 注意
start() 启动一个新线程,在新的线程运行 run 方法中的代码 start 方法只是让线程进入就绪,里面的代码不一定立刻运行(CPU的时间片还没有分给它)。每个线程对象的 start 方法只能调用一次,否则会出现异常
run() 新线程启动后会调用的方法 如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法。但可以创建 Thread 的子类对象来覆盖默认行为
join() 等待线程运行结束
join(long n) 等待线程运行结果,最多等待 n 毫秒
getId() 获取线程长整型的 id
getName() 获取线程名
setName(String) 修改线程名
getPriority() 获取线程优先级
setPriority(int) 修改线程优先级 java中规定线程优先级是1~10 的整数,较大的优先级能提高该线程被 CPU 调度的机率
getState() 获取线程状态 Java 中线程状态是用 6 个 enum 表示,分别为:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
isInterrupted() 判断是否被打断 不会清除 打断标记
isAlive() 线程是否存活(还没有运行完毕)
interrupt() 打断线程 如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出 InterruptedException,并清除 打断标记 ;如果打断的正在运行的线程,则会设置 打断标记 ;park 的线程被打断,也会设置 打断标记
interrupted() static 判断当前线程是否被打断 会清除 打断标记
currentThread() static 获取当前正在执行的线程
sleep(long n) static 让当前执行的线程休眠 n 毫秒,休眠时让出 CPU 的时间片给其他程序
yield() static 提示线程调度器让出当前线程对CPU的使用 主要是为了测试和调试

start 与 run

调用 run

public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug(Thread.currentThread().getName());
FileReader.read(Constants.MP4_FULL_PATH);
}
}; t1.run();
log.debug("do other things ...");
}

输出

19:39:14 [main] c.TestStart - main
19:39:14 [main] c.FileReader - read [1.mp4] start ...
19:39:18 [main] c.FileReader - read [1.mp4] end ... cost: 4227 ms
19:39:18 [main] c.TestStart - do other things ...

程序仍在 main 线程运行, FileReader.read() 方法调用还是同步的

总结

  • 直接调用 run 是在主线程中执行了 run,没有启动新的线程
  • 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码

sleep 与 yield

sleep

  • 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  • 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  • 睡眠结束后的线程未必会立刻得到执行(抢占时间片)
  • 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

yield

  • 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
  • 具体的实现依赖于操作系统的任务调度器

线程优先级

  • 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
  • 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用

join

等待一个线程执行结束

等待多个线程的结果

情况一:

package testJoin;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.demo1")
public class demo1 { static int r = 0 , r1 = 0 , r2 = 0; public static void main(String[] args) throws InterruptedException {
test2();
} private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
r1 = 10;
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(2000);
r1 = 20;
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
log.debug("join begin");
t1.join();
log.debug("t1 join end");
t2.join();
log.debug("t2 join end");
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}",r1,r2,end-start);
}
}

输出:

14:18:02 [main] c.demo1 - join begin
14:18:03 [main] c.demo1 - t1 join end
14:18:04 [main] c.demo1 - t2 join end
14:18:04 [main] c.demo1 - r1: 20 r2: 0 cost: 2008

情况二:

package testJoin;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.demo1")
public class demo1 { static int r = 0 , r1 = 0 , r2 = 0; public static void main(String[] args) throws InterruptedException {
test2();
} private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
r1 = 10;
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(2000);
r1 = 20;
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
log.debug("join begin");
t2.join();
log.debug("t2 join end");
t1.join();
log.debug("t1 join end");
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}",r1,r2,end-start);
}
}

输出:

14:19:19 [main] c.demo1 - join begin
14:19:21 [main] c.demo1 - t2 join end
14:19:21 [main] c.demo1 - t1 join end
14:19:21 [main] c.demo1 - r1: 20 r2: 0 cost: 2006

另外 join 也可以带参数,是有时效的等待。当到设定时间线程还未给出结果,直接向下运行,不再等待。如果设定时间还没到但是线程已经执行完毕,则直接向下执行,不再等待。

interrupt

打断 sleep,wait,join 的线程

这几个方法都会让线程进入阻塞状态

打断 sleep 的线程, 会清空打断状态,以 sleep 为例

package testInterrupt;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.demo1")
public class demo1 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("sleep...");
try {
Thread.sleep(5000);
//注意:sleep,wait,join等被打断并以异常形式表现出来后
// 会把打断标记重新置为 false(未打断状态)
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1"); t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
log.debug("打断标记:{}",t1.isInterrupted());
}
}

输出:

15:08:12 [t1] c.demo1 - sleep...
15:08:13 [main] c.demo1 - interrupt
15:08:13 [main] c.demo1 - 打断标记:false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at testInterrupt.demo1.lambda$main$0(demo1.java:11)
at java.lang.Thread.run(Thread.java:748) Process finished with exit code 0

打断正常运行的线程打断标记置为:true

package testInterrupt;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.demo2")
public class demo2 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true){
boolean interrupted = Thread.currentThread().isInterrupted();
if(interrupted){
log.debug("被打断了,退出循环");
break;
}
}
},"t1");
t1.start(); Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
}
}

输出:

15:17:40 [main] c.demo2 - interrupt
15:17:40 [t1] c.demo2 - 被打断了,退出循环

打断 park 线程

package testInterrupt;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = "c.demo4")
public class demo4 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("park...");
LockSupport.park();
log.debug("unpark...");
log.debug("打断状态:{}",Thread.currentThread().isInterrupted());
},"t1"); t1.start(); Thread.sleep(1000);
t1.interrupt();
}
}

输出:

14:16:21 [t1] c.demo4 - park...
14:16:22 [t1] c.demo4 - unpark...
14:16:22 [t1] c.demo4 - 打断状态:true

两阶段终止模式

package testInterrupt;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.demo3")
public class demo3 {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination tpt = new TwoPhaseTermination();
tpt.start();
Thread.sleep(3500);
tpt.stop();
}
} @Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination{
private Thread monitor; //启动监控线程
public void start(){
monitor = new Thread(() -> {
while (true){
Thread current = Thread.currentThread();
if(current.isInterrupted()){
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000);//情况1
log.debug("执行监控记录");//情况2
} catch (InterruptedException e) {
e.printStackTrace();
//重新设置打断标记
current.interrupt();
}
}
}); monitor.start();
} //终止监控线程
public void stop(){ monitor.interrupt();
}
}

输出:

15:33:02 [Thread-0] c.TwoPhaseTermination - 执行监控记录
15:33:03 [Thread-0] c.TwoPhaseTermination - 执行监控记录
15:33:04 [Thread-0] c.TwoPhaseTermination - 执行监控记录
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at testInterrupt.TwoPhaseTermination.lambda$start$0(demo3.java:29)
at java.lang.Thread.run(Thread.java:748)
15:33:04 [Thread-0] c.TwoPhaseTermination - 料理后事 Process finished with exit code 0

不推荐的方法

还有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁

方法名 static 功能说明
stop() 停止线程运行
suspend() 挂起(暂停)线程运行
resume() 恢复线程运行

Java 线程创建与常用方法的更多相关文章

  1. Java线程创建形式 Thread构造详解 多线程中篇(五)

    Thread作为线程的抽象,Thread的实例用于描述线程,对线程的操纵,就是对Thread实例对象的管理与控制. 创建一个线程这个问题,也就转换为如何构造一个正确的Thread对象. 构造方法列表 ...

  2. 多线程之:java线程创建

    java中创建线程有两种方式: 1.继承Thread类,重写run()方法,如: public class MyThread extends Thread { public void run(){ S ...

  3. 获取Java线程转储的常用方法

    1. 线程转储简介 线程转储(Thread Dump)就是JVM中所有线程状态信息的一次快照. 线程转储一般使用文本格式, 可以将其保存到文本文件中, 然后人工查看和分析, 或者使用工具/API自动分 ...

  4. java线程 — 创建和启动线程

    创建和启动线程,传统有两种方式: 方式1:继承Thread类: 方式2:实现Runnable接口: 线程类(java.lang.Thread):Thread类和Thread的子类才能称之为线程类.阅读 ...

  5. Java线程创建的两种方式

    java多线程总结一:线程的两种创建方式及优劣比较 (一)---之创建线程的两种方式 java实现多线程的两种方法的比较

  6. 【JAVA并发第二篇】Java线程的创建与运行,线程状态与常用方法

    1.线程的创建与运行 (1).继承或直接使用Thread类 继承Thread类创建线程: /** * 主类 */ public class ThreadTest { public static voi ...

  7. 使用Java 线程池的利弊及JDK自带六种创建线程池的方法

    1. 为什么使用线程池 诸如 Web 服务器.数据库服务器.文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务.请求以某种方式到达服务器,这种方式可能是通过网络协 ...

  8. Java线程池的原理及几类线程池的介绍

    刚刚研究了一下线程池,如果有不足之处,请大家不吝赐教,大家共同学习.共同交流. 在什么情况下使用线程池? 单个任务处理的时间比较短 将需处理的任务的数量大 使用线程池的好处: 减少在创建和销毁线程上所 ...

  9. java线程池原理及实现方式

    线程池的定义 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程 为什么要使用线程池 1.减少在创建和销毁线程上所花的时间以及系统资源的开 ...

随机推荐

  1. 一个chome的广告拦截小插件

    先附上下载地址:https://chromecj.com/productivity/2015-03/391.html 可以屏蔽绝大多数广告啊,浏览器用起来神清气爽! 下载完成后有一个名字为这个的文件, ...

  2. css实现半圆效果

    效果图: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF- ...

  3. Exchange日志

    Exchange日志是exchange的重要组成部分,也是管理exchang的重要指标.exchange日志产生的速度很快,而且会占用大量磁盘空间.如何管理日志成为exchange管理员的重要管理任务 ...

  4. flex布局控制最后一个元素右浮动

    可以在最后一个元素添加css属性 margin-left: auto; 例如我一排排列的元素 ,子元素并没有完全排列撑开父元素的宽度,这时候要使最后一个元素想最右 可以让最后一个元素的 margin- ...

  5. scrapy爬虫简单案例(简单易懂 适合新手)

    爬取所有的电影名字,类型,时间等信息 1.准备工作 爬取的网页 https://www.ddoutv.com/f/27-1.html 创建项目 win + R 打开cmd输入 scrapy start ...

  6. Struts2-使用forEach标签+el标签获取值栈数据

    import cn.web.body.User; import com.opensymphony.xwork2.ActionSupport; import java.util.ArrayList; i ...

  7. String类为什么被设计成不可变类

    1.享元模式: 1.共享元素模式,也就是说:一个系统中如果有多处用到了相同的一个元素,那么我们应该只存储一份此元素,而让所有地方都引用这一个元素. 2.Java中String就是根据享元模式设计的,而 ...

  8. 函数 装饰器 python

    今日内容概要 1.闭包函数 2.闭包函数的实际应用 3.装饰器简介(重点加难点) 4.简易版本装饰器 5.进阶版本装饰器 6.完整版本装饰器 7.装饰器模板(拷贝使用即可) 8.装饰器语法糖 9.装饰 ...

  9. 『现学现忘』Git基础 — 3、Git介绍

    目录 1.Git的历史 2.Git的特点 3.Git在项目协作开发中所解决的问题 1.Git的历史 Git是目前世界上最先进的分布式版本控制系统,开源.免费. Git 是 Linus (林纳斯)为了帮 ...

  10. android软件简约记账app开发day01-今日收支明细的界面绘制

    android软件简约记账app开发day01-今日收支明细的界面绘制 导入素材 导入在阿里iconfront图标库下载的字体图标分为大小两种,分别导入到项目目录mipmap-hdpi和mipmap- ...