线程的属性总结

java 线程中注入service java线程id_java 线程中注入service

1. 线程有哪些常见属性?

● 线程ID:线程用ID来标识出不同线程
● 线程名字(Name):让用户或者程序猿开发调试或运行中定位线程的问题等。
● 守护线程(isDaemon):当为true时,代表该线程为守护线程,false为非守护线程,也可以称作用户线程。
● 线程优先级(Priority):作用是告诉线程调度器,希望那个线程多运行,那个线程少运行。

1.1 线程ID

● 线程ID从1开始,JVM运行起来后,我们自己创建的线程Id 早已不是0

public class ID {
    public static void main(String[] args) {
        Thread thread = new Thread();
        System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());
        System.out.println(thread.getName() + ": " + thread.getId());
    }
}
main: 1
Thread-0: 11
Process finished with exit code 0

● 在线程初始化中,有tid赋值

/* For generating thread ID */
    private static long threadSeqNumber;
		private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
				......
        /* Set thread ID */
        tid = nextThreadID();
    }
private static synchronized long nextThreadID() {
    return ++threadSeqNumber;
}

● threadSeqNumber没有赋值,所以为0,而nextThreadID()的++threadSeqNumber使得线程从ID从1开始.
● 那么为什么答应出来的子线程ID是11呢?
○ 如果你是用的IDEA,可以通过debug下个断点在最后一句上面,然后看一下当前线程的情况。
○ 实际就是JVM帮我们起了一些线程。

1.2 线程名字

  1. 默认线程名字源码分析:
    ○ 在没有指定线程名字的时候,线程默认传一个"Thread-" + nextThreadNum()作为名字
    ○ 而nextThreadNum获取到的threadInitNumber则是一个从0自增的一个静态变量。因为用了synchronized,所以是不会重复的。
/* For autonumbering anonymous threads. */
    private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    } 
		public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
  1. 如何修改线程名字
    ○ 一个是通过构造方法
    ○ 另一个是通过setName(String name)设置
public Thread(String name) {
        init(null, null, name, 0);
    }
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        this.name = name;
				...
    }
    
    public final synchronized void setName(String name) {
        checkAccess();
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        this.name = name;
        if (threadStatus != 0) {
            setNativeName(name);
        }
    }

○ 注意setName(String name)调用的时候,当线程是NEW状态的时候,设置name会一同把JVM里面的CPP的线程名字设置好,而如果不是NEW状态则只能修改java中我们可以看到的名称。

1.3 守护线程(Daemon)

● 作用:给用户线程提供服务
● 三个特性:
○ 线程默认类型继承自父线程
○ 被谁启动
○ 不影响JVM退出
● 守护线程和普通线程的区别
○ 整体没有太大区别
○ 唯一的区别是是否影响JVM的退出
● 常见面试问题
○ 守护线程和用户线程的区别
○ 我们是否需要给线程设置为守护线程?
■ 不需要,如果设置生守护线程,在JVM退出时会忽略你正在执行的任务,如果你正在执行一些数据操作,那么就会造成数据不一致了。

1.4 线程优先级

● 在Java中优先级有个10个等级,默认为5,通过setPriority(int newPriority)设置
● 但是我们程序在编码的时候,不应该依赖于优先级
○ 高度依赖于操作系统,不同的操作系统在实现优先级执行的时候不一样(windows中只有7个等级,更甚有的没有优先级。)设置优先级是不可靠的
○ 优先级可能会被操作系统改变

/**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;
   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;
    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;
    
    public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }

1.5 常见问题

  1. 什么时候我们需要设置守护线程?
  2. 我们应该如何应用线程优先级来帮助程序运行?有哪些禁忌?
  3. 不同的操作系统如何处理优先级问题?

2. 未捕获异常处理UncaughtException

2.1 使用UncaughtExceptionHandler处理

  1. 为什么要使用UncaughtExceptionHandler来处理?
    ○ 主线程可以轻松发现异常,而子线程却不行
/**
 * 单线程抛出处理有异常堆栈
 * 而多线程,子线程发生异常有什么不同?
 *
 */
public class ExceptionInChild {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            throw new RuntimeException();
        });
        thread.start();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " +i);
        }
    }
}

运行结果

Exception in thread "Thread-0" java.lang.RuntimeException
	at com.imyiren.concurrency.thread.uncaughtexception.ExceptionInChild.lambda$main$0(ExceptionInChild.java:14)
	at java.lang.Thread.run(Thread.java:748)
main: 0
main: 1
main: 2
main: 3
main: 4
Process finished with exit code 0

○ 由上可看出,子线程报错,丝毫不印象主线程的执行。
○ 子线程的异常无法用传统的方法捕获

/**
 * 1. 不加try-catch 抛出四个异常
 * 2. 加了try-catch 期望捕获第一个线程的异常,线程234应该不运行,希望看到CaughtException
 * 3. 执行时发现,根本没有CaughtException,线程234依旧运行并抛出异常
 *
 */
public class CantCatchDirectly {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            throw new RuntimeException();};
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        Thread thread3 = new Thread(runnable);
        Thread thread4 = new Thread(runnable);
        try {
            thread1.start();
            Thread.sleep(200);
            thread2.start();
            Thread.sleep(200);
            thread3.start();
            Thread.sleep(200);
            thread4.start();
        } catch (RuntimeException e) {
            System.out.println("caught exception");
        }
    }
}

运行说明

Exception in thread "Thread-0" java.lang.RuntimeException
	at com.imyiren.concurrency.thread.uncaughtexception.CantCatchDirectly.lambda$main$0(CantCatchDirectly.java:15)
	at java.lang.Thread.run(Thread.java:748)
Exception in thread "Thread-1" java.lang.RuntimeException
	at com.imyiren.concurrency.thread.uncaughtexception.CantCatchDirectly.lambda$main$0(CantCatchDirectly.java:15)
	at java.lang.Thread.run(Thread.java:748)
Exception in thread "Thread-2" java.lang.RuntimeException
	at com.imyiren.concurrency.thread.uncaughtexception.CantCatchDirectly.lambda$main$0(CantCatchDirectly.java:15)
	at java.lang.Thread.run(Thread.java:748)
Exception in thread "Thread-3" java.lang.RuntimeException
	at com.imyiren.concurrency.thread.uncaughtexception.CantCatchDirectly.lambda$main$0(CantCatchDirectly.java:15)
	at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0

○ 如上,无法用传统的方法来捕获异常信息。
○ try-catch是针对主线程的,而不是针对子线程的。throw new RuntimeException()是运行在子线程的。

  1. 解决上面的主线程无法捕获的问题:
    ● 方案一(不推荐):在run()方法中进行try-catch
/**
 * 方案一:在run方法中try-catch
 */
public class CatchExceptionInRun {
    public static void main(String[] args) {
        new Thread(() -> {
            try {
                throw new RuntimeException();
            } catch (RuntimeException e) {
                System.out.println("Caught Exception ...");
            }
        }).start();
    }
}

● 方案二:利用UncaughtExceptionHandler接口处理
○ 先看下线程异常处理器的调用策略 在ThreadGroup类中
○ 它会检查是否有父线程,如果父线程不为空就一直向上找到最顶层。
○ 如果没有,那就尝试获取默认的异常处理器。如果取到的实现不为空,那就调用实现的处理方式,如果为空那就打印异常堆栈信息。
○ 从上面的案例可知 没有实现的时候是直接打印异常堆栈

public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

a. 给程序统一设置
■ 首先自定义一个Handler

/**
 * 自定义异常Handler
 */
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
    private String name;
    public MyUncaughtExceptionHandler(String name) {
        this.name = name;
    }
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        Logger logger = Logger.getAnonymousLogger();
        logger.log(Level.WARNING,  name + "caught thread exception : " + t.getName());
    }
}
■ 然后设置默认处理器
/**
 * 使用自定义的handler
 */
public class CatchByOwnUncaughtExceptionHandler {
    public static void main(String[] args) throws InterruptedException {
        Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler("catch-handler"));
        Runnable runnable = () -> {
            throw new RuntimeException();
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        Thread.sleep(200);
        thread2.start();
    }
}

运行说明

月 12, 2020 2:09:20 下午 com.imyiren.concurrency.thread.uncaughtexception.MyUncaughtExceptionHandler uncaughtException
警告: catch-handlercaught thread exception : Thread-0
二月 12, 2020 2:09:20 下午 com.imyiren.concurrency.thread.uncaughtexception.MyUncaughtExceptionHandler uncaughtException
警告: catch-handlercaught thread exception : Thread-1
二月 12, 2020 2:09:21 下午 com.imyiren.concurrency.thread.uncaughtexception.MyUncaughtExceptionHandler uncaughtException
警告: catch-handlercaught thread exception : Thread-2
二月 12, 2020 2:09:21 下午 com.imyiren.concurrency.thread.uncaughtexception.MyUncaughtExceptionHandler uncaughtException
警告: catch-handlercaught thread exception : Thread-3
Process finished with exit code 0
■ 如上可以看到 线程异常处理是使用我们自定义的处理器。

a. 可以给每个线程单独设置
■ 可以通过thread.setUncaughtExceptionHandler(handler)设置
b. 给线程池设置
■ 可以通过ThreadPoolExecutor来处理