Java 多线程常见知识点

2019-06-05  本文已影响0人  wuchao226

线程是什么?

从操作系统的角度,可以简单认为,线程是系统调度的最小单元,一个进程可以包含多个线程,作为任务的真正运行者,有自己的栈(Stack)、寄存器(Register)、本地存储(Thread Local)等,但是会和进程内其他线程共享文件描述符、虚拟地址空间等。

在具体实现中,线程还分为内核线程、用户线程,Java 的线程实现其实是与虚拟机相关的。

如果我们来看 Thread 的源码,你会发现其基本操作逻辑大都是以 JNI 形式调用的本地代码。

private native void start0();
private native void setPriority0(int newPriority);
private native void interrupt0();

什么是线程安全?

线程安全是一个多线程环境下正确性的概念,也就是保证多线程环境下共享的、可修改的状态的正确性,这里的状态反应在程序中其实可以看做是数据。即可变资源(内存)线程间共享

换个角度来看,如果状态不是共享的,或者不是可修改的,也就不存在线程安全问题,进而可以推理出保证线程安全的两个办法:

线程安全需要保证几个基本特性:

保证可见性的方法?

保证原子性的方法?

如何实现线程安全?

1、进程和线程的区别

2、Thread 中的 start 和 run 方法的区别

Thread类是Java里对线程概念的抽象,可以这样理解:我们通过new Thread() 其实只是 new 出一个 Thread 的实例,还没有操作系统中真正的线程挂起钩来。 只有执行了 start()方法后,才实现了真正意义上的启动线程。

start()方法让一个线程进入就绪队列等待分配 cpu,分到 cpu 后才调用实现 的 run()方法,start()方法不能重复调用,如果重复调用会抛出异常。

而 run 方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方 法并没有任何区别,可以重复执行,也可以被单独调用。

3、一个线程两次调用start()方法会出现什么情况?

Java 的线程是不允许启动两次的,第二次调用必然会抛出 IllegalThreadStateException,这是一种运行时异常,多次调用 start 被认为是编程错误。

在第二次调用 start() 方法的时候,线程可能处于终止或者其他(非 NEW)状态,但是不论如何,都是不可以再次启动的。

4、Thread 和 Runnable 是什么关系及区别

关系:

区别:
Thread 才是 Java 里对线程的唯一抽象,Runnable 只是对任务(业务逻辑) 的抽象。Thread 可以接受任意一个 Runnable 的实例并执行。

5、有几种新启线程的方式?

有两种

启动线程的方式有:

从 Thread.java 源码中可知,有两种启动线程的方式。

6、如何给 run() 方法传参

具体详解可参考:Java多线程:向线程传递参数的三种方法

7、如何实现处理线程的返回值

主线程等待法

缺点:需要自己实现循环等待的逻辑,当需要等待的变量一多,代码变得臃肿;也没法精准的控制
代码示例如下:

public class CycleWait implements Runnable {

    private String value;

    @Override
    public void run() {

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        value = "have data";
    }

    public static void main(String[] args) {
        CycleWait cycleWait = new CycleWait();
        Thread thread = new Thread(cycleWait);
        thread.start();
        while (cycleWait.value == null) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("value = " + cycleWait.value);
    }
}
使用 Thread 类的 join() 阻塞当前线程以等待子线程处理完毕

代码示例如下:

public class CycleWait implements Runnable {

    private String value;

    @Override
    public void run() {

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        value = "have data";
    }

    public static void main(String[] args) {
        CycleWait cycleWait = new CycleWait();
        Thread thread = new Thread(cycleWait);
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("value = " + cycleWait.value);
    }
}
通过 Callable 接口实现:通过FutureTask or 线程池获取

Callable 接口的实现类MyCallable示例如下:

public class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        String value = "test";
        System.out.println("Ready to work");
        Thread.sleep(5000);
        System.out.println("task done");
        return value;
    }
}

FutureTask 示例如下

public class FutureTaskDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> task = new FutureTask<>(new MyCallable());
        new Thread(task).start();
        if (!task.isDone()) {
            System.out.println("task has not finish, please wait");
        }
        System.out.println("task return:" + task.get());
    }
}

线程池 示例如下

public class ThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        Future<String> future = executor.submit(new MyCallable());
        if (!future.isDone()) {
            System.out.println("task has not finish, please wait");
        }
        try {
            System.out.println("task return:" + future.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            //关闭线程池
            executor.shutdown();
        }
    }
}

FutureTask 和 线程池 示例运行结果


8、线程的状态

线程状态被明确定义在其公共内部枚举类型 java.lang.Thread.State 中,分别是:

状态和方法之间的对应图:

9、sleep 和 wait 的区别

10、notify 和 notifyAll 的区别

锁池

假设线程 A 已经拥有了某个对象(不是类)的锁,而其它线程B、C想要调用这个对象的某个 Synchronized 方法(或者块),由于B、C线程在进入对象的 Synchronized 方法(或者块)之前必须先获得该对象锁的拥有权,而恰巧该对象的锁目前正被线程 A 所占用,此时 B、C线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池

11、yield

当调用Thread.yield()函数时,会给线程调度器一个当前线程愿意让出CPU使用的暗示,但是线程调度器可能会忽略这个暗示。

12、 如何中断线程

要么是 run 执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。

暂停、恢复和停止操作对应在线程 Thread 的 API 就是 suspend()、resume() 和 stop()。但是这些 API 是过期的,也就是不建议使用的。不建议使用的原因主要有:以 suspend() 方法为例,在调用后,线程不会释放已经占有的资源(比如 锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop() 方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为 suspend()、 resume()和 stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方 法。

安全的中止则是其他线程通过调用某个线程 A 的 interrupt()方法对其进行中断操作, 中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表 线程 A 会立即停止自己的工作,同样的 A 线程完全可以不理会这种中断请求。 因为 java 里的线程是协作式的,不是抢占式的。线程通过检查自身的中断标志位是否被置为 true 来进行响应

线程通过方法 isInterrupted()来进行判断是否被中断,也可以调用静态方法 Thread.interrupted()来进行判断当前线程是否被中断,不过 Thread.interrupted()会同时将中断标识位改写为 false

如果一个线程处于了阻塞状态(如线程调用了 thread.sleep、thread.join、 thread.wait 等),则在线程在检查中断标示时如果发现中断标示为 true,则会在 这些阻塞方法调用处抛出 InterruptedException 异常,并且在抛出异常后会立即 将线程的中断标示位清除,即重新设置为 false

不建议自定义一个取消标志位来中止线程的运行。因为 run 方法里有阻塞调用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取消标志。这种情况下,使用中断会更好,因为,

注意:处于死锁状态的线程无法被中断

被抛弃的方法

目前使用的方法

13、线程状态

线程状态

yield()方法

使当前线程让出 CPU 占有权,但让出的时间是不可设定的。也不会释放锁资源。注意:并不是每个线程都需要这个锁的,而且执行 yield( )的线程不一定就会持有锁,我们完全可以在释放锁后再调用 yield 方法。 所有执行 yield() 的线程有可能在进入到就绪状态后会被操作系统再次选中马上又被执行。

join()方法

把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。 比如在线程 B 中调用了线程 A 的 Join()方法,直到线程 A 执行完毕后,才会继续 执行线程 B。

示例:

/**
 * 演示Join()方法的使用
 */
public class UseJoin {
    static class Goddess implements Runnable {
        private Thread thread;

        public Goddess(Thread thread) {
            this.thread = thread;
        }

        public Goddess() {
        }

        public void run() {
            System.out.println("Goddess开始排队打饭.....");
            try {
                if(thread!=null) thread.join();
            } catch (InterruptedException e) {
            }
            SleepTools.second(2);//休眠2秒
            System.out.println(Thread.currentThread().getName()
                    + " Goddess打饭完成.");
        }
    }

    static class GoddessBoyfriend implements Runnable {

        public void run() {
            SleepTools.second(2);//休眠2秒
            System.out.println("GoddessBoyfriend开始排队打饭.....");
            System.out.println(Thread.currentThread().getName()
                    + " GoddessBoyfriend打饭完成.");
        }
    }

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

        Thread lison = Thread.currentThread();
        GoddessBoyfriend goddessBoyfriend = new GoddessBoyfriend();
        Thread gbf = new Thread(goddessBoyfriend);
        Goddess goddess = new Goddess(gbf);
        //Goddess goddess = new Goddess();
        Thread g = new Thread(goddess);
        g.start();
        gbf.start();
        System.out.println("lison开始排队打饭.....");
        g.join();
        SleepTools.second(2);//让主线程休眠2秒
        System.out.println(Thread.currentThread().getName() + " lison打饭完成.");
    }
}

运行结果:

线程的优先级

在 Java 线程中,通过一个整型成员变量 priority 来控制优先级,优先级的范围从 1~10,在线程构建的时候可以通过 setPriority(int) 方法来修改优先级,默认优先级是 5,优先级高的线程分配时间片的数量要多于优先级低的线程。 设置线程优先级时,针对频繁阻塞(休眠或者 I/O 操作)的线程需要设置较 高优先级,而偏重计算(需要较多 CPU 时间或者偏运算)的线程则设置较低的 优先级,确保处理器不会被独占。在不同的 JVM 以及操作系统上,线程规划会 存在差异,有些操作系统甚至会忽略对线程优先级的设定。

守护线程

Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调 度以及支持性工作。这意味着,当一个 Java 虚拟机中不存在非 Daemon 线程的 时候,Java 虚拟机将会退出。可以通过调用 Thread.setDaemon(true) 将线程设置 为 Daemon 线程。我们一般用不上,比如垃圾回收线程就是 Daemon 线程。

Daemon 线程被用作完成支持性工作,但是在 Java 虚拟机退出时 Daemon 线程中的 finally 块并不一定会执行。在构建 Daemon 线程时,不能依靠 finally 块中的内容来确保执行关闭或清理资源的逻辑。

示例:

/**
 * 守护线程的使用
 */
public class DaemonThread {
    private static class UseThread extends Thread {
        @Override
        public void run() {
            try {
                while (!isInterrupted()) {
                    System.out.println(Thread.currentThread().getName()
                            + " I am extends Thread.");
                }
                System.out.println(Thread.currentThread().getName()
                        + " interrupt flag is " + isInterrupted());
            } finally {
                //守护线程中finally不一定起作用
                System.out.println(" .............finally");
            }
        }
    }

    public static void main(String[] args)
            throws InterruptedException, ExecutionException {
        UseThread useThread = new UseThread();
        useThread.setDaemon(true);
        useThread.start();
        Thread.sleep(5);
        useThread.interrupt();
    }
}
上一篇 下一篇

猜你喜欢

热点阅读