二、Java线程

2.1 创建和运行线程

方法一:Thread创建线程方式:

  • 继承Thread类
  • 匿名内部类方式
public class CreateThread01 {public static void main(String[] args) {// 匿名内部类方式Thread t1 = new Thread("t1"){// run 方法内实现了要执行的任务public void run() {System.out.println("匿名内部类方式....."+ Thread.currentThread().getName());}};t1.start();// 继承Thread类MyThread t2 = new MyThread();t2.start();}
}class MyThread extends Thread{@Overridepublic void run() {System.out.println("创建类继承Thread接口....." + Thread.currentThread().getName());}
}

方法二:实现Runnable配合Thread

  • 实现Runnale接口
  • 匿名内部类方式
public class CreateThread02 {public static void main(String[] args) {// 实现Runnale接口MyThread1 myThread1 = new MyThread1();Thread t1 = new Thread(myThread1);t1.start();Runnable runnable = new Runnable() {@Overridepublic void run() {System.out.println("匿名内部类的方式....."+Thread.currentThread().getName());}};Thread t2 = new Thread(runnable);t2.start();}
}// 实现Runnale接口
class MyThread1 implements Runnable{@Overridepublic void run() {System.out.println("使用Runnable接口创建线程......"+ Thread.currentThread().getName());}
}

方式一和方式二的比较:

  • 开发中优先选择实现Runnable接口的方式
  • 原因:
    (1)实现的方式没有类的单继承性的局限性
    (2)实现的方式更适合来处理多个线程有共享数据的情况
  • 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中

方法三:实现Callable接口

实现Callable接口:

  1. 定义一个线程任务类实现Callable接口,申明线程执行结果类型
  2. 重写Callable接口的call方法,这个方法可以直接返回执行结果
  3. 创建一个Callable的线程任务对象
  4. 把Callable的线程任务对象包装成一个未来任务对象
  5. 把未来任务对象包装成线程对象
  6. 调用线程的start()方法启动线程
  • public FutureTask(Callable<V> callable):未来任务对象,在线程执行完后得到线程的执行结果。

  • FutureTask就是Runnable对象,因为Thread类只能执行Runnable实例的任务对象,所以把Callable包装成未来任务对象

  • public V get():同步等待 task 执行完毕的结果,如果在线程中获取另一个线程执行结果,会阻塞等待,用于线程同步

    • get()线程会阻塞等待任务完成
    • run()执行完后会把结果设置到FutureTask的一个成员变量,get()线程可以获取到该变量的值
public class CreateThread03 {public static void main(String[] args)  {// 第一种将实现Callable接口类传入FutureTaskFutureTask<String> task = new FutureTask<String>(new MyCallable());new Thread(task,"t3").start();// 获取call方法返回的结果(正常/异常结果)String result = null;try {result = task.get();System.out.println(result);} catch (Exception e) {e.printStackTrace();}// 第二种内部类实现FutureTask<String> task1 = new FutureTask<String>(() -> {return Thread.currentThread().getName() + "->" +"FutureTask";});new Thread(task1,"t4").start();try {result = task1.get();System.out.println(result);} catch (Exception e) {e.printStackTrace();}}
}class MyCallable implements Callable<String> {@Override  //重写线程任务类方法public String call() throws Exception {return Thread.currentThread().getName() + "->" +"实现Callable接口";}
}

优点:

  • 可以定义返回值

  • 可以抛出异常

方法四:利用线程池创建

使用 ThreadPoolExecutor 创建线程池,并从线程池中获取线程用于执行任务。在 JUC 中,Executor 框架已经实现了几种线程池,以 Executor 的 newFixedThreadPool 来作为 Demo 的展示。

public class CreateThread4 {public static void main(String[] args) throws ExecutionException,InterruptedException {System.out.println("----程序开始运行----");Date date1 = new Date();int taskSize = 5;// 创建一个线程池ExecutorService pool = Executors.newFixedThreadPool(taskSize);// 创建多个有返回值的任务List<Future> list = new ArrayList<Future>();for (int i = 0; i < taskSize; i++) {Callable c = new MyCallable1(i + " ");// 执行任务并获取Future对象Future f = pool.submit(c);// System.out.println(">>>" + f.get().toString());list.add(f);}// 关闭线程池pool.shutdown();// 获取所有并发任务的运行结果for (Future f : list) {// 从Future对象上获取任务的返回值,并输出到控制台System.out.println(">>>" + f.get().toString());}Date date2 = new Date();System.out.println("----程序结束运行----,程序运行时间【"+ (date2.getTime() - date1.getTime()) + "毫秒】");}
}class MyCallable1 implements Callable<Object> {private String taskNum;MyCallable1(String taskNum) {this.taskNum = taskNum;}public Object call() throws Exception {System.out.println(">>>" + taskNum + "任务启动");Date dateTmp1 = new Date();Thread.sleep(1000);Date dateTmp2 = new Date();long time = dateTmp2.getTime() - dateTmp1.getTime();System.out.println(">>>" + taskNum + "任务终止");return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";}
}
  • ExecutorService、Callable、Future 实际上都是属于 Executor 框架。

  • 线程池支持有返回结果和无返回结果的任务,有返回值的任务必须实现Callable接口,无返回值的任务必须实现Runnable接口。

  • 对于有结果的任务,执行 Callable 任务后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到Callable任务返回的 Object 了,

  • 但需要注意的是:get方法是阻塞的,如果线程未返回结果,那么 get() 方法会一直等待,直到有结果返回或者超时。

2.2 查看进程线程的方法

windows

- 任务管理器可以查看线程和进程数,也可以用来杀死进程
- tasklist      查看进程
- taskkill      杀死进程

linux

ps -fe    查看所有进程
ps -fT -p PID   查看某个进程(PID)的所有线程
kill    杀死进程
top     按大写H切换是否显示线程
top -H -p PID   查看某个进程(PID)的所有线程

Java

jps    查看所有java进程
jstack pid   查看某个java进程(Pid)的所有进程状态
jconsole   查看某个java进程中线程运行情况(图形界面)

jconsole 远程监控配置

  • 需要以如下方式运行你的 java 类

    java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote -
    Dcom.sun.management.jmxremote.port=`连接端口` -Dcom.sun.management.jmxremote.ssl=是否安全连接 -
    Dcom.sun.management.jmxremote.authenticate=是否认证 java类
  • 修改/etc/hosts文件将127.0.0.1映射至主机名

  • 如果要认证

    • 复制 jmxremote.password 文件
    • 修改 jmxremote.password 和 jmxremote.access 文件的权限为 600 即文件所有者可读写
    • 连接时填入 controlRole(用户名),R&D(密码)

2.3原理之线程运行

栈与栈帧

Java Virtual Machine Stacks (Java 虚拟机栈)

​ JVM由堆、栈、方法去所组成,每个线程启动后,虚拟机就会为其分配一块栈内存

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

线程上下文切换

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

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

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

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

2.4 常用api以及详解

方法 说明
start() 启动一个新线程,Java虚拟机调用此线程的 run 方法
run() 线程启动后调用该方法
join() 等待线程运行结束
join(long n) 等待线程运行结束,最多等n毫秒
getId() 获得线程长整型id id是唯一的
getName() 获得线程名
getName(String name) 修改线程名
getPriority() 获得线程优先级
setPriority(int n) 修改线程优先级,java中规定线程优先级是1-10的整数,较大的优先级能提高该线程被CPU调度的几率,注意:这不是一定按照优先级的大小来进行调度的
getState() 获取线程的状态,Java 中线程状态是用 6 个 enum 表示,分别为: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
isInterrupted() 判断是否被打断,不会清楚打断标记
isAlive() 线程是否存活,即是否运行完毕
interrupt() 打断线程,如果打断线程正在sleep、wait、join中会导致被打断的线程抛出出 InterruptedException,并清除打断标记;如果被打断的是正在运行的线程,则会设置打断标记,则会设置打断标记;park线程被打断,也会设置打断标记
isInterrupt() 判断当前线程是否被打断,会清楚打断标记
currentThread() 获取当前正在执行的线程
sleep(long n) 让当前线程休眠n毫秒,休眠时让出cpu时间片给其他线程,睡眠结束后的线程未必会立刻得到执行
yield() 提示线程调度器让出当前线程对CPU的使用

start与run

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

sleep与yield

sleep

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

yield

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

线程优先级

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

join方法

@Slf4j
public class ThreadJoinTest {static int r = 0;public static void main(String[] args) {test1();}private static void test1(){log.debug("开始");Thread t1 = new Thread(() -> {log.debug("开始");try {sleep(1);log.debug("结束");r = 10;} catch (InterruptedException e) {e.printStackTrace();}});t1.start();log.debug("结束:{}",r);log.debug("结束");}
}//注意: sleep用的是工具类
public class Sleeper {public static void sleep(int i) {try {TimeUnit.SECONDS.sleep(i);} catch (InterruptedException e) {e.printStackTrace();}}public static void sleep(double i) {try {TimeUnit.MILLISECONDS.sleep((int) (i * 1000));} catch (InterruptedException e) {e.printStackTrace();}}
}

打印结果为什么为0,而不是打印r=10 ?

  • 这个问题是因为主线程和t1线程是并行的,t1线程需要1秒才能算出r=10

  • 而主线程直接就打印出了r的值,所以只能打印出r=0

解决方法:

  • 我们能不能让主线程等待t1线程运行完,在打印出值。这就用到join()
@Slf4j
public class ThreadJoinTest {static int r = 0;public static void main(String[] args) throws InterruptedException {test1();}private static void test1() throws InterruptedException {log.debug("开始");Thread t1 = new Thread(() -> {log.debug("开始");try {sleep(1);log.debug("结束");r = 10;} catch (Exception e) {e.printStackTrace();}});t1.start();// 在主线程打印r值的前面加一个join,作用是等待t1线程运行完,在继续往下运行t1.join();log.debug("结束:{}",r);log.debug("结束");}
}

还有一个有时效的join方法

没等够时间

@Slf4j
public class ThreadJoinTest {static int r = 0;public static void main(String[] args) throws InterruptedException {test1();}private static void test1() throws InterruptedException {log.debug("开始");Thread t1 = new Thread(() -> {log.debug("开始");try {sleep(2);  // 睡2slog.debug("结束");r = 10;} catch (Exception e) {e.printStackTrace();}});t1.start();// 等1st1.join(1000);log.debug("结束:{}",r);log.debug("结束");}
}

等够时间

@Slf4j
public class ThreadJoinTest {static int r = 0;public static void main(String[] args) throws InterruptedException {test1();}private static void test1() throws InterruptedException {log.debug("开始");Thread t1 = new Thread(() -> {log.debug("开始");try {sleep(2);  // 睡2slog.debug("结束");r = 10;} catch (Exception e) {e.printStackTrace();}});t1.start();// 等3st1.join(3000);log.debug("结束:{}",r);log.debug("结束");}
}

interrupt方法

打断sleep线程

打断 sleep,wait,join 的线程

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

打断sleep的线程,会清空打断标记

@Slf4j
public class InterruptTest01 {public static void main(String[] args) {Runnable r1 = (() -> {// sleep 导入工具类  现在是以秒为单位sleep(1);});Thread t1 = new Thread(r1,"t1");t1.start();sleep(0.5);t1.interrupt();log.debug("打断状态:{}",t1.isInterrupted());}
}

  • 打断sleep的线程会抛出InterruptedException异常
  • 并且清空打断标记

打断正常线程

打断正常运行的线程,不会清空打断标记

@Slf4j
public class InterruptTest02 {public static void main(String[] args) {Runnable r1 = (() -> {while(true){Thread currentThread = Thread.currentThread();boolean interrupted = currentThread.isInterrupted();if (interrupted){log.debug("打断状态:{}",interrupted);break;}}});Thread t1 = new Thread(r1,"t1");t1.start();sleep(0.5);t1.interrupt();}
}

注意:

  • 并不是说我打断这个正常运行的线程,他就停止了,如果光打断正常运行的线程,不判断打断状态的话,并不会停止,只是打断标记为true而已。

打断park线程

park: 暂停当前线程,处于 WAIT 状态

unpark: 既可以在 park 之前调用或之后调用,都是用来恢复某个线程的运行,简单的说,调用 unpark 后再调用 park 线程依然不会暂停,类似提前“解毒”。

打断 park 线程, 不会清空打断状态

@Slf4j
public class InterruptTest03 {public static void main(String[] args) {Runnable r1 = (() -> {log.debug("park....");LockSupport.park();log.debug("打断状态:{}",Thread.currentThread().isInterrupted());});Thread t1 = new Thread(r1,"t1");t1.start();sleep(0.5);t1.interrupt();}
}

注意:并不会停止运行,只是将打断标记设置为true

但是如果打断标记已经是true,则park将会失去作用

@Slf4j
public class InterruptTest03 {public static void main(String[] args) {Runnable r1 = (() -> {for (int i =0; i < 5; i++){log.debug("park....");LockSupport.park();log.debug("打断状态:{}",Thread.currentThread().isInterrupted());}});Thread t1 = new Thread(r1,"t1");t1.start();sleep(0.5);t1.interrupt();}
}

模式之两阶段终止

在一个线程t1中如何终止线程t2,并且能让t2在终止前,能有一个操作的机会。

利用isInterrupt()方法

interrupt 可以打断正在执行的线程,无论这个线程是在 sleep,wait,还是正常运行。

@Slf4j
public class InterrruptApplyTest {public static void main(String[] args) throws InterruptedException {TPTInterrupt t1 = new TPTInterrupt();t1.start();Thread.sleep(3500);log.debug("调用封装的stop");t1.stop();}
}@Slf4j
class TPTInterrupt{private Thread thread;public void start(){thread = new Thread(()-> {while(true){Thread current = Thread.currentThread();if (current.isInterrupted()){log.debug("最后的操作!");break;}try {// 这里的sleep并不是封装的 因为我们想让他抛出异常才能进行我们的下一步操作sleep(1000);log.debug("业务操作!");} catch (InterruptedException e) {// 防止在睡的时候被主线程打断,我们在这里再次打断就可以继续运行current.interrupt();}}},"工作线程");thread.start();}public void stop(){thread.interrupt();}
}
利用停止标记

volatile:保证了可见性、有序性

  • 保证此变量对所有线程的可见性,当一个线程修改了这个变量的值,volatile保证了新值能立即同步到主内存中,每次使用前从主内存中取值
  • 禁止指令重排序优化,这个操作相当于一个内存屏障,指令重排序时不能把后面的指令重排序到内存屏障之前的位置,在懒汉式的优化中也经常这样使用。
@Slf4j
public class InterrruptApplyTest {public static void main(String[] args) throws InterruptedException {TPTVolatile t1 = new TPTVolatile();t1.start();Thread.sleep(3500);log.debug("调用封装的stop");t1.stop();}
}@Slf4j
class TPTVolatile{private Thread thread;// 停止标记用 volatile 是为了保证该变量在多个线程之间的可见// 主线程把它改成true,对t1线程可见private volatile boolean stop = false;public void start(){thread = new Thread(()-> {while(true){Thread current = Thread.currentThread();if (stop){log.debug("最后的操作!");break;}try {// 这里的sleep并不是封装的 因为我们想让他抛出异常才能进行我们的下一步操作sleep(1000);log.debug("业务操作!");} catch (InterruptedException e) {// 防止在睡的时候被主线程打断,我们在这里再次打断就可以继续运行current.interrupt();}}},"工作线程");thread.start();}public void stop(){stop = true;}
}

加粗样式

2.5 主线程和守护线程

默认情况下,Java进程需要等待所有线程都运行结束,才会结束,有一种特殊线程叫做守护线程,只要其他非守护线程运行结束了,即使守护线程里的代码没执行完,也会强制结束。

@Slf4j
public class GuardThreadTest {public static void main(String[] args) {log.debug("开始运行...");Thread t1 = new Thread(() -> {log.debug("开始运行....");// 2s后sleep(2);log.debug("运行结束....");},"daemon");// 设置该线程是守护线程t1.setDaemon(true);t1.start();sleep(1);log.debug("运行结束...");}
}

主线程结束后,守护线程并没有执行 运行结束

  • 垃圾回收器线程就是一种守护线程
  • Tomcat中的Acceptor和Poller线程都是守护线程,所以Tomcat接收到shutdown命令后,不会等待它门处理完当前请求

2.6五种状态

操作系统层面

  • 初始状态 仅仅是在语言层面创建了线程对象,还未与操作系统的线程关联
  • 可运行状态 (就绪状态) 指线程已经被创建(与操作系统的线程关联),可以由CPU调度执行
  • 运行状态 指已获取了CPU时间片运行中的状态
  • 阻塞状态
    • 如果调用了阻塞API,如BIO读写文件,这时该线程实际不会用到CPU,会导致线程上下文切换,进入阻塞状态
    • 等BIO操作完,会由操作系统唤醒阻塞的线程,转换至 可运行状态
    • 可运行状态的区别是,对 阻塞状态的线程来说只要一直不唤醒,调度器就一直不会考虑调度它们
  • 终止状态表示线程已经执行完毕,生命周期已经结束,不会再转换为奇它状态

2.7六种状态

Java层面 根据 Thread.State 枚举,分为六种状态

  • NEW 线程刚被创建,但是还没调用start()方法
  • RUNNABLE 当调用了start()方法之后,在Java API层面的 RUNNABLE状态涵盖了操作系统层面的 可运行状态运行状态阻塞状态(由于BIO导致的线程阻塞,在Java里无法区分,仍然认为是可运行)
  • BLOCKEDWAITINGTIME_WAITING都是Java API层面对 阻塞状态的细分
  • TERMINATED当线程代码运行结束

JUC并发编程之Java线程(二)相关推荐

  1. Java并发编程之Java线程池

    Java线程池: 线程池的核心配置参数: //线程等待任务的超时时间,当线程池的线程个数超过corePoolSize时生效,当线程等待任务的时间超过keepAliveTime时,线程池会停止超过cor ...

  2. 白话说编程之java线程

    白话说编程之java线程 线程和进程: 进程: 线程: 线程和进程的区别: 详解多线程: 并发 为什么使用并发 并发的执行原理 并行 线程的五种状态: 创建状态: 就绪状态: 运行状态: 阻塞状态: ...

  3. 并发编程之 Executor 线程池原理与源码解读

    并发编程之 Executor 线程池原理与源码解读 线程是调度 CPU 资源的最小单位,线程模型分为 KLT 模型与 ULT 模型,JVM使用的是 KLT 模型.java线程与 OS 线程保持 1:1 ...

  4. JUC并发编程之Callable接口、JUC三大辅助类

    目录 8. Callable接口 8.1 创建线程的多种方式 8.2 概述 8.3 用Callable接口创建Thred线程 8.4 小结(重点) 9. JUC 三大辅助类 9.1 概述 9.2 减少 ...

  5. Java 并发编程之 ThreadLocal 线程局部变量

    ThreadLocal 通过get和set方法,为每个使用该变量的线程提供一个独立的副本,使得线程安全的共享某个变量:使用 set 方法设置变量后,一定要记得及时使用 remove 方法清理,否则多线 ...

  6. 并发编程之Java内存模型

    在介绍Java内存模型之前,先来了解一下为什么要有内存模型,以及内存模型是什么.然后我们基于对内存模型的了解,学习Java内存模型以及并发编程的三大特性. 为什么要有内存模型 在计算机中,所有的运算操 ...

  7. 并发编程之 Java 内存模型 + volatile 关键字 + Happen-Before 规则

    前言 楼主这个标题其实有一种作死的味道,为什么呢,这三个东西其实可以分开为三篇文章来写,但是,楼主认为这三个东西又都是高度相关的,应当在一个知识点中.在一次学习中去理解这些东西.才能更好的理解 Jav ...

  8. 并发编程之Executor线程池原理与源码解读

    1. 线程池 "线程池",顾名思义就是一个线程缓存,线程是稀缺资源,如果被无限制的创建,不 仅会消耗系统资源,还会降低系统的稳定性,因此Java中提供线程池对线程进行统一分配. 调 ...

  9. JUC并发编程(1.Java线程)

    博客指南

最新文章

  1. 因为一条SQL,程序员差点被祭天......
  2. python enumerate_python中enumerate的用法实例解析
  3. 【两种方法】基础实验4-2.7 修理牧场 (25 分)
  4. hadoop2.2支持snappy压缩安装及配置
  5. jQuery1.4新特性
  6. 谷歌chrome模拟手机浏览网页:iPhone/Android
  7. C#中split分隔字符串的应用
  8. $.ajax data怎么处理_AJAX
  9. MySQL关于Table cache设置,看这一篇就够了
  10. Windows环境下查看Java进程ID,找到java程序对应的进程pid
  11. 洛谷P3275 [SCOI2011]糖果
  12. 报错:Avoid adding reactive properties to a Vue instance or its root $data at runtime - declare it upfr
  13. c语言自学方式,c语言学习方法
  14. 微信小程序报错40163-“errmsg“解决方案
  15. 快速查看南京商品房销售信息
  16. 【Android】关于WIFI局域网的手机摄像头当视频监控用实现方案详解
  17. 用 RIME 定制输入法
  18. 文章结构层次序数(序号)的规范要求
  19. ATOM基础教程一linter-php配置(12)
  20. 使用realsense D435i实现机械臂对物体的自动抓取总结

热门文章

  1. python中matplotlib库的学习1
  2. sql多表联查(内连接、外连接)、实验八表联查
  3. 卖产品如何在抖音引流?如何利用抖音引流卖货?
  4. 新零售社交电商系统开发社交新零售电商系统模式
  5. 2019计算机复试平均分,19考研全国平均分公布,20考研难度分析!
  6. java实习生面试被问的较多的面试题(附参考答案)
  7. 并不对劲的线段树套平衡树
  8. AutoCut: 一款通过字幕自动剪辑视频的神器
  9. printf 输出彩色
  10. 替尼泊苷多层包衣白蛋白纳米粒/人血清白蛋白-聚己内酯纳米的制备