线程基本概念方法

进程与线程

进程

  • 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的。
  • 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
  • 进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)

线程

  • 一个进程之内可以分为一到多个线程。
  • 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行。
  • Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作为线程的容器。

二者对比

  • 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集

  • 进程拥有共享的资源,如内存空间等,供其内部的线程共享

  • 进程间通信较为复杂

    • 同一台计算机的进程通信称为 IPC(Inter-process communication)
    • 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
  • 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量

  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低

并行与并发

单核 cpu 下,线程实际还是 串行执行 的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感觉是 同时运行的 。总结为一句话就是: 微观串行,宏观并行 ,一般会将这种 线程轮流使用 CPU 的做法称为并发

image-20210126155115307

image-20210126155143294

多核 cpu下,每个 核(core) 都可以调度运行线程,这时候线程可以是并行的。

image-20210126155253326

image-20210126155341033

引用 Rob Pike 的一段描述:

  • 并发(concurrent)是同一时间应对(dealing with)多件事情的能力
  • 并行(parallel)是同一时间动手做(doing)多件事情的能力

创建线程

创建和运行线程方法一,直接使用 Thread

/**
 * @author WGR
 * @create 2021/1/27 -- 13:24
 */
@Slf4j(topic = "c.Test1")
public class Test1 {

    public static void main(String[] args) {
        test1();
        log.info("main running");
    }

    public static void test1(){
        Thread thread = new Thread("t1") {
            @Override
            public void run() {
                log.info("test1 running");
            }
        };
        thread.start();
    }
}

方法二,使用 Runnable 配合 Thread
把【线程】和【任务】(要执行的代码)分开Thread 代表线程,Runnable 可运行的任务(线程要执行的代码)

    public static void test2(){
        new Thread(() ->{
            log.info("runnable running");
        }).start();
    }

方法三,FutureTask 配合 Thread
FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况

 @SneakyThrows
    public static void test3(){
        FutureTask<Integer> futureTask = new FutureTask<>(() -> {
            log.debug("hello");
            return 100;
        });
        new Thread(futureTask,"t3").start();
        System.out.println(futureTask.get());
    }

常见方法

方法名 static 功能说明 注意
start() 启动一个新线 程,在新的线程 运行 run 方法 中的代码 start 方法只是让线程进入就绪,里面代码不一定立刻 运行(CPU 的时间片还没分给它)。每个线程对象的 start方法只能调用一次,如果调用了多次会出现 IllegalThreadStateException
run() 新线程启动后会 调用的方法 如果在构造 Thread 对象时传递了 Runnable 参数,则 线程启动后会调用 Runnable 中的 run 方法,否则默 认不执行任何操作。但可以创建 Thread 的子类对象, 来覆盖默认行为
join() 等待线程运行结 束
join(long n) 等待线程运行结 束,最多等待 n 毫秒
getId() 获取线程长整型 的 id id唯一
getName() 获取线程名
setName(String) 修改线程名
getPriority() 获取线程优先级
setPriority(int) 修改线程优先级 java中规定线程优先级是1~10 的整数,较大的优先级 能提高该线程被 CPU 调度的机率
getState() 获取线程状态 Java 中线程状态是用 6 个 enum 表示,分别为: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
isInterrupted() 判断是否被打断 不会清除 打断标记
isAlive() 线程是否存活(还没有运行完毕)
interrupt() 打断线程 如果被打断线程正在 sleep,wait,join 会导致被打断 的线程抛出 InterruptedException,并清除 ;如果打断的正在运行的线程,则会设置 ;park 的线程被打断,也会设置 打断标记
interrupted() static 判断当前线程是 否被打断 会清除 打断标记,即返回true后,改成false(Thread.interrupted())
currentThread() static 获取当前正在执 行的线程
sleep(long n) static 让当前执行的线 程休眠n毫秒, 休眠时让出 cpu 的时间片给其它 线程
yield() static 提示线程调度器 让出当前线程对 CPU的使用 主要是为了测试和调试

start和run

public static void main(String[] args) {
        new Thread(() ->{
            log.info("start test....");
        },"t1").start();
        new Thread(() ->{
            log.info("run test....");
        },"t2").run();
    }
15:53:09.915 c.Test2 [main] - run test....
15:53:09.915 c.Test2 [t1] - start test....
  • 直接调用 run 是在主线程中执行了 run,没有启动新的线程
  • 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码

sleep 与 yield

状态的演示:

    @SneakyThrows
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            log.info("test...");
        });
        log.info(""+thread.getState());
        thread.start();
        log.info(""+thread.getState());
    }

image-20210127160415839

直接运行,状态是RUNNABLE

    @SneakyThrows
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            log.info("test...");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        log.info(""+thread.getState());
        thread.start();
        Thread.sleep(1000);
        log.info(""+thread.getState());
    }

image-20210127160515483

当有睡眠的时候,状态变成TIME_WAITING。

@Slf4j(topic = "c.Test3")
public class Test3 {

    @SneakyThrows
    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @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);
        log.debug("interrupt...");
        t1.interrupt();
    }
}

image-20210127171207487

打断正在睡眠的线程会有异常。

sleep

  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  3. 睡眠结束后的线程未必会立刻得到执行
  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
@Slf4j(topic = "c.Test4")
public class Test4 {
    public static void main(String[] args) {
        Runnable task1 = () -> {
            int count = 0;
            for (int x =0;x<10;x++) {
                System.out.println("---->1 " + count++);
            }
        };
        Runnable task2 = () -> {
            int count = 0;
            for (int x =0;x<10;x++) {
            //    Thread.yield();
                System.out.println("              ---->2 " + count++);
            }
        };
        Thread t1 = new Thread(task1, "t1");
        Thread t2 = new Thread(task2, "t2");

        t1.start();
        t2.start();
    }
}

image-20210127172908972image-20210127172932065

假如yield方法后的对比。

join

 @SneakyThrows
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("running");
        });
        log.info("start....");
        thread.start();
        thread.join();
        log.info("end....");

    }

image-20210127211739331

可以看出会等t1线程运行结束,主线程才会运行。

@Slf4j(topic = "c.Test6")
public class Test6 {

    static int r1 = 0;
    static int r2 = 0;

    @SneakyThrows
    public static void main(String[] args) {

        Thread thread = new Thread((() -> {

          sleep(1);
            r1 = 10;
        }));

        Thread thread2 = new Thread((() -> {
            sleep(2);
            r2 = 20;

        }));
        long start = System.currentTimeMillis();
        thread.start();
        thread2.start();
        thread.join();
        thread2.join();
        long end = System.currentTimeMillis();
        log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
    }
}
21:31:45.722 c.Test6 [main] - r1: 10 r2: 20 cost: 2005

等多个线程进行等待的话,会以时间长的位置,和异步编排一样。

interrupt

打断 sleep 的线程, 会清空打断状态,以 sleep 为例

public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            sleep(1);
        });
        thread.start();
        sleep(0.5);
        thread.interrupt();
        System.out.println(thread.isInterrupted());
    }

image-20210127215905676

public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for(;;){
                Thread currentThread = Thread.currentThread();
                if(currentThread.isInterrupted()){
                    break;
                }
            }
        });
        thread.start();
        thread.interrupt();
        System.out.println(thread.isInterrupted());
    }

打断正常线程会为true。

private static void test3() {
        Thread t1 = new Thread(() -> {
            log.debug("park...");
            LockSupport.park();
            log.debug("unpark...");
            log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();

        sleep(0.5);
        t1.interrupt();
    }

image-20210127220643163

打断 park 线程, 不会清空打断状态

    private static void test4() {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                log.debug("park...");
                LockSupport.park();
                log.debug("打断状态:{}", Thread.interrupted());
            }
        });
        t1.start();


        sleep(1);
        t1.interrupt();
    }

image-20210128100409534

    private static void test4() {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                log.debug("park...");
                LockSupport.park();
                log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
            }
        });
        t1.start();


        sleep(1);
        t1.interrupt();
    }

image-20210128100533205

二阶段提交

/**
 * @author WGR
 * @create 2021/1/27 -- 22:26
 */
@Slf4j(topic = "c.Test9")
public class Test9 {

    private Thread thread;
    private volatile boolean stop = false;

    public  void start(){
        thread = new Thread(() ->{
            for(;;){
                if(stop){
                    log.debug("料理后事。。。");
                    break;
                }
                try {
                    Thread.sleep(1000);
                    log.debug("将结果保存");
                } catch (InterruptedException e) {
                }
            }
        }, "t1");
        thread.start();

    }


    public void stop() {
        stop = true;
        thread.interrupt();
    }

    public static void main(String[] args) {
        Test8 t = new Test8();
        t.start();
        sleep(2.5);
        log.debug("stop");
        t.stop();
    }
}


/**
 * @author WGR
 * @create 2021/1/27 -- 21:47
 */
@Slf4j(topic = "c.Test8")
public class Test8 {

    private Thread thread;

    public  void start(){
        thread = new Thread(() ->{
            for(;;){
                Thread t1 = Thread.currentThread();
                if(t1.isInterrupted()){
                    log.debug("料理后事。。。");
                    break;
                }
                try {
                    Thread.sleep(1000);
                    log.debug("将结果保存");
                } catch (InterruptedException e) {
                   // e.printStackTrace();
                    t1.interrupt();
                }
            }
        }, "t1");
        thread.start();

    }


    public void stop() {
        thread.interrupt();
    }

    public static void main(String[] args) {
        Test8 t = new Test8();
        t.start();
        sleep(2.5);
        log.debug("stop");
        t.stop();
    }


}

image-20210127222955840

守护线程

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

/**
 * @author WGR
 * @create 2021/1/27 -- 22:40
 */
public class Test10 {

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("开始。。。。");
            sleep(2);
            System.out.println("结束。。。。");
        });
        thread.setDaemon(true);
        thread.start();
        sleep(1);
    }
}

image-20210127224429682

注意

  • 垃圾回收器线程就是一种守护线程
  • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求
posted @ 2021-01-28 10:08  天宇轩-王  阅读(124)  评论(0编辑  收藏  举报