本文源码: GitHub. 点这里 https://github.com/cicadasmile/java-base-parent || GitEE. 点这里 https://gitee.com/cicadasmile/java-base-parent
一, 线程基本机制
1, 概念描述
并发编程的特点是: 可以将程序划分为多个分离且独立运行的任务, 通过线程来驱动这些独立的任务执行, 从而提升整体的效率. 下面提供一个基础的演示案例.
2, 应用案例
场景: 假设有一个容器集合, 需要拿出容器中的每个元素, 进行加工处理, 一般情况下直接遍历就好, 如果数据偏大, 可以根据线程数量对集合切割, 每个线程处理一部分数据, 这样处理时间就会减少很多.
- public class ExtendThread01 {
- public static void main(String[] args) {
- List<Object> dataList = new ArrayList<>() ;
- dataList.add("A");
- dataList.add("B");
- dataList.add("C");
- // 把一个大的集合按照每个子集合的 2 个元素切割
- List<List<Object>> splitList = splitList(dataList,2);
- for (List<Object> list:splitList){
- System.out.println(list);
- }
- // 多线程处理
- for (List<Object> childList:splitList){
- ListTask listTask = new ListTask(childList) ;
- Thread runThread = new Thread(listTask);
- runThread.start();
- }
- }
- /**
- * List 集合切割
- */
- private static List<List<Object>> splitList (List<Object> list, int childSize) {
- if (list == null || list.size() == 0 || childSize <1) {
- return null;
- }
- List<List<Object>> result = new ArrayList<>();
- int size = list.size();
- int count = (size + childSize - 1) / childSize ;
- for (int i = 0; i <count; i++) {
- List<Object> subList = list.subList(i * childSize, ((i + 1) * childSize> size ? size : childSize * (i + 1)));
- result.add(subList);
- }
- return result;
- }
- }
- class ListTask implements Runnable {
- private List<Object> list ;
- public ListTask (List<Object> list){this.list=list;}
- @Override
- public void run() {
- for (Object object:list){
- System.out.println(Thread.currentThread().getName()+"=="+object);
- }
- }
- }
注意: 这里案例只是对场景原理的实现, 在开发中, 是不允许这种操作的, 需要使用线程池处理, 后续会说. 如果集合没有控制好, 导致大量创建 Thread 线程, 导致内存溢出.
二, 线程停止启动
1, 基础流程
线程启动后执行任务方法, 在执行过程中可以被阻塞, 休眠, 唤醒, 停止等一系列状态操作.
线程休眠作用: 当线程一部分任务执行完毕后进入休眠 (阻塞) 状态, 线程调度器可以切换到另外线程, 这对分布任务的执行相对公平.
2, 使用案例
- public class ExtendThread02 {
- public static void main(String[] args) {
- StopThread stopThread = new StopThread() ;
- stopThread.start();
- // 标记当前线程停止信号, 且抛出中断异常, 但没有停止
- stopThread.interrupt();
- // 判断当前线程是否已经是终止状态
- System.out.println("1=="+stopThread.isInterrupted());
- // 清除当前线程的终止信号
- System.out.println("2=="+stopThread.interrupted());
- // 再次判断当前线程状态
- System.out.println("3=="+stopThread.isInterrupted());
- System.out.println("main end ...");
- }
- }
- class StopThread extends Thread {
- @Override
- public void run() {
- for (int i = 0 ; i <10 ; i++){
- try {
- System.out.println(Thread.currentThread().getId()+"="+i);
- // 线程阻塞 1 秒
- Thread.sleep(1000);
- } catch (InterruptedException e){
- e.printStackTrace();
- }
- }
- }
- }
3, 核心方法
sleep(long millis): 线程休眠指定的时间, 进入阻塞状态;
interrupt(): 切换线程为中断状态, 抛出中断异常, 不会停止线程, 可以监视线程的中断状态并定义执行策略.
interrupted(): 清除调用该方法线程的中断状态, 也不会影响线程的执行, 且返回当前执行'stopThread.interrupted()'的线程是否中断, 这里就是指 main 线程是否中断.
isInterrupted(): 判断调用该方法的线程是否已经是中断状态.
补刀一句: 线程的这几个方法极其容易混淆, 需要断点源码追踪一下看看, 进入源码方法, 调用相关 API 查看一下状态.(附断点图一张:)
三, 线程优先级
1, 基础概念
CPU 执行和处理线程的顺序是不确定的, 但是线程调度器倾向执行线程优先级高的线程, 线程优先级高说明获取 CPU 资源的概率高, 或者获取的执行时间分片多, 但不代表优先级低的一定最后执行.
2, 使用案例
- public class ExtendThread03 {
- public static void main(String[] args) {
- Priority01 priority01 = new Priority01();
- priority01.start();
- System.out.println("priority01="+priority01.getPriority());
- Priority02 priority02 = new Priority02();
- priority02.start();
- System.out.println("priority02="+priority02.getPriority());
- priority01.setPriority(10);
- priority02.setPriority(1);
- }
- }
- class Priority01 extends Thread {
- @Override
- public void run() {
- for (int i = 0 ; i < 100 ; i++){
- System.out.println(Thread.currentThread().getName()+";i="+i);
- }
- }
- }
- class Priority02 extends Thread {
- @Override
- public void run() {
- for (int a = 0 ; a < 100 ; a++){
- System.out.println(Thread.currentThread().getName()+";a="+a);
- }
- }
- }
注意: 优先级范围[MAX_PRIORITY=10,MIN_PRIORITY=1], 如果超出范围会抛出 IllegalArgumentException 异常.
建议: 通常实际开发中, 是不允许轻易修改线程运行的参数, 容易引发认知之外的异常.
四, 线程加入
1, 基本概念
如果在线程 A 中, 执行线程 B 的加入方法, 那么 A 线程就会等待线程 B 执行完毕再返回继续执行.
2, 使用案例
- public class ExtendThread04 {
- public static void main(String[] args) {
- JoinThreadA joinThreadA = new JoinThreadA() ;
- joinThreadA.start();
- }
- }
- class JoinThreadA extends Thread {
- @Override
- public void run() {
- System.out.println("缺水中...");
- JoinThreadB joinThreadB = new JoinThreadB() ;
- joinThreadB.start();
- try{
- joinThreadB.join();
- } catch (Exception e){
- e.printStackTrace();
- }
- System.out.println("喝水中...");
- }
- }
- class JoinThreadB extends Thread {
- @Override
- public void run() {
- System.out.println("买水中...");
- try{
- TimeUnit.SECONDS.sleep(2);
- } catch (Exception e){
- e.printStackTrace();
- }
- System.out.println("买到水...");
- }
- }
注意: 可以设置线程的加入时间 join(long), 毕竟等不到雪月风花, 人生都是有时差, 只能后会无期了.
五, 本地线程
1, 基本概念
本地的线程变量, 底层维护 ThreadLocalMap 存储值:
- static class Entry extends WeakReference<ThreadLocal<?>> {
- Object value;
- Entry(ThreadLocal<?> k, Object v) {
- super(k);
- value = v;
- }
- }
即以 Key-Value 键值对的方式存储数据. 如果对集合容器的源码熟悉的话, 这个 Entry 就是似曾相识感觉.
2, 使用案例
- public class ExtendThread05 {
- private static final ThreadLocal<Long> threadLocal = new ThreadLocal<>() ;
- private static void initBegin (){
- threadLocal.set(System.currentTimeMillis());
- }
- private static Long overTime (){
- return System.currentTimeMillis()-threadLocal.get();
- }
- public static void main(String[] args) throws Exception {
- ExtendThread05.initBegin();
- TimeUnit.SECONDS.sleep(3);
- System.out.println(ExtendThread05.overTime());
- }
- }
ThreadLocal 提供线程内存储变量的能力, 并且绑定到当前线程, 通过 get 和 set 方法就可以得到和设置当前线程对应的值. 这个在 web 开发中是常见的应用.
六, 守护线程
1, 基本概念
守护线程是支持辅助型线程, 主要在程序中起到调度和支持性作用, 当 Jvm 中非守护线程全部结束, 守护线程也就会结束.
2, 使用案例
- public class ExtendThread06 {
- public static void main(String[] args) throws Exception {
- InputStreamReader is = new InputStreamReader(System.in);
- BufferedReader br = new BufferedReader(is);
- String value = br.readLine();
- CheckThread checkThread = new CheckThread(value) ;
- checkThread.setDaemon(true);
- checkThread.start();
- System.out.println("Main End ...");
- }
- }
- class CheckThread extends Thread {
- private String spell ;
- public CheckThread (String spell){
- this.spell = spell ;
- }
- @Override
- public void run() {
- if (spell.startsWith("cs")){
- System.out.println(spell+": 输入正确");
- } else {
- System.out.println(spell+": 输入错误");
- }
- try {
- TimeUnit.SECONDS.sleep(10);
- } catch (InterruptedException e){
- e.printStackTrace();
- }
- }
- }
注意: 守护线程需要显式调用 setDaemon(true); 方法, 这里可以看到 main 线程结束, 整合程序就结束了, 丝毫理会休眠中的守护线程. 如果打断非守护线程的休眠, 试试会不会抛你一脸异常?
七, 线程异常处理
1, 机制描述
按照 Java 中异常处理机制, 抛异常逐级降低, 线程的任务方法 run 没有抛异常, 那重写或者实现的方法自然不能直接 throws 异常出去. 多线程中推荐捕获异常, 可以针对性处理机制.
2, 使用案例
- public class ExtendThread07 {
- public static void main(String[] args) {
- TryThread tryThread = new TryThread();
- tryThread.setName("try-name");
- // 定义运行中异常处理策略
- MyExe myExe = new MyExe() ;
- tryThread.setUncaughtExceptionHandler(myExe);
- tryThread.start();
- }
- }
- class TryThread extends Thread {
- @Override
- public void run() {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e){
- e.printStackTrace();
- }
- // 如何处理这里异常?
- Integer.parseInt("cicada") ;
- }
- }
- class MyExe implements Thread.UncaughtExceptionHandler {
- @Override
- public void uncaughtException(Thread t, Throwable e) {
- System.out.println(t.getName()+"; 异常:"+e.getMessage());
- }
- }
通过实现 UncaughtExceptionHandler 接口, 并且线程要指定自定义异常处理对象, 也可以处理未检查的异常.
八, 源代码地址
GitHub. 地址
https://github.com/cicadasmile/java-base-parent
GitEE. 地址
https://gitee.com/cicadasmile/java-base-parent
来源: https://www.cnblogs.com/cicada-smile/p/12498742.html