美文网首页并发编程
java多线程基础

java多线程基础

作者: xiaolyuh | 来源:发表于2019-04-30 13:52 被阅读31次

    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,线程是否中断由线程自己决定,推荐使用这种方式去关闭线程。

    使用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这是我开源的一个多级缓存框架的实现,如果有兴趣可以看一下

    相关文章

      网友评论

        本文标题:java多线程基础

        本文链接:https://www.haomeiwen.com/subject/ulcimqtx.html