JUC并发编程之Java线程(二)
二、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接口:
- 定义一个线程任务类实现Callable接口,申明线程执行结果类型
- 重写Callable接口的call方法,这个方法可以直接返回执行结果
- 创建一个Callable的线程任务对象
- 把Callable的线程任务对象包装成一个未来任务对象
- 把未来任务对象包装成线程对象
- 调用线程的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
- 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
- 睡眠结束后的线程未必会立刻得到执行
- 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
yield
- 调用yield会让当前线程从Running进入Runnable就绪状态,然后调度执行其他线程
- 具体实现依赖于操作系统的任务调度器
线程优先级
- 线程优先级会提示(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里无法区分,仍然认为是可运行)
- BLOCKED、WAITING、TIME_WAITING都是Java API层面对 阻塞状态的细分
- TERMINATED当线程代码运行结束
JUC并发编程之Java线程(二)相关推荐
- Java并发编程之Java线程池
Java线程池: 线程池的核心配置参数: //线程等待任务的超时时间,当线程池的线程个数超过corePoolSize时生效,当线程等待任务的时间超过keepAliveTime时,线程池会停止超过cor ...
- 白话说编程之java线程
白话说编程之java线程 线程和进程: 进程: 线程: 线程和进程的区别: 详解多线程: 并发 为什么使用并发 并发的执行原理 并行 线程的五种状态: 创建状态: 就绪状态: 运行状态: 阻塞状态: ...
- 并发编程之 Executor 线程池原理与源码解读
并发编程之 Executor 线程池原理与源码解读 线程是调度 CPU 资源的最小单位,线程模型分为 KLT 模型与 ULT 模型,JVM使用的是 KLT 模型.java线程与 OS 线程保持 1:1 ...
- JUC并发编程之Callable接口、JUC三大辅助类
目录 8. Callable接口 8.1 创建线程的多种方式 8.2 概述 8.3 用Callable接口创建Thred线程 8.4 小结(重点) 9. JUC 三大辅助类 9.1 概述 9.2 减少 ...
- Java 并发编程之 ThreadLocal 线程局部变量
ThreadLocal 通过get和set方法,为每个使用该变量的线程提供一个独立的副本,使得线程安全的共享某个变量:使用 set 方法设置变量后,一定要记得及时使用 remove 方法清理,否则多线 ...
- 并发编程之Java内存模型
在介绍Java内存模型之前,先来了解一下为什么要有内存模型,以及内存模型是什么.然后我们基于对内存模型的了解,学习Java内存模型以及并发编程的三大特性. 为什么要有内存模型 在计算机中,所有的运算操 ...
- 并发编程之 Java 内存模型 + volatile 关键字 + Happen-Before 规则
前言 楼主这个标题其实有一种作死的味道,为什么呢,这三个东西其实可以分开为三篇文章来写,但是,楼主认为这三个东西又都是高度相关的,应当在一个知识点中.在一次学习中去理解这些东西.才能更好的理解 Jav ...
- 并发编程之Executor线程池原理与源码解读
1. 线程池 "线程池",顾名思义就是一个线程缓存,线程是稀缺资源,如果被无限制的创建,不 仅会消耗系统资源,还会降低系统的稳定性,因此Java中提供线程池对线程进行统一分配. 调 ...
- JUC并发编程(1.Java线程)
博客指南
最新文章
- 因为一条SQL,程序员差点被祭天......
- python enumerate_python中enumerate的用法实例解析
- 【两种方法】基础实验4-2.7 修理牧场 (25 分)
- hadoop2.2支持snappy压缩安装及配置
- jQuery1.4新特性
- 谷歌chrome模拟手机浏览网页:iPhone/Android
- C#中split分隔字符串的应用
- $.ajax data怎么处理_AJAX
- MySQL关于Table cache设置,看这一篇就够了
- Windows环境下查看Java进程ID,找到java程序对应的进程pid
- 洛谷P3275 [SCOI2011]糖果
- 报错:Avoid adding reactive properties to a Vue instance or its root $data at runtime - declare it upfr
- c语言自学方式,c语言学习方法
- 微信小程序报错40163-“errmsg“解决方案
- 快速查看南京商品房销售信息
- 【Android】关于WIFI局域网的手机摄像头当视频监控用实现方案详解
- 用 RIME 定制输入法
- 文章结构层次序数(序号)的规范要求
- ATOM基础教程一linter-php配置(12)
- 使用realsense D435i实现机械臂对物体的自动抓取总结