在我们学习之前先了解下jvm常用的调优工具,Jconsole,jProfile,VisualVM
1) Jconsole : jdk自带,功能简单,可以在系统有一定负荷的情况下使用;对垃圾回收算法有很详细的跟踪。
2) JProfiler :商业软件,需要付费;功能强大。
3) VisualVM :JDK自带,功能强大,与JProfiler类似;推荐。
cmd执行jconsole命令,或者直接执行jdk bin目录下的jconsole.exe、jvisualvm.exe来查看线程运行的信息。详细使用可以参照网上专门的教程。
1.1 线程生命周期
线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、销毁。
线程的生命周期-
新建:就是刚使用new方法,new出来的线程;
-
就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;
-
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;
-
阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;
-
销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;
1.2 Thread
thread构造方法new Thread() init方法1.public Thread():分配一个新的 Thread
对象。从源码中可以看到new Thread()时,默认有一个线程名是以Thread-开头,其中nextThreadNum()是以0开始的累加方法。
2.public Thread(String name) :分配一个指定名字的新的线程对象。
3.public Thread(Runnable target) :分配一个带有指定目标新的线程对象。如果Runnable参数不传的话,很明显该Thread不会调用任何东西。
4.public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字
5.public Thread(ThreadGroup group, Runnable target):分配一个新的 Thread
对象。
6.public Thread(ThreadGroup group, Runnable target, String name) :分配一个新的 Thread对象,使其具有 target作为其运行对象,具有指定的 name作为其名称,属于 group引用的线程组。
7.public Thread(ThreadGroup group, Runnable target, String name):分配一个新的 Thread
对象,使其具有 target
作为其运行对象,具有指定的 name
作为其名称,属于 group
引用的线程组。
8.public Thread(ThreadGroup group, Runnable target, String name, long stackSize):分配一个新的 Thread对象,以便它具有 target作为其运行对象,将指定的 name正如其名,以及属于该线程组由称作 group ,并具有指定的 堆栈大小 。
9.public Thread(ThreadGroup group, String name):分配一个新的 Thread对象。
1.2.1 Thread常用方法
以下是关系到线程运行状态的几个方法:
1)start方法
start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
2)run方法
run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
3)sleep方法
sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。当当前线程调用sleep()方法进入阻塞状态后,在其睡眠时间内,该线程不会获得执行机会,即使系统中没有其他可执行线程,处于sleep()中的线程也不会执行,因此sleep()方法常用来暂停程序的执行sleep方法不会释放锁
4)yield方法
yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入到就绪状态。即让当前线程暂停一下,让系统的线程调度器重新调度一次,完全可能的情况是:当某个线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行。调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,当某个线程调用了yield()方法之后,只有优先级与当前线程相同或者比当前线程更高的处于就绪状态的线程才会获得执行机会。注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
5)interrupt()方法
中断并不能直接终止另一个线程,它只是要求被中断线程在合适的时机中断自己,这需要被中断的线程自己处理中断。就好像每个父母都希望子女学有所成,但实际情况还是取决于子女自身。
Thread.currentThread().interrupt() :线程中断只是一个状态而已,其中isInterrupted()返回true表示已中断,false表示未中断。 设置线程中断不影响线程的继续执行,但是线程设置中断后,线程内调用了wait、join、sleep方法中的一种, 立马抛出一个 InterruptedException,且中断标志被清除,重新设置为false。
public static void main(String[] args) {
Thread thread = new Thread(()-> {
try {
// 线程内部调用sleep方法,立刻抛出java.lang.InterruptedException: sleep interrupted
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 中断标志被清除,重新设置为false。
System.out.println( Thread.currentThread().isInterrupted());
}
});
thread.start();
System.out.println(thread.isInterrupted());// 返回false.线程未中断
thread.interrupt();
System.out.println(thread.isInterrupted());// 返回true.线程中断
6)join()方法
在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程将可能早于子线程结束。如果主线程需要知道子线程的执行结果时,就需要等待子线程执行结束了。主线程可以sleep(xx),但这样的xx时间不好确定,因为子线程的执行时间不确定,join()方法比较合适这个场景。
join源码:
/**
*等待该线程终止的时间最长为 millis 毫秒。超时为 0 意味着要一直等下去。
*millis - 以毫秒为单位的等待时间。
*/
public final synchronized void join(long millis)
throws InterruptedException {
//获取启动时的时间戳,用于计算当前时间
long base = System.currentTimeMillis();
//当前时间
long now = 0;
if (millis < 0) {//等待时间不能小于0则抛出IllegalArgumentException
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {//等待时间为0,则无限等待
//需要注意,如果当前线程未被启动或者终止,则isAlive方法返回false
//即意味着join方法不会生效
while (isAlive()) {
wait(0);
}
} else {
//需要注意,如果当前线程未被启动或者终止,则isAlive方法返回false
//即意味着join方法不会生效
while (isAlive()) {
//计算剩余的等待时间
long delay = millis - now;
if (delay <= 0) {//如果剩余的等待时间小于等于0,则终止等待
break;
}
//等待指定时间
wait(delay);
//获取当前时间
now = System.currentTimeMillis() - base;
}
}
}
从源码中可以得知,如果要join正常生效,调用join方法的对象必须已经调用了start()方法且并未进入终止状态。
1.2.2 Thread守护线程
我们知道java中线程分为两种类型:用户线程和守护线程。通过Thread.setDaemon(false)设置为用户线程;通过Thread.setDaemon(true)设置为守护线程。默认为用户线程。守护线程时一种低级别的线程,当 Java 虚拟机中仅剩下守护线程的时候 JVM 会退出。我们可以通过方法 Thread.setDaemon(true) 将线程设置为守护线程,该方法必须在 Thread.start()方法之前调用。否则会抛出一个IllegalThreadStateException异常。
网友评论