JUC并发编程 - 进程与线程

1 概念

1.1 进程

(1)程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理IO的。
(2)进程可以看作是一个程序的实例。

1.2 线程

(1)线程是一个指令流,将指令流中的一条条指令以一定的顺序交给CPU执行。
(2)线程作为最小调度单位,进程作为资源分配的最小单位。

1.3 对比

  • 进程基本上是独立的,而线程存在于进程当中,是进程的一个子集。
  • 进程拥有共享的资源,如内存空间,供内部的线程共享。
  • 进程间通信分为两种
    • 同一台计算机的进程通信称为IPC(Inter-process communication)
    • 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议
  • 线程通信相对简单,因为他们共享进程内的内存
  • 线程更轻量,线程上下文切换的成本比进程上下文切换低

1.4 并发

单核
一个人 一个时间段 做几件事情

1.5 并行

多核
多个人 一个时间段 同时做几件事情

1.5 同步

一个人必须等待上一个人的结果才能继续做事

1.5 异步

彼此互不相干

2 线程的应用

2.1 创建线程

2.1.1 Thread

直接创建Thread对象,重写run方法

Thread t1 = new Thread(() -> {
            System.out.println("线程创建1");
        });

2.1.2 实现Runable

实现runable接口,并将实现类作为参数传入Thread构造方法

public static void main(String[] args) {
       ThreadDemo threadDemo = new ThreadDemo();
       Thread thread2 = new Thread(threadDemo);
   }
   
   
static class ThreadDemo implements Runnable{
     @Override
     public void run() {
         System.out.println("线程创建2");
     }
 }

2.1.3 Runable和Thread的关系

Thread实现了Runable

public class Thread implements Runnable {...}

2.1.4 实现Callable

实现Callable接口,作为参数构造FutureTask
FutureTask实现了Runable接口,作为参数构造Thread

FutureTask<Integer> futureTask = new FutureTask<Integer>(() -> {
            System.out.println("线程创建3");
            return 1;
        });
Thread thread = new Thread(futureTask);

2.2 运行线程

thread.start();

2.3 查看线程

jconsole图像化工具

2.3.1 栈和栈帧

JVM由堆、栈、方法区组成。线程使用栈内存

  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着赈灾执行的那个方法

2.3.1 上下文切换

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

当发生上下文切换时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java中对应的概念就是程序计数器,它的作用就是记住下一条jvm的执行地址,时线程私有的

  • 状态包括程序计数器,虚拟机栈中的每个栈帧的信息,如局部遍历,操作数栈、返回地址
  • 上下文频繁切换影响性能

2.4 线程API

2.4.1 start()和run()

直接调用
start() 新建线并进入就绪状态(RUNNABLE),等待cpu执行
run() 在当前线程执行run方法里的方法

2.4.2 sleep()

  • 由RUNNABLE 状态 转换成 TIMED_WAITING 状态
Thread thread = new Thread(() -> {
    System.out.println("start...");
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("end...");
});
thread.start();
System.out.println("status->" + thread.getState());
Thread.sleep(1000);
System.out.println("status->" + thread.getState());

在这里插入图片描述

  • 其它线程可以使用interrrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptException
Thread thread = new Thread(() -> {
        System.out.println("start...");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            System.out.println("interrupted..");
            e.printStackTrace();
        }
        System.out.println("end...");
    });
    thread.start();
    System.out.println("status->" + thread.getState());
    Thread.sleep(1000);
    // 打断线程
    thread.interrupt();
    System.out.println("status->" + thread.getState());
}

在这里插入图片描述

  • 睡眠结束后的线程未必立刻得到执行
  • 建议用TimeUnit的sleep代替Thread的sleep来获得更好的可读性
// 当前线程睡眠3秒
TimeUnit.SECONDS.sleep(3);

sleep应用

2.4.3 yield()

  • 调用yield会当当前线程从Running进入Runnable就绪状态,然后调度执行其他线程
  • 具体的实现依赖于操作系统的任务调度器

2.4.4 线程优先级

一切看大哥脸色 任务调度器

  • 线程优先级会提示(hint)调度器有限调度该线程,但仅仅是一个提示,调度器可以忽略它
  • 如果cpu比较忙,那么优先级高的线程会获得更多的时间片,cpu闲的时候,优先级没用

2.4.5 join()

等待线程运行结束

static int i = 0;

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
        System.out.println("start...");
        try {
            TimeUnit.SECONDS.sleep(3);
            i++;
        } catch (InterruptedException e) {
            System.out.println("interrupted..");
            e.printStackTrace();
        }
        System.out.println("end...");
    });
    thread.start();
    System.out.println(i);
    // 等待线程结束
    thread.join();
    System.out.println(i);
}

2.4.6 interrupt()

打断sleep、wait、join的线程

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
        while (true) {
            // 如果被打断,退出死循环
            if (Thread.interrupted()) {
                System.out.println("interrupt..");
                break;
            }
        }
    });
    thread.start();
    TimeUnit.SECONDS.sleep(2);
    // 打断线程
    System.out.println("begin interrupt..");
    thread.interrupt();
}

2.4.7 两阶段终止模式

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

在这里插入图片描述

3 示范

isInterrupted() 和 interrupted() 区别
isInterrupted()不会打断清除标记
interrupted()会清除打断标记

public static void main(String[] args) throws InterruptedException {
     TwoPhaseTermination termination = new TwoPhaseTermination();
     termination.start();
     TimeUnit.SECONDS.sleep(5);
     termination.stop();
 }

static class TwoPhaseTermination {
    private Thread monitor;

    public void start(){
        monitor = new Thread(()->{
            while (true) {
                Thread current = Thread.currentThread();
                // 如果被打断了,那就料理后事
                if (current.isInterrupted()) {
                    System.out.println("料理后事...");
                    break;
                }
                System.out.println("处理事情...");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    // 睡眠时被打断,标记不会改变,手动改变
                    current.interrupt();
                }
            }
        });
        monitor.start();
    }

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

2.4.8 过时方法

stop() 停止线程运行
suspend() 挂起(暂停)线程运行
resume() 恢复线程运行

2.5 守护线程

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

// 设置线程为守护线程
thread.setDaemon(true);
  • 垃圾回收器就是一种守护线程
  • Timcat中Acceptor和Poller线程都是守护线程,所以Tomcat接受到shutdown命令后,不会等待他们处理完当前请求。

2.6 线程状态

2.6.1 五种状态

在这里插入图片描述

  • 初始状态 创建了线程对象,但未与操作系统做关联
  • 可运行状态(就绪状态) 线程被创建,可以由CPU调度
  • 运行状态 获取到了CPU时间片
  • 阻塞状态
    • 调用了阻塞API,如BIO读写文件,这是线程不会用到CPU,会导致上下文切换,进入阻塞状态
    • 等BIO结束,会u由操作系统唤醒阻塞的线程,转换为可运行状态
    • 与可运行状态相比,堆阻塞状态的线程来说,只要他们一直不欢喜,调度器就一直不会考虑调度他们
  • 终止状态 线程执行完毕

2.6.1 六种状态

在这里插入图片描述

  • NEW 线程刚刚被创建,但是还没有调用start()方法
  • RUNNABLE 当调用了start()方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值