线程在不同的层次不同的说法与环境,就Java语言层次来说

线程状态

就线程来说不同的层面有不同的说法。
底层P(S),V(S)资源操作,编码层次对象锁操作,在Java层面对线程的状态定义如下
Java的枚举类Thread.State定义了6个线程的状态
image.png
线程状态。 线程可以处于以下状态之一:

  • NEW 尚未启动的线程处于此状态。
  • RUNNABLE 在 Java 虚拟机中执行的线程处于这种状态。
  • BLOCKED 被阻塞等待监视器锁的线程处于这种状态。
  • WAITING 无限期等待另一个线程执行特定操作的线程处于此状态。
  • TIMED_WAITING 等待另一个线程执行操作达指定等待时间的线程处于此状态。
  • TERMINATED 已退出的线程处于此状态。
    一个线程在给定的时间点只能处于一种状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。

线程之间的转换不多BB了,不想画图了

验证NEW、RUNNABLE、TERMINATED

NEW:线程为执行start
RUNNABLE:线程进入运行状态
TERMINATED:线程被消耗

//验证NEW、RUNNABLE、TERMINATED
class AA{
    public static class TestThread extends Thread{
        TestThread(){
            System.out.println("调用构造Thread方法的线程"+Thread.currentThread().getState());
            System.out.println("TestThread生成的线程"+ getState());
        }
        @Override
        public void run() {
            System.out.println("TestThread已经运行了"+currentThread().getState());
            System.out.println("TestThread运行结束");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestThread testThread = new TestThread();
        System.out.println("main方法的testThread线程状态"+testThread.getState());
        Thread.sleep(100);
        testThread.start();
        Thread.sleep(100);
        System.out.println("main方法的testThread线程状态"+testThread.getState());
    }
}

初始的main方法所在线程一直在RUNNABLE,但是testThread的线程从NEW变为RUNNABLE直到TERMINATED。生命周期结束
image.png

验证BLOCKED、WAITING、TIMED_WAITING

BLOCKED:阻塞,抢不到锁
WAITING:直观地使用wait方法
TIMED_WAITING:直观地使用sleep方法

class AB{
    private Object object=new Object();
    private void test(){
        synchronized (object){
            try {
                Thread.sleep(1000);
                object.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        AB ab = new AB();
        Thread thread1 = new Thread(() -> ab.test());
        Thread thread2 = new Thread(() -> ab.test());
        Thread.sleep(100);
        thread1.start();
        thread2.start();
        System.out.println(thread1.getState());
        System.out.println(thread2.getState());
        Thread.sleep(2000);
        System.out.println(thread1.getState());
    }
}

结果如下,使用了sleep之后thread1进入TIMED_WAITING状态。thread2抢不到锁进入BLOCKED状态,之后thread1又在WAITING状态,释放了锁之后thread2得到了进入TIMED_WAITING状态。因为不是守护线程,主线程main在这之后也不会结束
image.png

线程组

线程组类似于linux文件目录管理,利用树的结构进行管理
image.png
组中可以有组,也可以有线程对象

一级关联

树的层级为2,不再创建新的层级。
创建一个线程组,然后将部分线程归属到该组中,可以对线程对象进行有效的组织与规划

class BA {
    public static void main(String[] args) {
        ThreadGroup threadGroupA = new ThreadGroup("ThreadGroupA");
        Thread thread1 = new Thread(threadGroupA, new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }));
        Thread thread2 = new Thread(threadGroupA, new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }));
        thread1.start();
        thread2.start();
        System.out.println(threadGroupA.getName()+" "+threadGroupA.activeCount());
        System.out.println(threadGroupA.getName()+" "+threadGroupA.activeGroupCount());
    }
}

线程组A有2个线程分别为线程1和线程3
image.png

多级线程的使用

public ThreadGroup(ThreadGroup parent, String name)构造方法构造
image.png
不贴代码了

其他线程组常用API

  • 线程组自动归属特性
    创建了一个线程组时候,如果没有显式给创建的线程组赋予父级线程组,那么会隐式地添加到创建该线程组的线程所在的线程组的子线程组
  • 获取线程组父final ThreadGroup getParent()
//获取根线程组
class BB {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getThreadGroup().getName());
        System.out.println(Thread.currentThread().getThreadGroup().getParent().getName());
        System.out.println(Thread.currentThread().getThreadGroup().getParent().getParent().getName());
    }
}

main线程所在线程组为main,其父线程组为system。再往上为NPE,所以JVM的顶级线程组为system
image.png

  • 批量中断线程组中的所有线程 final void interrupt()
  • 查看线程组中活动的线程数目 int activeCount()
  • 将对此线程组及其子组中每个活动子组的引用复制到指定的数组中 int enumerate(ThreadGroup list[])

SimpleDateFormat

http://jtao.work/archives/di-er-shi-yi-zhang--he-xin-d-a-t-e--t-i-m-e-lei
除了java.util.Date和java.util.Calendar之外,SimpleDateFormat也是线程不安全的
image.png

  • 代码测验
class CA {
    private SimpleDateFormat simpleDateFormat;
    private String date;

    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        List<String> list = Arrays.asList("2021-01-01", "2021-02-01", "2021-03-01", "2021-04-01", "2021-05-01", "2021-06-01", "2021-07-01", "2021-08-01", "2021-09-01", "2021-01-10");
        IntStream.iterate(0, i -> i = +1).limit(10).forEach(t ->
                {
                    new Thread(() -> {
                        String date = null;
                        Date parse = null;
                        try {
                            Thread.sleep(100);
                            date = list.get(t);
                            parse = sdf.parse(date);
                        } catch (InterruptedException | ParseException e) {
                            e.printStackTrace();
                        }
                        String format = sdf.format(parse);
                        if (!date.equals(format)){
                            System.out.println("ThreadName="+Thread.currentThread().getName()+"转换错误:"+date+"转换的日期为:"+format);
                        }
                    }).start();
                }
        );
    }
}

开启10个线程去跑10个,转换的结果乱七八糟,甚至直接报错格式转换异常
image.png

解决方法,多个SimpleDateFormat对象,或者加锁

JDK源码里面的注释写的很清楚了

线程中出现异常的处理

线程中run方法内catch处理也可以,但是会冗余,和Spring差不多的异常处理,全局异常处理

单个的异常处理

image.png
因为这个接口是Functional接口,所以可以写成lambda形式

class DA{
    public static void main(String[] args) {
        Thread thread1 = new Thread(()->{
            throw new RuntimeException("RuntimeException");
        });
        Thread thread2 = new Thread(()->{
            throw new RuntimeException("RuntimeException");
        });
        Thread thread3 = new Thread(()->{
            throw new Error("Error");
        });
        thread1.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(t.getName());
                e.printStackTrace();
            }
        });
        thread2.setUncaughtExceptionHandler((t,e)->{
            System.out.println(t.getName());
            e.printStackTrace();
        });
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

前2个异常均被捕获后打印
image.png

默认的异常处理

static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)
image.png
即如果A线程设置了setDefaultUncaughtExceptionHandler那么A线程创建的子线程会默认实现这个异常处理。

线程组的异常处理

线程组中的某个线程出现异常,默认不影响其他线程的执行。
可以重写该线程组的uncaughtException方法,出现异常的时候调用线程组的interrupt方法进行中断。
如果此时单个线程setUncaughtExceptionHandler()方法,则方法组的uncaughtException对其不起作用,不会执行

        ThreadGroup threadGroup = new ThreadGroup("MyThreadGroup") {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                //super.uncaughtException(t, e);
                interrupt();
            }
        };

这个家伙很懒,啥也没有留下😋