java多线程基础

java是一个天生就支持多线程的语言。

查看JVM内的线程

public static void main(String[] args) {
        //虚拟机线程管理的接口
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        ThreadInfo[] threadInfos =
                threadMXBean.dumpAllThreads(false, false);

        for(ThreadInfo threadInfo:threadInfos) {
            System.out.println("["+threadInfo.getThreadId()+"]"+" "
                    +threadInfo.getThreadName());
        }
    }

[8] JDWP Command Reader
[7] JDWP Event Helper Thread
[6] JDWP Transport Listener: dt_socket
[5] Attach Listener
[4] Signal Dispatcher
[3] Finalizer
[2] Reference Handler
[1] main

我们发现启动一个main方法,JVM虚拟机会给我们启动8个线程。

新建线程的3种方式

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口

Callable和Runnable的区别

  1. Callable有返回值,Runnable没有。
  2. Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
  3. Runnable的执行 new Thread(runnable).start(); Callable的执行需要用到FutureTask。

Runnable接口

    @Test
    public void runnableTest() {
        // JDK 1.7
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "::Runnable异步任务1");
            }
        }).start();

        // JDK 1.8
        new Thread(() -> System.out.println(Thread.currentThread().getName() + "::Runnable异步任务2")).start();
    }

输出结果:
Thread-3::Runnable异步任务1
Thread-4::Runnable异步任务2

实现Callable接口

    @Test
    public void callableTest() {
        ExecutorService service = Executors.newFixedThreadPool(10);
        // JDK 1.7
        FutureTask<String> futureTask1 = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return Thread.currentThread().getName() + "::Callable异步任务1";
            }
        });

        // JDK 1.8
        FutureTask<String> futureTask2 = new FutureTask<>(() -> Thread.currentThread().getName() + "::Callable异步任务2");

        service.submit(futureTask1);
        service.submit(futureTask2);
        try {
            System.out.println(futureTask1.get(5, TimeUnit.SECONDS));
            System.out.println(futureTask2.get(5, TimeUnit.SECONDS));
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
    }

输出结果:
pool-1-thread-1::Callable异步任务1
pool-1-thread-2::Callable异步任务2

停止一个线程

上面的示例是如和新建并启动一个异步线程,下面我们来说一下如何停止一个线程。

  • 线程自然终止(自然执行完或抛出未处理异常)
  • stop():这是一个抢占式方法不建议使用,stop()是直接停止线程,而不管该线程的资源是否已经释放掉了。
  • suspend(),resume(): 线程的挂起与恢复,也不建议使用,因为suspend在调用过程中不会释放其占用的共享资源如:锁,容易引起死锁。
  • interrupt():该方法是一个协作式方法,并不会强制关闭线程,他只是将线程的中断标志位设置成true(中断线程),默认是false(不中断线程),线程是否中断由线程自己决定,推荐使用这种方式去关闭线程。

使用interrupt中断线程,我们必须在线程内部对线程的中断状态进行处理,否则即使调用了interrupt()方法也不会有效果。那么我们怎么知道线程中断状态呢,有两个方法可以知道线程是否处于中断状态:

  • isInterrupted() :它是一个无副作用的方法,可以重复调用。
  • 静态的interrupted():它是一个有无副作用的方法,在调用的时候会同时将中断标志为设置成false。

注意:方法里如果抛出InterruptedException异常,线程的中断标志位会被复位成false,如果确实是需要中断线程,我们需要自己在catch语句块里再次调用interrupt()方法。

/**
 * 中断线程有异常
 *
 * @author yuhao.wang3
 */
public class HasInterrputException {
    public static void main(String[] args) throws InterruptedException {
        Thread endThread = new Thread(() -> {
            String threadName = Thread.currentThread().getName();
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    System.out.println("UseThread:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS")));
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    System.out.println(threadName + " catch interrput flag is " + Thread.currentThread().isInterrupted() +
                            " at " + (LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS"))));
                    Thread.currentThread().interrupt();
                    e.printStackTrace();
                }
                System.out.println(threadName);
            }
            System.out.println(threadName + " interrput flag is " + Thread.currentThread().isInterrupted());
        });
        endThread.start();
        System.out.println("Main:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS")));
        Thread.sleep(800);
        System.out.println("Main begin interrupt thread:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS")));
        endThread.interrupt();
    }
}

输出结果:

Main:2019-03-20 19:57:35_221
thread:2019-03-20 19:57:35_221
Main begin interrupt thread:2019-03-20 19:57:36_035
Thread-0 catch interrput flag is false at 2019-03-20 19:57:36_035
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.xiaolyuh.HasInterrputException.lambda$main$0(HasInterrputException.java:18)
    at java.lang.Thread.run(Thread.java:745)
Thread-0
Thread-0 interrput flag is true

Process finished with exit code 0

如果将 catch里面的Thread.currentThread().interrupt();去掉,那么线程就会永远执行。

线程的几种状态

我们刚刚说了线程的启动的和停止,那么一个线程到底有几种状态呢?

微信图片_20190327183718.jpg

我们线程一共有5个状态,几种状态的扭转看上图。

yield():方法也是一个协作式方法,他会让出CPU执行权,但是下一次CPU调度的时候该线程依然有可能再次获取到CPU执行权,并执行。
sleep():阻塞线程,让出CPU执行权,但是不会释放锁,到了时间后会继续执行;状态切换到阻塞和可运行都会产生上下文切换;多用于使当前线程等待;这是Thread的方法,不需要获取到锁就可以执行。
wait():阻塞线程,让出CPU执行权,会释放锁,唤醒后需要重新获取锁才能执行;状态切换到阻塞和可运行都会产生上下文切换,但是切换到可运行时候还有一锁池状态,必须要获取到锁后才会切换到可运行状态;多用于线程间的通信,如果是多线程编程建议使用该方法,减少cpu上下文的切换;这是Object的方法,必需要获取到锁才可以执行。
notify():唤醒等待队列最前面的一个线程,该方法不会立即唤醒线程,而是需要等方法体执行完了才会执行唤醒动作;这是Object的方法,必需要获取到锁才可以执行。
notifyAll():唤醒等待队列里面所有的线程,该方法不会立即唤醒线程,而是需要等方法体执行完了才会执行唤醒动作;这是Object的方法,必需要获取到锁就才可以执行。
join():线程插队,当t1线程在执行的过程中调用了t2线程的join()方法,那么t1线程会挂起(内部调用的是wait/notify机制),t2线程会立即执行。

java线程分类

java线程分为守护线程和非守护线程。

  • 守护线程:和主线程一起结束的线程,叫守护线程,我们的垃圾回收线程就是一个守护线程。
  • 非守护线程:主线程的结束不影响线程的执行的线程,也叫用户线程。

注意:

  • 调用 t.setDaemon(true) 方法可以将用户线程设置成守护线程。
  • 守护线程结束时不能保证finally语句块一定执行。
/**
 * 守护线程
 *
 * @author yuhao.wang3
 */
public class DaemonThread {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                while (!Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName()
                            + " 任务执行 " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS")));
                    Thread.sleep(50);
                }
                System.out.println(Thread.currentThread().getName()
                        + " 任务中断 " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS")));
            } catch (InterruptedException e) {
                e.printStackTrace();
                Thread.currentThread().interrupt();
            } finally {
                System.out.println("...........finally");
            }
        });
        thread.setDaemon(true);
        thread.start();
        Thread.sleep(500);
    }
}

输出结果:

"C:\Program Files\Java\jdk1.8.0_112\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:53547,suspend=y,server=n -Dvisualvm.id=210666216419015 -javaagent:C:\Users\yuhao.wang3\.IntelliJIdea2018.3\system\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_112\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\rt.jar;D:\workspace\spring-boot-student\spring-boot-student-concurrent\target\classes;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot-starter-web\1.5.13.RELEASE\spring-boot-starter-web-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot-starter-tomcat\1.5.13.RELEASE\spring-boot-starter-tomcat-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\apache\tomcat\embed\tomcat-embed-core\8.5.31\tomcat-embed-core-8.5.31.jar;C:\Users\yuhao.wang3\.m2\repository\org\apache\tomcat\tomcat-annotations-api\8.5.31\tomcat-annotations-api-8.5.31.jar;C:\Users\yuhao.wang3\.m2\repository\org\apache\tomcat\embed\tomcat-embed-el\8.5.31\tomcat-embed-el-8.5.31.jar;C:\Users\yuhao.wang3\.m2\repository\org\apache\tomcat\embed\tomcat-embed-websocket\8.5.31\tomcat-embed-websocket-8.5.31.jar;C:\Users\yuhao.wang3\.m2\repository\org\hibernate\hibernate-validator\5.3.6.Final\hibernate-validator-5.3.6.Final.jar;C:\Users\yuhao.wang3\.m2\repository\javax\validation\validation-api\1.1.0.Final\validation-api-1.1.0.Final.jar;C:\Users\yuhao.wang3\.m2\repository\org\jboss\logging\jboss-logging\3.3.2.Final\jboss-logging-3.3.2.Final.jar;C:\Users\yuhao.wang3\.m2\repository\com\fasterxml\classmate\1.3.4\classmate-1.3.4.jar;C:\Users\yuhao.wang3\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.8.11.1\jackson-databind-2.8.11.1.jar;C:\Users\yuhao.wang3\.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.8.0\jackson-annotations-2.8.0.jar;C:\Users\yuhao.wang3\.m2\repository\com\fasterxml\jackson\core\jackson-core\2.8.11\jackson-core-2.8.11.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-web\4.3.17.RELEASE\spring-web-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-aop\4.3.17.RELEASE\spring-aop-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-beans\4.3.17.RELEASE\spring-beans-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-context\4.3.17.RELEASE\spring-context-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-webmvc\4.3.17.RELEASE\spring-webmvc-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-expression\4.3.17.RELEASE\spring-expression-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot-starter\1.5.13.RELEASE\spring-boot-starter-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot\1.5.13.RELEASE\spring-boot-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\1.5.13.RELEASE\spring-boot-autoconfigure-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot-starter-logging\1.5.13.RELEASE\spring-boot-starter-logging-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\ch\qos\logback\logback-classic\1.1.11\logback-classic-1.1.11.jar;C:\Users\yuhao.wang3\.m2\repository\ch\qos\logback\logback-core\1.1.11\logback-core-1.1.11.jar;C:\Users\yuhao.wang3\.m2\repository\org\slf4j\jcl-over-slf4j\1.7.25\jcl-over-slf4j-1.7.25.jar;C:\Users\yuhao.wang3\.m2\repository\org\slf4j\jul-to-slf4j\1.7.25\jul-to-slf4j-1.7.25.jar;C:\Users\yuhao.wang3\.m2\repository\org\slf4j\log4j-over-slf4j\1.7.25\log4j-over-slf4j-1.7.25.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-core\4.3.17.RELEASE\spring-core-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\yaml\snakeyaml\1.17\snakeyaml-1.17.jar;C:\Users\yuhao.wang3\.m2\repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;C:\Users\yuhao.wang3\.m2\repository\com\h2database\h2\1.4.197\h2-1.4.197.jar;D:\Program Files\JetBrains\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar" com.xiaolyuh.DaemonThread
Connected to the target VM, address: '127.0.0.1:53547', transport: 'socket'
Thread-0 任务执行 2019-03-20 20:39:43_667
Thread-0 任务执行 2019-03-20 20:39:43_731
Thread-0 任务执行 2019-03-20 20:39:43_781
Thread-0 任务执行 2019-03-20 20:39:43_832
Thread-0 任务执行 2019-03-20 20:39:43_883
Thread-0 任务执行 2019-03-20 20:39:43_934
Thread-0 任务执行 2019-03-20 20:39:43_985
Thread-0 任务执行 2019-03-20 20:39:44_036
Thread-0 任务执行 2019-03-20 20:39:44_089
Thread-0 任务执行 2019-03-20 20:39:44_140
Disconnected from the target VM, address: '127.0.0.1:53547', transport: 'socket'

Process finished with exit code 0

我们可以发现finally 语句块就没执行。当thread.setDaemon(true);这句去掉时,即使主线程结果了,子线程也会一直运行下去。

源码

https://github.com/wyh-spring-ecosystem-student/spring-boot-student/tree/releases

spring-boot-student-concurrent 工程

layering-cache

为监控而生的多级缓存框架 layering-cache这是我开源的一个多级缓存框架的实现,如果有兴趣可以看一下

推荐阅读更多精彩内容

  • 写在前面的话: 这篇博客是我从这里“转载”的,为什么转载两个字加“”呢?因为这绝不是简单的复制粘贴,我花了五六个小...
    SmartSean阅读 1,849评论 11 39
  • 转自 http://blog.csdn.net/ChatHello/article/details/6906097...
    lvcyong阅读 2,255评论 0 68
  • 进程:正在执行中的程序,其实是应用程序在内存中运行的那片空间。 线程:进程中的一个执行单元,负责进程中程序的执行。...
    丹青笔阅读 125评论 2 7
  • 1 多线程的引入 1.1 进程与线程 在学习多线程之前,我们应该明白线程是什么,进程是什么,以及它们的联系与区别,...
    LeiLv阅读 294评论 0 3
  • 前些日子,一家人跑去逛商场,恰好有酸奶试吃的活动,于是给小宝宝来了两口,就像这个样子: 宝宝初尝酸奶味...
    潇洒一生_4c16阅读 94评论 0 0