Java线程的生命周期
1:线程的创建即我们常说的线程的new过程,但并未启动线程。
2:线程的就绪。线程在调用start()方法之后,线程进入就绪状态,JVM会为其创建函数调度栈和计数器并初始化成员变量,此时线程并未正式运行,而是等待CPU调度。
3:线程的运行。当准备就绪的线程获取到CPU的调度之后,就开始进入线程的运行状态了。
4:线程的阻塞。由于线程在运行的过程中我们可能调用了Thread.sleep()或者Object.wait()或者等待其他线程join()执行完毕的过程,会将当前运行的线程暂停让出CPU调度给其他线程使用。等到sleep()方法时间执行完毕或者有其他线程唤醒通过nontify()方法缓存正在wait()方法的线程。
5:线程的消亡。当线程执行完毕之后或者线程被杀死即线程结束进入消亡了。
Java中创建线程的四种方式
(一)显示创建线程
/**
* @ClassName NewThread
* @Description 线程的第一种启动方式 显示的创建线程
* @Author huangwb
* @Date 2019-02-21 22:18
* @Version 1.0
**/
public class NewThread {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 200000; i++) {
count++;
}
}
});
thread.start();
//等待子线程thread执行完毕
thread.join();
System.out.println("线程执行完毕,count=" + count);
}
}
(二)继承Thread类方式
/**
* @ClassName ExtendThread
* @Description TODO 通过继承Thread方式创建线程
* @Author huangwb
* @Date 2019-02-21 22:24
* @Version 1.0
**/
public class ExtendThread extends Thread {
private static int count = 0;
@Override
public void run() {
for (int i = 0; i < 20000; i++) {
count++;
}
}
public static void main(String[] args) throws InterruptedException {
ExtendThread extendThread = new ExtendThread();
extendThread.start();
extendThread.join();
System.out.println(count);
}
}
(三)实现Runnable接口的方式
/**
* @ClassName ImplementsRunnable
* @Description TODO
* @Author huangwb
* @Date 2019-02-21 22:28
* @Version 1.0
**/
public class ImplementsRunnable implements Runnable {
private static int count = 0;
@Override
public void run() {
for (int i = 0; i < 20000; i++) {
count++;
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new ImplementsRunnable());
thread.start();
thread.join();
System.out.println(count);
}
}
(四)实现Callable接口的方式
/**
* @ClassName ImplementsCallable
* @Description TODO 使用实现Callable和Future创建具备返回值的线程
* @Author huangwb
* @Date 2019-02-21 22:31
* @Version 1.0
**/
public class ImplementsCallable implements Callable {
private static int count = 0;
@Override
public Object call() throws Exception {
for (int i = 0; i < 20000; i++) {
count++;
}
return count;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ImplementsCallable implementsCallable = new ImplementsCallable();
//需要创建futureTask方法用来包装Callable对象
FutureTask<Integer> futureTask = new FutureTask<>(implementsCallable);
new Thread(futureTask).start();
//无需Thread.join()等待线程执行完毕拿值
System.out.println(futureTask.get());
}
}
四种方式的比较
第一种显示创建线程优点方便快捷,能够直接创建线程并且使用。
第二种继承Thread实现线程的方式,优点编写简单,如果需要访问多线程直接使用this即可。缺点:在Java中类都是单继承多实现的方式,所以该类不能再继承其他父类。
第三种和第四种类都只实现了Runnable和Callable方法,能够继承其他类。非常适合多个线程处理同一份资源的情况,可以将数据和代码分开。Callable能够拿到具有返回值的线程,并且能够使用FutureTask进行包装,能够拿到执行过后的结果。而第一种到第三种都必须显示调用Thread.join()方法才能拿到线程的结果。如果当我们需要父线程获取子线程的结果时这个方法会变得很有用。Future和FutureTask中有很多非常有有用的方法来使用,后面有时间使用到ExecutorService也会去讲解Future接口和FutureTask类。
Thread中经常使用方法讲解
1、start()方法
启动一个线程,一个线程只能start一次,这个前面也有测试就不多使用了。
run()方法,不需要用户来调用的,在讲线程生命周期时,当线程启动之后就会等待CPU执行调度。
2、sleep()
在Thread中sleep有两个重载的方法
sleep(long millis);
sleep(long millis,int nanoseconds);
第一个参数为毫秒,第二个参数会纳秒
sleep从英文理解就是睡眠,在线程调用sleep的执行过程中,会交出CPU,让CPU去执行其他任务。
注意点:sleep方法并不会释放锁,会持续占有锁,其他线程并不能继续访问这个线程的执行。
/**
* @ClassName ThreadSleep
* @Description TODO
* @Author huangwb
* @Date 2019-02-21 23:04
* @Version 1.0
**/
public class ThreadSleep {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("CPU执行线程一");
//沉睡两秒钟
try {
Thread.sleep(2000);
System.out.println("线程一进入沉睡");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程一执行完毕");
}
});
thread1.start();
}
}
CPU执行线程一
线程一进入沉睡
线程一执行完毕
3、yield()
调用yield方法会直接让出CPU权限,让CPU去执行其他线程,与sleep方法类似 但不同的是yield()方法并不会阻塞线程。
注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
4、join方法()
join方法有三个重载版本
join();
join(long millis);
join(long millis,int nanoseconds);
join()实际是利用了wait(),只不过它不用等待notify()/notifyAll(),且不受其影响。它结束的条件是:1)等待时间到;2)目标线程已经run完(通过isAlive()来判断)。
我们在上面线程的四种实现方法的前三种都用到了join,就是为了能够更好的观察到结果,如果不使用的话,打印出来的结果在mian主线程中即为0
5、interupt();
此操作会中断等待中的线程,并将线程的中断标志位置位。如果线程在运行态则不会受此影响。
可以通过以下三种方式来判断中断:
(1)isInterrupted()
此方法只会读取线程的中断标志位,并不会重置。
(2)interrupted()
此方法读取线程的中断标志位,并会重置。
3)throw InterruptException
抛出该异常的同时,会重置中断标志位。