Java并发编程学习笔记2——Java线程

目录

1、创建和运行线程

1.1、方法1——直接使用Thread

1.2、方法2——使用Runnable配合Thread

1.3、方法3——FutureTask配合Thread

2、原理之Thread与Runnable的关系

3、观察多个线程同时运行

4、查看进程线程的方法

4.1、Windows

4.2、Linux

4.3、Java

5、原理之线程运行

5.1、栈与栈帧

5.2、线程上下文切换(Thread Context Switch)

6、线程中的常见方法

6.1、start()与run()

6.2、sleep()与yield()

6.2.1、线程优先级

6.3、join()

6.3.1、为什么需要join()

6.3.2、有时效的join()

6.4、interrupt()

6.4.1、打断sleep(),wait(),join()的线程

6.4.2、打断正常运行的线程

6.4.3、模式之两阶段终止

6.4.4、打断park线程

6.5、不推荐的方法

6.6、主线程与守护线程

6.7、线程的五种状态

6.8、线程的六种状态

7、案例

7.1、案例1——防止CPU占用100%

7.1.1、sleep实现

7.1.2、wait实现

7.2、案例2——应用之同步


1、创建和运行线程

1.1、方法1——直接使用Thread

        // 创建线程对象
        Thread t = new Thread() {
          public void run() {
              // 要执行的任务
          }
        };
        // 启动线程
        t.start();

例如:

        // 构造方法的参数是给线程指定名字,推荐
        Thread t1 = new Thread("t1") {
            public void run() {
                log.debug("hello");
            }
        };
        t1.start();

1.2、方法2——使用Runnable配合Thread

把【线程】和【任务】(要执行的代码)分开。

  • Thread代表线程;
  • Runnable可运行的任务(线程要执行的代码);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                log.debug("hello");
            }
        };
        // 参数1是任务对象,参数2是线程名字,推荐
        Thread t2 = new Thread(runnable, "t2");
        t2.start();

Java8以后可以使用lambda精简代码。

        // 创建任务对象
        Runnable task = () -> log.debug("hello");
        // 参数1是任务对象,参数2是线程名字,推荐
        Thread t3 = new Thread(task, "t3");
        t3.start();

1.3、方法3——FutureTask配合Thread

FutureTask能够接收Callable类型的参数,用来处理有返回结果的情况。

        // 创建任务对象
        FutureTask task3 = new FutureTask(new Callable() {
            @Override
            public Integer call() throws Exception {
                log.debug("hello");
                return 100;
            }
        });

        // 参数1 是任务对象;参数2 是线程名字,推荐
        new Thread(task3, "t3").start();

        // 主线程调用get()方法导致自身阻塞,同步等待task执行完毕的结果
        Integer result = task3.get();
        log.debug("结果是:{}", result);


结果:
18:47:17.417 [t3] DEBUG com.bfbc.test.Test2 - hello
18:47:17.420 [main] DEBUG com.bfbc.test.Test2 - 结果是:100

2、原理之Thread与Runnable的关系

  • 方法1是把线程和任务合并在了一起,方法2是吧线程和任务分开了;
  • 用Runnable更容易与线程池等高级API配合;
  • 用Runnable让任务类脱离了Thread继承体系,更灵活。

3、观察多个线程同时运行

        new Thread(() -> {
            while(true) {
                log.debug("running");
            }
        }, "t1").start();

        new Thread(() -> {
            while(true) {
                log.debug("running");
            }
        }, "t2").start();

4、查看进程线程的方法

4.1、Windows

  • 任务管理器可以查看进程和线程数,也可以用来杀死进程;
  • tasklist查看进程;
  • taskkill杀死进程。

4.2、Linux

  • ps -fe:查看所有进程;
  • ps -fT -p :查看某个进程(PID)的所有线程;
  • kill:杀死进程;
  • top:按大写H切换是否显示进程;
  • top -H -p :查看某个进程(PID)的所有线程。

4.3、Java

  • jps命令查看所有Java进程;
  • jstask :查看某个Java进程(PID)的所有线程状态;
  • jconsole:查看某个Java进程中线程的运行情况(图形界面)。

jconsole远程监控配置:

  1. 需要以如下方式运行你的java类:java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=`连接端口` -Dcom.sun.management.jmxremote.ssl=是否安全连接 -Dcom.sun.management.jmxremote.authenticate=是否认证 java类
  2. 修改/etc/hosts文件将127.0.0.1映射至主机名。

5、原理之线程运行

5.1、栈与栈帧

Java Virtual Machine Stacks(Java虚拟机栈)。我们都知道JVM中由堆、栈、方法区所组成,其中栈内存是给谁使用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。

  • 每个栈由多个栈帧(Frame)组成,对应每次方法调用时所占用的内存;
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。
public class TestFrames {
    public static void main(String[] args) {
        method1(10);
    }

    private static void method1(int x) {
        int y = x + 1;
        Object m = method2();
        System.out.println(m);
    }

    private static Object method2() {
        Object o = new Object();
        return o;
    }
}

5.2、线程上下文切换(Thread Context Switch)

因为以下一些原因导致CPU不再执行当前的线程,转而执行另一个线程的代码。

  • 线程的CPU时间片用完;
  • 垃圾回收;
  • 有更高优先级的线程需要运行;
  • 线程自己调用了sleep、yield、wait、join、park、synchronized、lock等方法。

当Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条JVM指令的执行地址,是线程私有的。

  • 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等;
  • Context Switch频繁发生会影响性能。

6、线程中的常见方法

方法名 static 功能说明 注意
start() 启动一个新线程,在新的线程运行run()方法中的代码

start()方法只是让线程进入就绪,里面代码不一定立刻运行(CPU的时间片还没分给它)。每个线程对象的start()方法只能调用一次,如果调用了多次会出现IllegalThreadStateException。

run() 新线程启动后会调用的方法

如果在构造Thread对象时传递了Runnable参数,则

线程启动后会调用Runnable中的run()方法,否则默认不执行任何操作,但可以创建Thread的子类对象,来覆盖默认行为。

join() 等待线程运行结束
join(long n) 等待线程运行结束,最多等待n毫秒
getId() 获取线程长整型的id id唯一。
getName() 获取线程名
setName() 修改线程名
getPriority() 获取线程优先级
setPriority() 修改线程优先级

Java中规定线程优先级是1-10的整数,较大的优先级能提高该线程被CPU调度的几率。

getState() 获取线程状态

Java中线程状态使用6个enum表示,分别为:NEW,RUNNABLE,BLOCKED,WAITING

,TIMED_WAITING,TERMINATED

isInterrupted() 判断是否被打断 不会清除打断标记
isAlive() 线程是否存活(还没有运行完毕)
interrupt() 打断线程 如果打断线程正在sleep、wait、join会导致被打断的线程抛出InterruptException,并清除打断标记;如果打断的正在运行的线程,则会设置打断标记;park的线程被打断,也会设置打断标记。
interrupted() static 判断当前线程是否被打断 会清除打断标记
currentThread() static 获取当前正在执行的线程
sleep(long n) static 让当前执行的线程休眠n毫秒,休眠时间让出CPU的时间片给其他线程
yield() static 提示线程调度器让出当前线程对CPU的使用 主要是为了测试和调试

6.1、start()与run()

    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };

        t1.run();
    }

结果:
19:24:07.580 [main] DEBUG com.bfbc.test.Test5 - running...
    public static void main(String[] args) {
        Thread t2 = new Thread() {
            @Override
            public void run() {
                log.debug("running...");
            }
        };

        System.out.println(t2.getState());
        t2.start();
        System.out.println(t2.getState());
    }

结果:
NEW
RUNNABLE
19:24:49.309 [Thread-0] DEBUG com.bfbc.test.Test6 - running...

6.2、sleep()与yield()

sleep

  • 调用sleep会让当前线程从Running进入Timed Waiting状态;
  • 其他线程可以使用interrupt()方法打断正在睡眠的线程,这时sleep()方法会抛出InterruptException;
  • 睡眠结束后的线程未必会立刻得到执行;
  • 建议用TimeUnit的sleep()来代替Thread的sleep()来获得更好的可读性。

yield

  • 调用yield会让当前线程从Running状态进入Runnable状态,然后调度执行其他同优先级的线程,如果这时没有同优先级的线程,那么不能保证当前线程暂停的效果;
  • 具体的实现依赖于操作系统的任务调度器。
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t1.start();

        Thread.sleep(500);
        log.debug("t1 state : {}", t1.getState());
    }

结果:
19:32:06.399 [main] DEBUG com.bfbc.test.Test7 - t1 state : TIMED_WAITING
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                log.debug("enter sleep...");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    log.debug("wake up...");
                    e.printStackTrace();
                }
            }
        };
        t1.start();

        Thread.sleep(1000);
        // TimeUnit.SECONDS.sleep(1); // 同Thread.sleep(1000)效果一样
        log.debug("interrupt...");
        t1.interrupt();
    }

结果:
19:36:22.727 [Thread-0] DEBUG com.bfbc.test.Test8 - enter sleep...
19:36:23.725 [main] DEBUG com.bfbc.test.Test8 - interrupt...
19:36:23.725 [Thread-0] DEBUG com.bfbc.test.Test8 - wake up...
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.bfbc.test.Test8$1.run(Test8.java:13)

6.2.1、线程优先级

  • 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它;
  • 如果CPU比较忙,那么优先级高的线程会获得更多的时间片,但CPU空闲时,优先级几乎没作用。
    public static void main(String[] args) {
        Runnable task1 = () -> {
            int count = 0;
            for (;;) {
                System.out.println("---->1" + count++);
            }
        };
        Runnable task2 = () -> {
            int count = 0;
            for (;;) {
//                Thread.yield();
                System.out.println("        ---->2" + count++);
            }
        };
        Thread t1 = new Thread(task1, "t1");
        Thread t2 = new Thread(task2, "t2");
        t1.setPriority(Thread.MIN_PRIORITY);
        t2.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
    }

6.3、join()

6.3.1、为什么需要join()

    private static int r = 0;
    public static void main(String[] args) {
        test1();
    }

    private static void test1() {
        log.debug("开始");
        Thread t1 = new Thread(() -> {
            log.debug("开始");
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("结束");
            r = 10;
        });
        t1.start();
        log.debug("结果为:{}", r);
        log.debug("结束");
    }

结果:
10:22:00.870 [main] DEBUG com.bfbc.test.Test12 - 开始
10:22:00.928 [Thread-0] DEBUG com.bfbc.test.Test12 - 开始
10:22:00.928 [main] DEBUG com.bfbc.test.Test12 - 结果为:0
10:22:00.930 [main] DEBUG com.bfbc.test.Test12 - 结束
10:22:00.930 [Thread-0] DEBUG com.bfbc.test.Test12 - 结束

分析:

  • 因为主线程和线程t1是并行执行的,t1线程需要1秒之后才能算出r=10;
  • 而主线程一开始就要打印r的结果,所以只能打印出r=0。

解决方法:用join(),加载t1.start()之后即可。

    private static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        test1();
    }

    private static void test1() throws InterruptedException {
        log.debug("开始");
        Thread t1 = new Thread(() -> {
            log.debug("开始");
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("结束");
            r = 10;
        });
        t1.start();
        t1.join(); // 主线程等待t1运行结束
        log.debug("结果为:{}", r);
        log.debug("结束");
    }

结果:
10:26:17.260 [main] DEBUG com.bfbc.test.Test12 - 开始
10:26:17.310 [Thread-0] DEBUG com.bfbc.test.Test12 - 开始
10:26:17.325 [Thread-0] DEBUG com.bfbc.test.Test12 - 结束
10:26:17.325 [main] DEBUG com.bfbc.test.Test12 - 结果为:10
10:26:17.325 [main] DEBUG com.bfbc.test.Test12 - 结束
    private static void test2() {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r = 10;
        });
        t1.start();
        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r = 20;
        });
        t2.start();
        try {
            log.debug("join begin");
            t1.join();
            log.debug("t1 join end");
            t2.join();
            log.debug("t2 join end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

结果:
10:37:12.509 [main] DEBUG com.bfbc.test.Test12 - join begin
10:37:13.510 [main] DEBUG com.bfbc.test.Test12 - t1 join end
10:37:14.510 [main] DEBUG com.bfbc.test.Test12 - t2 join end

6.3.2、有时效的join()

    private static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        test1();
    }

    private static void test1() throws InterruptedException {
        Thread t1 = new Thread(()-> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r = 10;
        });
        t1.start();

        //线程执行结束会导致join()结束
        log.debug("join begin");
        t1.join(1500);
        log.debug("{}", r);
    }

结果:
10:43:50.235 [main] DEBUG com.bfbc.test2.Test1 - join begin
10:43:51.739 [main] DEBUG com.bfbc.test2.Test1 - 0

6.4、interrupt()

6.4.1、打断sleep(),wait(),join()的线程

打断sleep()的线程,会清空打断状态(打断标记为false),以sleep()为例。

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("sleep...");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        t1.start();
        Thread.sleep(1000);
        log.debug("interrupt...");
        t1.interrupt();
        log.debug("打断标记:{}", t1.isInterrupted());
    }

结果:
10:50:40.342 [t1] DEBUG com.bfbc.test2.Test2 - sleep...
10:50:41.354 [main] DEBUG com.bfbc.test2.Test2 - interrupt...
10:50:41.354 [main] DEBUG com.bfbc.test2.Test2 - 打断标记:false
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.bfbc.test2.Test2.lambda$main$0(Test2.java:11)
	at java.lang.Thread.run(Thread.java:748)

6.4.2、打断正常运行的线程

打断正常运行的线程,不会清空打断状态(打断标记仍为true)。

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while(!Thread.currentThread().isInterrupted()) {
                log.debug("running...");
            }
            log.debug("被打断了,退出循环...");
        }, "t1");
        t1.start();
        Thread.sleep(1000);
        log.debug("interrupt...");
        t1.interrupt();
        log.debug("打断标记:{}", t1.isInterrupted());
    }

结果:
...
10:57:34.985 [t1] DEBUG com.bfbc.test2.Test2 - running...
10:57:34.985 [t1] DEBUG com.bfbc.test2.Test2 - running...
10:57:34.985 [t1] DEBUG com.bfbc.test2.Test2 - running...
10:57:34.993 [main] DEBUG com.bfbc.test2.Test2 - interrupt...
10:57:34.994 [main] DEBUG com.bfbc.test2.Test2 - 打断标记:true
10:57:34.996 [t1] DEBUG com.bfbc.test2.Test2 - 被打断了,退出循环...

6.4.3、模式之两阶段终止

两阶段终止模式(Two Phase Termination):在一个线程T1中如何“优雅”地终止线程T2?这里的“优雅”指的是给T2一个料理后事的机会。

错误思路

  • 使用线程对象的stop()方法停止线程。stop()方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其他线程将永远无法获取锁。
  • 使用System.exit(int)方法停止线程。目的仅是停止一个线程,但这种做法会让整个程序都停止。

正确思路

Java并发编程学习笔记2——Java线程_第1张图片

package com.bfbc.test2;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
        twoPhaseTermination.start();

        Thread.sleep(3500);
        twoPhaseTermination.stop();
    }
}

@Slf4j()
class TwoPhaseTermination {
    private Thread monitor;

    // 启动监控线程
    public void start() {
        monitor = new Thread(()-> {
            while(true) {
                Thread thread = Thread.currentThread();
                if (thread.isInterrupted()) {
                    log.debug("料理后事...");
                    break;
                }
                try {
                    Thread.sleep(1000); // 情况1
                    log.debug("执行监控记录..."); // 情况2
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // 重新设置打断标记
                    thread.interrupt();
                }
            }
        });
        monitor.start();
    }

    //停止监控线程(设置打断标记)
    public void stop() {
        monitor.interrupt();
    }
}

结果:
11:24:13.268 [Thread-0] DEBUG com.bfbc.test2.TwoPhaseTermination - 执行监控记录...
11:24:14.269 [Thread-0] DEBUG com.bfbc.test2.TwoPhaseTermination - 执行监控记录...
11:24:15.269 [Thread-0] DEBUG com.bfbc.test2.TwoPhaseTermination - 执行监控记录...
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.bfbc.test2.TwoPhaseTermination.lambda$start$0(Test3.java:30)
	at java.lang.Thread.run(Thread.java:748)
11:24:15.770 [Thread-0] DEBUG com.bfbc.test2.TwoPhaseTermination - 料理后事...

6.4.4、打断park线程

打断park线程,不会清空打断状态(打断标记仍为true)。

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("park...");
            LockSupport.park(); // 让当前线程停下来
            log.debug("unpark...");
            log.debug("打断状态:{}", Thread.interrupted());

            LockSupport.park();
            log.debug("unpark");
        }, "t1");
        t1.start();

        Thread.sleep(1000);
        t1.interrupt(); // t1停下来之后,打断t1,t1继续执行后面的代码
        Thread.sleep(1000);
        t1.interrupt(); // t1停下来之后,打断t1,t1继续执行后面的代码
    }

结果:
11:36:55.081 [t1] DEBUG com.bfbc.test2.Test4 - park...
11:36:56.096 [t1] DEBUG com.bfbc.test2.Test4 - unpark...
11:36:56.096 [t1] DEBUG com.bfbc.test2.Test4 - 打断状态:true
11:36:57.115 [t1] DEBUG com.bfbc.test2.Test4 - unpark

6.5、不推荐的方法

这些方法已经过时,容易破坏同步代码块,造成线程死锁。

方法名 static 功能说明
stop() 停止线程运行
suspend() 挂起(暂停)线程运行
resume() 恢复线程运行

6.6、主线程与守护线程

默认情况下,Java进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

    public static void main(String[] args) throws InterruptedException {
        log.debug("开始运行...");
        Thread t1 = new Thread(()->{
            log.debug("开始运行...");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("运行结束");
        }, "daemon");
        t1.setDaemon(true);
        t1.start();

        Thread.sleep(1000);
        log.debug("运行结束...");
    }

结果:
13:46:15.764 [main] DEBUG com.bfbc.test2.Test5 - 开始运行...
13:46:15.815 [daemon] DEBUG com.bfbc.test2.Test5 - 开始运行...
13:46:16.828 [main] DEBUG com.bfbc.test2.Test5 - 运行结束...

注意:

  • 垃圾回收器线程就是一种守护线程;
  • Tomcat中的Acceptor和Poller线程都是守护线程,所以Tomcat接收到shutdown命令后,不会等待它们处理完当前请求。

6.7、线程的五种状态

这是从操作系统层面来描述的。

Java并发编程学习笔记2——Java线程_第2张图片

  • 【初始状态】:仅是在语言层面创建了线程对象,还未与操作系统线程关联;
  • 【可运行状态(就绪状态)】:指该线程已经被创建(与操作系统线程关联),可以由CPU调度执行;
  • 【运行状态】:指获取了CPU时间片运行中的状态。当CPU时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程上下文切换,进入【阻塞状态】;
  • 【阻塞状态】:如果调用了阻塞API,如BIO读写文件,这时该线程实际不会用到CPU,会导致线程上下文切换,进入【阻塞状态】;等BIO操作完毕,会由操作系统唤醒阻塞进程,转换至【可运行状态】;与【可运行状态】的区别是,对【阻塞状态】的线程来说,只要它们一直不唤醒,调度器就一直不会考虑调度它们;
  • 【终止状态】:表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态。

6.8、线程的六种状态

这是从Java API层面来描述的。根据Thread.State枚举,分为六种状态:

  • 【NEW】:线程刚被创建,但是还没有调用start()方法;
  • 【RUNNABLE】:当调用了start()方法后,注意Java API层面的RUNNABLE状态涵盖了操作系统层面的【可运行状态】、【运行状态】和【阻塞状态】(由于BIO导致的线程阻塞,在Java里无法区分,仍然认为是可运行);BLOCKED、WAITING、TIMED_WAITING都是Java API层面对【阻塞状态】的细分。
  • 【TERMINATED】:当线程代码运行结束,进入此状态。
@Slf4j
public class Test6 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("running...");
        }, "t1");

        Thread t2 = new Thread(() -> {
            while (true) {
                // 运行 RUNNABLE 状态
            }
        }, "t2");
        t2.start();

        Thread t3 = new Thread(() -> {
            log.debug("running...");
        }, "t3");
        t3.start();

        Thread t4 = new Thread(() -> {
            synchronized (Test6.class) {
                try {
                    Thread.sleep(1000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t4");
        t4.start();

        Thread t5 = new Thread(()->{
            try {
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t5");
        t5.start();

        Thread t6 = new Thread(()->{
            synchronized (Test6.class) { // BLOCKED 
                try {
                    Thread.sleep(1000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t6");
        t6.start();

        Thread.sleep(500);

        log.debug("t1 state {}", t1.getState());
        log.debug("t2 state {}", t2.getState());
        log.debug("t3 state {}", t3.getState());
        log.debug("t4 state {}", t4.getState());
        log.debug("t5 state {}", t5.getState());
        log.debug("t6 state {}", t6.getState());
    }
}

结果:
14:20:37.523 [t3] DEBUG com.bfbc.test2.Test6 - running...
14:20:38.025 [main] DEBUG com.bfbc.test2.Test6 - t1 state NEW
14:20:38.026 [main] DEBUG com.bfbc.test2.Test6 - t2 state RUNNABLE
14:20:38.026 [main] DEBUG com.bfbc.test2.Test6 - t3 state TERMINATED
14:20:38.026 [main] DEBUG com.bfbc.test2.Test6 - t4 state TIMED_WAITING
14:20:38.026 [main] DEBUG com.bfbc.test2.Test6 - t5 state WAITING
14:20:38.026 [main] DEBUG com.bfbc.test2.Test6 - t6 state BLOCKED

7、案例

7.1、案例1——防止CPU占用100%

7.1.1、sleep实现

在没有利用CPU来讲计算时,不要让while(true)空转浪费CPU,这时可以使用yield或sleep来让出CPU的使用权给其他程序。

    public static void main(String[] args) {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();
    }
  • 可以用wait或条件变量达到类似的效果;
  • 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景;
  • sleep适用于无需锁同步的场景。

7.1.2、wait实现

7.2、案例2——应用之同步

以调用方角度来讲,如果:

  • 需要等待结果返回,才能继续运行就是同步;
  • 不需要等待结果返回,就能继续运行就是异步。

你可能感兴趣的:(并发编程Java,java,学习,开发语言)