java程序员面试笔试宝典4.10多线程

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),什么时候运行多线程代码是由操作系统决定的。
1class 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方法
2class 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方法结束返回结果。
3public 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:在一个线程中sleep1000)方法,将使该线程在多长时间后获得对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()方法从而中止线程
1public 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,则表示的是用户进程模式。
当在一个守护线程中产生了其他线程,那么这些新产生大的线程默认还是守护线程,用户线程也是如此。
1class 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.
1class 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

猜你喜欢

转载自blog.csdn.net/m0_37301141/article/details/80490533