【记录】并发编程-学习日志(一)

1. 进程与线程

1.1 进程与线程

程序运行 ---> 开启进程

一个进程包含众多线程;一个线程包含众多指令

1.2 并行与并发

CPU、核(core)、线程

core ---> 单核/多核

多个线程在同一时间里需被 CPU 处理,CPU 为单核 CPU,CPU 需不停切换线程做处理 ---> 并发

多个线程在同一时间里需被 CPU 处理,CPU 为多核 CPU,当 CPU core数大于等于线程数,则每个线程都可分配一个 core 做处理 ---> 并行

并发 和 并行 可同时发生 ---> 1 < core 数 < 线程数


1.3(多线程、多核 CPU)应用

1.3.1(多线程、多核 CPU)应用之异步调用

异步、同步

需等待排在前面的指令运行结束才能运行 ---> 同步

无需等待排在前面的指令运行结束即可运行 ---> 异步

并发:多个任务在同一个时间段内同时执行,如果是单核心计算机,CPU 会不断地切换任务来完成并发操作。

并行:多任务在同一个时刻同时执行,计算机需要有多核心,每个核心独立执行一个任务,多个任务同时执行,不需要切换。

同步:多任务开始执行,任务 A、B、C 全部执行完成后才算是结束。

异步:多任务开始执行,只需要主任务 A 执行完成就算结束,主任务执行的时候,可以同时执行异步任务 B、C,主任务 A 可以不需要等待异步任务 B、C 的结果。

举一个例子

你的朋友在广州,但是有 2 辆小汽车在深圳,需要你帮忙把这 2 辆小汽车送到广州去。

同步的方式,你先开一辆小汽车到广州,然后再坐火车回深圳,再开另外一辆小汽车去广州。这是串行的方法,2 辆车需要的时间也就更长了。

异步的方式,你开一辆小汽车从深圳去广州,同时请一个代驾把另外一辆小汽车从深圳开去广州。这也就是并行方法,两个人两辆车,可以同时行驶,速度很快。

并发的方式,你一个人,先开一辆车走 500 米,停车跑回来,再开另外一辆车前行 1000 米,停车再跑回来,循环从深圳往广州开。并发的方式,你可以把 2 辆车一块送到朋友手里,但是过程还是很辛苦的。

1.3.2(多线程、多核 CPU)应用之提高效率

2. Java 线程

2.1 创建和运行线程

2.1.1 方法一 :直接使用 Thread

package com.rui.test;

import lombok.extern.slf4j.Slf4j;

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

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

        t1.setName("t1");
        t1.start();
    }
}

2.1.2 :方法二 使用 Runnable 配合 Thread(推荐)

package com.rui.test;

import lombok.extern.slf4j.Slf4j;

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

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

        Thread t2 = new Thread(runnable, "t2");

        t2.start();
    }
}

package com.rui.test;

import lombok.extern.slf4j.Slf4j;

/**
 * lambda 简化
 */
@Slf4j(topic = "c.Test3")
public class Test3 {
    public static void main(String[] args) {
        Runnable runnable = () -> {
            log.debug("running");
        };

        Thread t2 = new Thread(runnable, "t2");

        t2.start();
    }
}

方法一将线程(Thread)和任务(Runnable)合并,方法二将线程(Thread)和任务(Runnable)分开

2.1.3 方法三 :FutureTask 配合 Thread

package com.rui.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

@Slf4j(topic = "c.Test4")
public class Test4 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> ft = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                log.debug("running");
                Thread.sleep(1000);
                return 100;
            }
        });

        Thread t4 = new Thread(ft, "t4");

        t4.start();

        log.debug("{}", ft.get());


    }
}

package com.rui.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 练练手
 */
@Slf4j(topic = "c.Test5")
public class Test5 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> ft = new FutureTask<>(() -> {
            log.debug("running");
            Thread.sleep(1000);
            return 100;
        });

        Thread t4 = new Thread(ft, "t4");

        t4.start();

        log.debug("{}", ft.get());
    }
}

2.2 观察多个线程同时运行

package com.rui.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test6")
public class Test6 {
    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                log.debug("running");
            }
        }, "t1").start();

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

两个线程交替执行,执行先后由 CPU 决定

2.3 查看|杀死进程的方法 - Windows

2.3.1 任务管理器

2.3.2 CMD

PID - 进程 ID

tasklist  - 查看进程

jps - 查看 Java 进程

taskkill - 杀死进程

格式 :taskkill /F /PID 进程 ID

2.4 原理之线程运行

2.4.1 栈与栈帧 (Java 虚拟机栈)

线程 对应 栈

方法 对应 栈帧

2.4.2 线程上下文切换

线程交还 CPU 的使用权即发生一次线程上下文切换

线程上下文切换会影响程序运行性能

导致线程上下文切换的情况:

        1.线程的 CPU 时间片用完

        2.垃圾回收

               垃圾回收触发时, CPU 的使用权会交给垃圾回收线程,工作线程处于阻塞状态

        3.有更高优先级的线程需要运行

        4.线程主动调用了 sleep、yield、join等方法

2.5 常见方法

2.5.1 start 与 run

run
package com.rui.test;

import lombok.extern.slf4j.Slf4j;

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

        t1.run();
        log.debug("do other things...");
    }
}

start
package com.rui.test;

import lombok.extern.slf4j.Slf4j;

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

        t1.start();
        log.debug("do other things...");
    }
}

通过上述两条代码可以看出,执行 run 方法,由主线程调用,此时代码为同步调用;执行 start 方法,由 t1 线程调用,此时代码为异步调用。

2.5.2 sleep 与 yield

sleep
package com.rui.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test9")
public class Test9 {
    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) {
                    e.printStackTrace();
                }
                log.debug("wake up...");
            }
        };

        t1.start();

    }
}

未调用 yield
package com.rui.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test11")
public class Test11 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                int count = 0;
                for (; ; ) {
                    System.out.println("--->" + count++);
                }
            }
        };
        t1.start();

        Thread t2 = new Thread("t2") {
            @Override
            public void run() {
                int count = 0;
                for (; ; ) {
                    System.out.println("         --->" + count++);
                }
            }
        };
        t2.start();
    }
}

yield
package com.rui.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test11")
public class Test11 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                int count = 0;
                for (; ; ) {
                    System.out.println("--->" + count++);
                }
            }
        };
        t1.start();

        Thread t2 = new Thread("t2") {
            @Override
            public void run() {
                int count = 0;
                for (; ; ) {
                    Thread.yield();
                    System.out.println("         --->" + count++);
                }
            }
        };
        t2.start();
    }
}

【2.6 知识提前了解】

调用 sleep 方法会使线程交出对 CPU 的使用权从运行状态进入阻塞状态,在该线程处于阻塞状态的时间里任务调度器不会为其分配时间片,待该线程阻塞状态结束会进入就绪状态,此时任务调度器可能会为其分配时间片

从 Java API 的角度分析,调用 sleep 方法会使线程由 RUNNABLE 转换为 TIMED_WAITING,线程进入睡眠

调用 yield 方法会使线程交出对 CPU 的使用权从运行状态进入就绪状态,此时任务调度器可能会为其分配时间片

【你可能会有疑问】为什么是可能会为其分配时间片呢?

当处于就绪状态的线程多于 CPU core 时,供不应求就会导致某些线程未分配到时间片

【让了个寂寞】若 CPU 空闲,例如 CPU core 数大于线程数时, 线程之间完全不需要谦让,此时某一线程调用 yield 方法是完全没有必要的。

【说句废话】若 CPU 繁忙时,yield 能很好地发挥作用。

【补充】线程优先级

类似于 yield 方法

线程优先级划分为 1 ~ 10  10 个等级

setPriority

public static final int MIN_PRIORITY = 1; //最小优先级
public static final int NORM_PRIORITY = 5; //默认优先级 
public static final int MAX_PRIORITY = 10; //最大优先级
package com.rui.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test11")
public class Test12 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                int count = 0;
                for (; ; ) {
                    System.out.println("--->" + count++);
                }
            }
        };
        t1.start();

        Thread t2 = new Thread("t2") {
            @Override
            public void run() {
                int count = 0;
                for (; ; ) {
                    System.out.println("         --->" + count++);
                }
            }
        };
        t2.start();

        t1.setPriority(Thread.MIN_PRIORITY);
        t2.setPriority(Thread.MAX_PRIORITY);
    }
}

【说声抱歉】线程优先级同 yield 类似,唯有 CPU 繁忙时才可充分发挥其性能,此处由于博主的运行环境为多核 CPU,因此无法展示 CPU 繁忙时(例如单核 CPU )调用线程优先级方法后的执行效果。

2.5.3 join

未调用 join 方法
package com.rui.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test13")
public class Test13 {
    static int r = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                try {
                    log.debug("t1 start...");
                    Thread.sleep(2000);
                    r = 10;
                    log.debug("t1 end...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        t1.start();
        log.debug("main start...");
        log.debug("r = {}", r);
    }
}

join - 同步等待
package com.rui.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test13")
public class Test13 {
    static int r = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                try {
                    log.debug("t1 start...");
                    Thread.sleep(2000);
                    r = 10;
                    log.debug("t1 end...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        t1.start();
        log.debug("main start...");
        t1.join();
        log.debug("r = {}", r);
    }
}

通过上述两条代码可以看出,调用 join 方法是一种同步调用思想的体现,在主线程中调用 t1 线程的 join 方法,使得主线程在运行到该代码时需等待 t1 线程运行结束才可继续执行

join(long millis)
package com.rui.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test13")
public class Test13 {
    static int r = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                try {
                    log.debug("t1 start...");
                    Thread.sleep(2000);
                    r = 10;
                    log.debug("t1 end...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        t1.start();
        log.debug("main start...");
        t1.join(1000);
        log.debug("r = {}", r);
    }
}

通过上述代码可以看出,在主线程中调用 t1 线程的 join(long millis) 方法,表示主线程在运行到该条代码时会等待 t1 线程运行 millis 时间或运行结束才继续执行。

2.5.4 interrupt

interrupt
package com.rui.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test14")
public class Test14 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                try {
                    log.debug("t1 start...");
                    Thread.sleep(2000);
                    log.debug("t1 end...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t1.start();

        Thread.sleep(1000);
        log.debug("interrupt...");
        t1.interrupt();
    }
}

isInterrupt

默认的 isInterrupt(打断标记)= false

调用 sleep、wait、join 等方法的阻塞线程的 isInterrupt = false

运行线程的 isInterrupt = true

package com.rui.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test15")
public class Test15 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("t1 start...");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t1.start();

        Thread t2 = new Thread("t2") {
            @Override
            public void run() {
                log.debug("t2 start...");
                while (true) {

                }
            }
        };
        t2.start();

        Thread.sleep(1000);
        t1.interrupt();
        t2.interrupt();
        log.debug("t1 线程的打断标记 : {}", t1.isInterrupted());
        log.debug("t2 线程的打断标记 : {}", t2.isInterrupted());
    }

}

2.5.5 【补充】模式之两阶段终止

两阶段终止模式在本阶段不算特别重要的设计模式,但博主觉得很不错呦~

package com.rui.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test16")
public class Test16 {
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination tpt = new TwoPhaseTermination();
        tpt.start();
        Thread.sleep(5000);
        tpt.end();
    }
}

@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination {
    private Thread t1;

    public void start() {
        t1 = new Thread("t1") {
            @Override
            public void run() {
                boolean flag = Thread.currentThread().isInterrupted();
                while (true) {
                    if (flag) {
                        log.debug("料理后事");
                        break;
                    }
                    try {
                        Thread.sleep(1000);
                        log.debug("无异常,执行监控记录");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        flag = true;
                    }
                }
            }
        };
        t1.start();
    }

    public void end() {
        t1.interrupt();
    }
}

2.6 状态

2.6.1 五种状态 - 操作系统

初始状态、就绪状态(可运行状态)、运行状态、阻塞状态、终止状态

初始状态 : new Thread 之后,start 之前

就绪状态 : start 之后,线程还未获得 CPU 的使用权(任务调度器未给该线程分配时间片)

运行状态 : 线程获得了 CPU 的使用权

阻塞状态

终止状态 : 线程运行结束

运行状态转变为阻塞状态|就绪状态时会触发线程上下文切换

阻塞状态解除后会转换为就绪状态,而非运行状态

2.6.2 六种状态 - Java API

NEW : 对应操作系统中的初始状态

RUNNABLE : 对应操作系统中的就绪状态、运行状态、阻塞状态

TERMINATED : 对应操作系统中的结束状态

此外,Java API 中也有其自己的三种阻塞状态,需与操作系统中的阻塞状态区分开:

WAITING : join 触发

TIMED_WAITING : sleep 触发

BLOCKED : 锁触发

package com.rui.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test10")
public class Test10 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {

            }
        };

        Thread t2 = new Thread("t2") {
            @Override
            public void run() {
                while (true) {

                }
            }
        };
        t2.start();

        Thread t3 = new Thread("t3") {
            @Override
            public void run() {

            }
        };
        t3.start();

        Thread t4 = new Thread("t4") {
            @Override
            public void run() {
                try {
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t4.start();

        Thread t5 = new Thread("t5") {
            @Override
            public void run() {
                synchronized (Test10.class) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t5.start();

        Thread t6 = new Thread("t6") {
            @Override
            public void run() {
                synchronized (Test10.class) {

                }
            }
        };
        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());

    }
}

2.7 守护线程

setDaemon(true)

线程均默认为非守护线程【setDaemon(false)】

在线程处于初始状态 | NEW 时将其设置为守护线程

进程中所有非守护线程运行结束后进程结束

若进程中所有非守护线程均已运行结束,但守护线程仍在运行,守护线程会被强制结束

package com.rui.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test17")
public class Test17 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                while (true) {
                    if (Thread.currentThread().isInterrupted()) {
                        break;
                    }
                }
            }
        };
        t1.setDaemon(true);
        t1.start();

        Thread.sleep(1000);
        log.debug("end...");
    }
}

2.8 习题

package com.rui.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test18")
public class Test18 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                try {
                    log.debug("洗水壶");
                    Thread.sleep(1000);
                    log.debug("烧开水");
                    Thread.sleep(15000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t1.start();

        log.debug("洗茶壶,洗茶杯,拿茶叶");
        Thread.sleep(4000);

        t1.join();

        log.debug("泡茶");
    }
}

说些废话

本篇文章为博主日常学习记录,故而会小概率存在各种错误,若您在浏览过程中发现一些,请在评论区指正,望我们共同进步,谢谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值