1.什么是线程?它和进程有什么区别?为什么要 使用多线程?
线程是指程序在执行过程中,能够执行程序代码的一个执行单元。
在java语言中,线程有四种状态:运行,就绪,挂起和结束。
进程是指一段正在执行的程序。线程有时也被称为轻量级进程,它是程序执行的最小单元,
一个进程可以拥有多个线程,各个线程之间共享程序的内存空间(代码段,数据段和堆空间)及一些进程级的资源(例如打开的文件)
但是各个线程拥有自己的栈空间,进程与线程的对比关系如下:
在操作系统级别上,程序的执行都是以进程为单位的,而每个进程通常都会有多个线程互不影响并发执行,那么为什么要使用多线程呢?
1)可以减少程序的响应时间
单线程:程序执行过程中只有一个有效操作的序列,不同操作之间都有明确的执行先后顺序。
在单线程的情况下,如果一个操作很耗时,此时程序不会响应鼠标和键盘等操作。
使用多线程后,可以把这个耗时的线程分配到一个单独的线程去执行,从而使程序具备了更好的交互性。
2)与进程相比,线程的创建和开销更小。
3)多CPU或多核计算机本身就具有执行多线程的能力。
4)使用多线程能够简化程序的结构,使程序便于理解和维护。
2.同步和异步有什么区别?
在多线程的环境中,经常会碰到数据的共享问题,即多个线程需要访问同一个资源,他们需要以某种顺序来确保该资源在某一时刻是能被一个线程使用,否则程序的运行结果将会是不可预料的。
这种情况下就必须对数据进行同步。
例如多个线程同时对同一数据机进行写操作,即当线程A需要某个资源时,如果这个资源正在被线程B使用,同步机制据会让A一直等待下去,直到线程B结束对该资源的使用后,线程A才能使用这个资源。
可见同步机制能够保证资源的安全。
要想实现同步操作,必须要获得每一个线程对象的锁。获得他可以保证在同一时刻只有一个线程能够进入临界区(访问互斥资源的代码块),并且在这个锁被释放之前,其他线程就不能再进入这个临界区。
如果还有其他线程想要获得该对象的锁,只能进入等待队列等待。
只有当拥有该对象锁的线程退出临界区时,锁才会被释放,等待队列中优先级最高的先成功才能获得该锁,从而进入共享代码区。
可以使用synchronized关键字来实现同步,但是系统开销很大,可能造成死锁。
实现同步的方式有两种:
1)利用同步代码块来实现同步
2)利用同步方法来实现同步
异步与非阻塞相似,由于每个线程都包含了运行时所需要的数据和方法,因此,在进行输入输出处理时,不必关心其他线程的状态或行为,也不必等到输入输出处理完毕才返回。
当应用程序调用了一个很耗时的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,异步能够提高程序的效率。
例子:
同步就是你喊我吃饭,如果我听到了,我就去和你吃饭,如果我没有听到,你就不停喊,直到我告诉你我听到了,我们才一起去吃饭。
异步就是你喊我,然后自己去吃饭,我得到消息后可能立即走,可能等到下班才去吃饭。
3.如果实现java多线程?
java中多线程的实现一般有三种方法,前两种常用。
1)继承Thread类,重写run方法
Thread本质也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start方法。
start方法是一个navite(本地)方法,它将启动一个新线程,并执行run方法。(Thread中提供的run方法是一个空方法。)
注意:
调用start方法后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行多线程代码是由操作系统决定的。
例1:
class MyThread extends Thread{
@Override
public void run() {
System.out.println("Thread body");//线程的函数体
}
}
public class Test{
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();//开启线程
}
}
2)实现Runnable接口,并实现该接口的run方法,以下是主要步骤:
自定义类并实现Runnable接口,实现run方法
创建Thread对象,用实现Runnable接口的对象作为参数实例化该Thread对象
调用Thread的start方法
例2:
class MyThread implements Runnable{
@Override
public void run() {
System.out.println("Thread body");//线程的函数体
}
}
public class Test{
public static void main(String[] args) {
MyThread thread = new MyThread();
Thread t = new Thread(thread);
t.start();//开启线程
}
}
其实不管是通过继承Thread类还是通过使用Runnable接口来实现多线程的方法,最终都是通过Thread的对象的API来控制线程的。
3)实现Callable接口,重写call方法
Callable接口实际是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更强大的功能,主要表现为以下三点:
1)Callable可以在任务结束后提供一个返回值,Runnable无法提供这个功能。
2)Callable中的call方法可以抛出异常,而Runnable的run方法不能抛出异常。
3)运行Callable可以拿到一个Future对象,Future对象表示异步计算的结果,它提供了检查计算是否完成的方法。
由于线程属于异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下,就可以使用Future来监视目标线程调用call方法的情况。
当调用Future的get方法以获取结果时,当前线程就会被阻塞,直到call方法结束返回结果。
例3:
public class CallableAndFuture {
//创建线程类
public static class CallableTest implements Callable<String>{
@Override
public String call() throws Exception {
return "Hello World";
}
}
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
//启动线程
Future<String> future = threadPool.submit(new CallableTest());
try {
System.out.println("waiting thread to finish");
System.out.println(future.get());//等待线程结束并获取返回结果
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果:
waiting thread to finish
Hello World
在以上三种方式中,前两种方式线程执行完后都没返回值,只有最后一种是带返回值的。
当需要实现多线程时,一般推荐实现Runnable接口的方式,原因如下:
1)Thread类定义了多种方法可以被派生类使用或重写,但是只有run方法是必须被重写的,在run方法中实现这个线程的主要功能。这当然是实现Runnable接口所需的方法。
2)很多java开发人员认为,一个类仅在他们需要被加强或修改时才会被继承,
因此,如果没有必要重写Thread类中的其他方法,那么通过继承Thread的实现方式与实现Runnable接口的效果相同,
在这种情况下最好通过实现Runnable接口的方式来创建线程
引申:一个类是否可以同时继承Thread类和实现Runnable接口?
可以,如下所示:
public class Test extends Thread implements Runnable{
public static void main(String[] args) {
Thread t = new Thread(new Test());
t.start();
}
}
能够通过编译并运行
因为Test类从Thread类中继承了run方法,这个继承的方法可以被当作Runnable接口的实现。
当然也可以不使用继承的run方法,而是需要通过在Test类中重写run方法来实现Runnable接口中的run方法。
public class Test extends Thread implements Runnable{
@Override
public void run() {
System.out.println("this is run()");
}
public static void main(String[] args) {
Thread t = new Thread(new Test());
t.start();
}
}
结果:
this is run()
4.run()方法与start()方法有什么区别?
通常,系统通过调用线程类的start()方法来启动一个线程,此时线程处于就绪状态,而非运行状态,意味着这个线程可以被JVM来调度执行。
在调度过程中,JVM通过调用线程类的run()方法来完成实际的操作,当run()方法结束后,此线程就会中止。
如果直接调用线程类的run()方法,这会被当做一个普通的函数调用,程序中仍然只有主线程这一个线程,
即start()方法能够异步调用run方法,但是直接调用run方法确实同步的,因此也就无法达到多线程的目的。
因此,只有通过调用线程类的start()方法才能真正达到多线程的目的。
例1:run方法和start方法的区别:
class ThreadDemo extends Thread{
public void run() {
System.out.println("ThreadDemo:begin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ThreadDemo:end");
}
}
public class Test{
public static void test1(){
System.out.println("test1:begin");
Thread t1 = new ThreadDemo();
t1.start();
System.out.println("test1:end");
}
public static void test2(){
System.out.println("test2:begin");
Thread t1 = new ThreadDemo();
t1.run();
System.out.println("test2:end");
}
public static void main(String[] args) {
test1();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println();
test2();
}
}
结果:
test1:begin
test1:end
ThreadDemo:begin
ThreadDemo:end
test2:begin
ThreadDemo:begin
ThreadDemo:end
test2:end
说明:
线程t1是在test1方法结束后才执行的
System.out.println("test1:end");语句不需要等待t1.start()运行结束就可以执行。因此,test1中调用start()方法是异步的,所以main线程与t1线程是异步执行的。
从test2的运行结果可以看出,调用t1.run()方法是同步的调用方法,因为System.out.println("test2:end");只有等t1.run()调用结束后才能执行。
5.多线程实现的方法有哪些?
当使用多线程访问同一个资源时,非常容易出现线程安全的问题,因此需要采用同步机制来解决问题。
Java提供了3种实现同步机制的方法。
1)synchronized关键字
在java语言中,每个对象都有一个对象锁与之相关联,该锁表明对象在任何时候只允许被一个线程所拥有,
当一个线程调用对象的一段synchronized代码时,需要先获取这个锁,然后去执行相应的代码,执行结束后,释放锁。
synchronized关键字主要有两种用法:synchronized方法和synchronized代码块,此外该关键字还可以作用于静态方法,类或某个实例,但是对程序影响很大。
a)synchronized方法
public synchronized void mutiThreadAccess();
只要把多个线程对类需要被同步的资源的操作放到mutiThreadAccess方法中,就能保证这个方法在同一时刻只能被一个线程访问,从而保证了多线程访问的安全性。
然而当一个方法的方法体规模非常大时,把该方法申明为synchronized 会很影响程序的执行效率,为了提高程序的执行效率,Java提供了synchronized 代码块。
b)synchronized 代码块
既可以把任意的代码申明为synchronized ,也可以指定上锁的对象
synchronized (syncObject){
//访问syncObject的代码
}
2)wait()方法与notify()方法
当使用synchronized 来修饰某个共享资源时,如果线程A1在执行synchronized 代码,另外一个线程A2也要同时执行同一对象的synchronized 代码时,
线程A2将要等到线程A1执行完后,才能继续执行,这种情况下使用wait方法和notify方法。
在synchronized 代码被执行期间,线程可以调用对象的wait方法,释放对象锁,进入等待状态,并且可以调用notify方法或者notifyAll方法通知正在等待的其他线程。
notify方法仅唤醒一个线程,并允许它去获得锁
notifyAll方法唤醒所有等待这个对象的线程,并让他们去竞争。
3)Lock
jdk1.5新增了Lock接口以及它的一个实现类ReentrantLock(重入锁),Lock也可以用来实现多线程的同步。
a)lock(),以阻塞的方式获取锁,即如果获得了锁,立即返回;如果别的线程持有锁,当前线程等待,直到获得锁后返回。
b)tryLock(),以非阻塞的方式获得锁,只是常识性去获取一下锁,如果获得锁,立即返回true,否则,立即返回false。
c)tryLock(long timeout,TimeUnit unit)如果获得了锁,立即返回true,否则会等待参数给定的时间单元,在等待的过程中,如果获得了锁,就返回true,如果等待超时,返回false。
d)lockInterruptibly().如果获取了锁,立即返回;如果没有获取锁,当前线程处于休眠状态,直到获得锁。
或者当前线程被别的线程中断(会收到InterruptedException异常)
它与lock方法最大的区别是 如果lock方法获取不到锁,,就会一直处于阻塞状态,且会忽略interrupt方法
例1:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test{
public static void main(String[] args) throws InterruptedException {
final Lock lock = new ReentrantLock();
lock.lock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lockInterruptibly();
//lock.lock();//编译器报错
} catch (InterruptedException e) {
System.out.println(" interrupted.");
}
}
});
t1.start();
t1.interrupt();
Thread.sleep(1);
}
}
结果:
interrupted.
如果把lock.lockInterruptibly();替换lock.lock(),编译器将会提示catch块代码无效。
这是因为lock.lock()不会抛出异常,
由此可见lock()方法会忽略interrupt()引发的异常。
6.sleep()方法与wait()方法有什么区别?
sleep()方法是使线程暂停执行一段时间的方法
wait()方法也是一种使线程暂停执行的方法。
例如,当线程交互时,如果线程对一个同步对象x发出一个wait调用请求,那么该线程会暂停执行,被调用对象进入等待状态,直到被唤醒或等待时间超时。
区别如下:
1)原理不同:
sleep是Thread类的静态方法,是线程用来控制自身的,时间一到,线程就会自动苏醒。
wait是Object类的方法,用于线程间的通信。使当前拥有该对象锁的进程等待,直到其他线程调用notify()方法才醒来,开发人员也可以指定醒来时间。
与wait方法配套的方法还有notify()和notifyAll()
2)对锁的处理机制不同
sleep不会释放锁
wait会释放锁
3)使用区域不同
sleep任何地方可以使用,必须捕获异常,有可能被其他对象调用它的interrupt方法,产生InterruptedException异常。
wait必须放在同步控制方法或同步语句块中使用,而wait,notify,notifyAll不需要捕获异常。
sleep不会释放锁,容易死锁,推荐wait方法。
引申:sleep方法和yield方法有什么区别?
1)sleep方法给其他线程运行机会时不考虑线程的优先级,因此会给优先级低的线程运行的机会
yield方法只会给相同优先级或更高优先级的线程以运行的机会。
2)线程执行sleep方法后会转入阻塞状态,所以执行sleep方法的线程在指定的时间内肯定不会被之心那个
yield方法只是使当前线程重新回到可执行状态,所以执行yield方法的线程可能在进入到可执行状态后马上被执行。
3)sleep方法申明抛出InterruptedException ,而yield方法没有申明任何异常。
4)sleep方法比yield方法(跟操作系统相关)具有更好的移植性。
笔1:利用Thread.wait()同步线程,可以设置超时时间吗?
可以
函数原型为wait(long timeout)和wait(long timeout,int nanos)
timeout代表最长的等待时间,ms,nanos代表额外的等待时间,单位为ns
笔2:在一个线程中sleep(1000)方法,将使该线程在多长时间后获得对CPU的控制(假设睡眠过程中不会有其他事件唤醒该线程)C
A:正好1000ms
B:少于1000ms
c:大于等于1000ms
D:不一定
说明:sleep方法指定的时间为线程不会运行的最短时间。
当睡眠时间结束后,线程会返回到可运行状态,不是运行状态,还需要等待CPU调度执行
因此sleep()方法并不能保证该线程睡眠到期后就开始执行。
7.中止线程的方法有哪些?
在java语言中,可以使用stop方法与suspend方法来终止线程的执行。
当用Thread.stop()来终止线程时,可能会导致程序执行的不确定性。
当调用suspend()方法容易发生死锁,因为不会释放锁。
死锁指:两个或两个以上的进程在执行过程中,因争夺资源而造成互相等待的现象,如果无外力作用,他们都无法推进。
建议采用让线程自动结束进入Dead状态,一个线程进入Dead状态,即执行完run方法
即想要停止一个线程的执行,就要提供某种方式让线程能够自动结束run方法的执行。
在实现时,可以通过设置一个flag标志来控制循环是否执行,通过这种方法来让线程离开run()方法从而中止线程
例1:
public class MyThread implements Runnable{
private volatile Boolean flag;
public void stop() {
flag = false;
}
@Override
public void run() {
while(flag){
;//do something
}
}
}
上例中,通过调用MyThread的stop方法虽然能够中止线程,但是同样存在问题
当线程处于非运行状态时,(当sleep方法被调用,wait方法被调用,当被I/O阻塞),上面的方法就不可用了。
此时可以使用interrupt()方法来打破阻塞的情况,当interrupt方法被调用时候,抛出InterruptedException 异常,可以通过在run方法中捕获这个异常来让线程安全退出。
public class MyThread{
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread go to sleep");
try {
//用休眠来模拟线程被阻塞
Thread.sleep(5000);
System.out.println("thread finish");
} catch (InterruptedException e) {
System.out.println("thread is interrupted");
}
}
});
thread.start();
thread.interrupt();
}
}
结果:
thread go to sleep
thread is interrupted
说明:如果程序因为I/O而停滞,进入非运行状态,基本上要的能带I/O完成才能离开这个状态。
在这种情况下,无法使用interrupt()方法来使程序离开run()方法
需要使用一个替代的方法,基本思路也是触发一个异常,而这个异常与所使用的I/O相关
例如,如果使用readLine()方法在等待网络上的一个信息,此时线程处于阻塞状态。
让程序离开run()就是使用close()关闭流
在这种情况下会引发IOException异常,run()方法可以通过捕获这个异常来安全地结束线程。
8.synchronized与Lock有什么异同?
Java语言提供了两种锁机制来实现对某个共享资源的同步:
1)synchronized
使用Object对象本身的notify,wait,notifyAll调度机制
2)Lock
使用Condition进行线程之间的调度,完成synchronized实现的所有功能。
区别:
1)用法不一样
在需要同步的对象中加入synchronized控制,synchronized既可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。synchronized是托管给JVM执行
Lock需要显示地指定起始位置了中止位置。Lock的锁定需要通过代码来实现。它有比synchronized更精确的线程定义。
2)性能不一样
在jdk1.5中增加了一个Lock接口的实现类ReentrantLock。
它不仅拥有和synchronized相同的并发性和内存语义,还多了锁投票,定时锁,等候和中断锁。
他们的性能在不同情况下会不同
在竞争不激烈的情况下,synchronized性能好
竞争那个激烈的情况下,synchronized性能下降很快,ReentrantLock性能基本不变。
3)锁机制不一样
synchronized获得锁和释放锁都是在块结构中,当获得多个锁时候,必须以相反的顺序释放,并且是自动解锁,不会因为出现了异常而导致锁没有被释放从而引发死锁。
Lock需要开发人员手动去释放,并且必须在finally块中释放,否则会引起死锁的问题
此外Lock还提供了更强大的功能,它的tryLock()方法可以采用非阻塞的方式去获取锁。
注意:
最好吧不要同时使用这两种同步机制,因为synchronized和ReentrantLock所使用的机制不同,运行是独立的,相当于两种类型的锁,在使用时互不影响。
例1:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class SyncTest{
private int value = 0;
Lock lock = new ReentrantLock();
public synchronized void addValueSync(){
this.value++;
System.out.println(Thread.currentThread().getName()+":"+value);
}
public void addValueLock(){
try {
lock.lock();
value++;
System.out.println(Thread.currentThread().getName()+":"+value);
} catch (Exception e) {
e.printStackTrace();
} finally{
lock.unlock();
}
}
}
public class Test{
public static void main(String[] args){
final SyncTest st = new SyncTest();//测试synchronized
//final SyncTest st = new LockTest();//测试Lock
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
st.addValueSync();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
st.addValueLock();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
t2.start();
}
}
结果:
Thread-0:1
Thread-1:2
Thread-0:3
Thread-1:4
Thread-1:5
Thread-0:6
Thread-0:7
Thread-1:8
Thread-1:10
Thread-0:10
说明:
上例中,并不是每次运行结果都是相同的,输出结果的value值也不是连续的,这就是因为两种上锁方式采用了不同的机制造成的。
因此实际使用时,最好不要同时使用两种上锁机制。
笔1:当一个线程进入一个对象的synchronized方法后,其他线程是否可以进入此对象的其他方法?
当一个线程进入一个对象的synchronized方法后,其他线程能否进入此对象的其他方法取决与方法本身,如果该方法是非synchronized方法,那么是可以访问的,
示例如下:
class Test{
public synchronized void synchronizedMethod(){
System.out.println("begin calling synchronizedMethod");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
System.out.println("finish calling synchronizedMethod");
}
public void generalMethod(){
System.out.println("call generalMethod...");
}
}
public class MutiThread {
static final Test t = new Test();
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
t.synchronizedMethod();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
t.generalMethod();
}
});
t1.start();
t2.start();
}
}
结果:
begin calling synchronizedMethod
call generalMethod...
finish calling synchronizedMethod
说明:
从上例可以看出,线程t1在调用sychronized方法的过程中,线程t2仍然可以访问同一对象的非sychronized方法
例2:如果其他方法是静态方法,它用的同步锁是当前类的字节码,与非静态的方法不能同步,(因为非静态的方法用的是this)
因此,静态方法可以被调用,实例如下:
class Test{
public synchronized void synchronizedMethod(){
System.out.println("begin calling synchronizedMethod");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
System.out.println("finish calling synchronizedMethod");
}
public static synchronized void generalMethod(){
System.out.println("call generalMethod...");
}
}
public class MutiThread {
static final Test t = new Test();
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
t.synchronizedMethod();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
t.generalMethod();
}
});
t1.start();
t2.start();
}
}
结果:
begin calling synchronizedMethod
call generalMethod...
finish calling synchronizedMethod
从上例可以看出,当线程t1在调用对象t1的sychronized方法时,线程t2仍然可以调用这个对象的静态sychronized()方法。
如果这个对象内部调用了wait方法,那么其他线程可以访问同一对象的其他sychronized方法
如果这个方法内部没有调用wait方法,并且其他方法都为sychronized方法,那么其他线程将无法访问这个对象的其他方法。
9.什么是守护线程?
java提供了两种线程:守护线程和用户线程。
守护线程又被称为"服务进程","精灵线程"或"后台线程",是指程序运行时在后台提供的一种通用服务的线程,这种线程并不属于程序中不可或缺的部分。
即任何一个守护线程都是整个JVM中所有非守护线程的保姆。
用户线程和守护线程几乎一样,唯一不同的地方就是如果用户线程已经全部退出运行,只剩下守护线程存在了,JVM也就退出了。
因为当所有非守护线程结束时,没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了,程序也就中止了,同时会杀死所有守护线程。
即只要有任何非守护线程还在运行,程序就不会终止。
在java语言中,守护线程一般具有较低的优先级,它并非只由JVM内部提供,用户在编写程序时也可以自己设置守护线程。
例如将一个用户线程设置成守护线程的方法就是在调用start()方法启动线程之前调用对象的setDaemon(true)方法,若将以上参数设置为false,则表示的是用户进程模式。
当在一个守护线程中产生了其他线程,那么这些新产生大的线程默认还是守护线程,用户线程也是如此。
例1:
class ThreadDemo extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":begin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":end");
}
}
public class Test {
public static void main(String[] args) {
System.out.println("test3:begin");
Thread t1 = new ThreadDemo();
t1.setDaemon(true);
t1.start();
System.out.println("test3:end");
}
}
结果:
test3:begin
test3:end
Thread-0:begin
从运行结果发现,没有输出Thread - 0:end.
之所以这样是在启动线程前将其设置为守护线程了,当程序只有守护线程存在时,JVM是可以退出的。
即当JVM中只有守护线程运行时,JVM会自动关闭。
因此当test3方法调用结束后,main线程将退出,此时线程t1还处于休眠状态没有运行结束,但是由于此时只有这个守护线程在运行,JVM将关闭,因此不会输出Thread - 0:end.
守护线程的一个典型例子就是垃圾回收器。只要JVM启动,它始终在运行,实时监控和管理系统中可以被回收的资源。
10.join()方法的作用是什么?
在java语言中,join()方法的作用是让调用该方法的线程在执行完run()方法后,再执行join之后的代码。
就是将两个线程合并,用于实现同步功能。
具体而言,可以通过线程A的join()方法来等待线程A的结束,或者使用线程A的join(2000)方法来等待线程A的结束,但是最多只等待2s.
例1:
class ThreadImp implements Runnable{
@Override
public void run() {
try {
System.out.println("Begin ThreadImp");
Thread.sleep(5000);
System.out.println("End ThreadImp");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class JoinTest {
public static void main(String[] args) {
Thread t = new Thread(new ThreadImp());
t.start();
try {
t.join(1000);//主线程等待t结束,只等1s
if(t.isAlive()){//t已经结束
System.out.println("t has not finished");
}else {
System.out.println("t has finished");
}
System.out.println("joinFinish");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:
Begin ThreadImp
t has not finished
joinFinish
End ThreadImp