阿里架构师讲面试:线程生命周期管理

228 阅读7分钟

线程状态切换

线程创建

线程的创建原理

当我们调用 new Thread() 时,JVM 并不会立即创建一个与其对应的系统线程<在堆区创建线程对象>,而是当调用了 start() 方法之后,JVM 才会通过系统调用 clone 来创建一个与其对应的系统线程(参考 pthread_create())。因为 Java 线程最终被映射为系统线程,所以当我们需要创建线程时,尤其是需要大量线程时,我们需要注意:

  • 操作系统对线程的数量的限制
  • 创建、调度和终止线程的系统开销
  • 线程本身对系统资源的消耗(尤其是内存,JVM 需要为每个线程维护一个独立的线程栈 -Xss)

Thread 类中的start() 和 run() 方法有什么区别?

这个问题经常被问到,但还是能从此区分出面试者对Java线程模型的理解程度。start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。

参考:zhuanlan.zhihu.com/p/55819440

线程创建4种方式

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

public class NewThreadDemo {

    public static void main(String[] args) throws Exception {

        //第一种方式 Thread
        Thread t1 = new Thread(){
            @Override
            public void run() {
                System.out.println("第1种方式:new Thread 1");
            }
        };
        t1.start();

        TimeUnit.SECONDS.sleep(1);

        //第二种方式 Runnable
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("第2种方式:new Thread 2");
            }
        });
        t2.start();

        TimeUnit.SECONDS.sleep(1);

        //第三种方式 Callable
        FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                String result = "第3种方式:new Thread 3";
                return result;
            }
        });
        Thread t3 = new Thread(futureTask);
        t3.start();

        // 线程执行完,才会执行get(),所以FutureTask也可以用于闭锁
        String result = futureTask.get();
        System.out.println(result);

        TimeUnit.SECONDS.sleep(1);

         //第四种方式 线程池
        ExecutorService pool = Executors.newFixedThreadPool(5);

        Future<String> future = pool.submit(new Callable<String>(){
            @Override
            public String call() throws Exception {
                String result = "第4种方式:new Thread 4";
                return result;
            }
        });

        pool.shutdown();
        System.out.println(future.get());
    }
}

用Runnable还是Thread?

大家都知道我们可以通过继承Thread类或者调用Runnable接口来实现线程,问题是,那个方法更好呢?什么情况下使用它?这个问题很容易回答,如果你知道Java不支持类的多重继承,但允许你调用多个接口。所以如果你要继承其他类,当然是调用Runnable接口好了。

Java中Runnable和Callable有什么不同?

Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。

线程暂停

Thread.sleep() (睡眠,不释放锁)

线程类方法。

sleep()方法需要指定等待的时间,它可以让当前正在执行该语句的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是sleep()方法不会释放“锁标志”,也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据。

public class TestThreadSleep implements Runnable{
    public static void main(String[] args) {  

        TestThreadSleep runnable = new TestThreadSleep();  
        Thread thread = new Thread(runnable);  
        thread.start();  
    }  
    @Override  
    public void run() {  

        System.out.println("i am sleep for a while!");  
        try {  
            Date currentTime = new Date();  
            long startTime = currentTime.getTime();  
            Thread.sleep(4000);  
            currentTime = new Date();  
            long endTime = currentTime.getTime();  
            System.out.println("休眠时间为:"+(endTime-startTime)+"ms");  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}

thread.join() (主线程等待子线程)

线程对象实例方法。

等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。

在很多情况下,主线程创建并启动了线程,如果子线程中进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。

package concurrent;

public class TestJoin {

    public static void main(String[] args) {
        Thread thread = new Thread(new JoinDemo());
        thread.start();

        for (int i = 0; i < 20; i++) {
            System.out.println("主线程第" + i + "次执行!");
            if (i >= 2)
                try {
                    // t1线程合并到主线程中,主线程停止执行过程,转而执行t1线程,直到t1执行完毕后继续。
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        }
    }
}

class JoinDemo implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("线程1第" + i + "次执行!");
        }
    }
}

Thread.yield() (礼让CPU,重新回到就绪态)

线程类方法。

yield()只是使执行语句的当前线程重新回到可执行状态**,****所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。**yield()只能使同优先级或更高优先级的线程有执行的机会。调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。

public static void main(String[] args) {
    Runnable runnable = () -> {
        for (int i = 0; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "-----" + i);
            if (i % 20 == 0) {
                Thread.yield();
            }
        }
    };
    new Thread(runnable, "栈长").start();
    new Thread(runnable, "小蜜").start();
}

blog.csdn.net/youanyyou/a…

线程中断

Java中interrupt,interrupted 和 isInterrupted方法的区别?

segmentfault.com/a/119000002…

线程协作(wait,notify,notifyAll)

首先要注意的是:wait,notify,notifyAll 这三个方法跟 sleep,yield,join,interrupt,suspend,resume,stop 这些不一样,后者是 Thread 特有的方法,前者是 Object 的方法。

锁池和等待池

锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。

等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中

notify和notifyAll的区别

  • 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁
  • 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
  • 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
  • 综上,所谓唤醒线程,另一种解释可以说是将线程由等待池移动到锁池,notifyAll调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而notify只会唤醒一个线程。
public class WaitNotifyCase {
    public static void main(String[] args) {
        final Object object = new Object();//锁

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread A is waiting to get lock");
                synchronized (object) {
                    try {
                        System.out.println("thread A get lock");
                        TimeUnit.SECONDS.sleep(1);
                        System.out.println("thread A do wait method");
                        object.wait();
                        System.out.println("wait end");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread B is waiting to get lock");
                synchronized (object) {
                    System.out.println("thread B get lock");
                    try {
                        TimeUnit.SECONDS.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    object.notify();
                    System.out.println("thread B do notify method");
                }
            }
        }).start();
    }
}

参考:www.zhihu.com/question/37…

觉得有收获的话帮忙点个赞吧,让有用的知识分享给更多的人

## 欢迎关注掘金号:五点半社

## 关注微信公众号:五点半社(工薪族的财商启蒙)##