1.java多线程——java线程

线程概述

进程是系统分配资源的基本单位,线程是调度 CPU 的基本单位,一个进程至少包含一个执行线程,线程寄生在进程当中。每个线程都要一个程序计数器(记录要执行的下一条指令),一组寄存器(保存当前线程的工作变量),堆栈(记录执行历史,其中每一帧保存了一个已经调用但未返回的过程)。

线程是调度 cpu 的最小单元,也叫轻量级进程 LWP(Light Weight Process)。

线程创建方式

继承 Thread 类
实现 Runnable 接口
实现 Callable 接口
线程池
其实这四种的底层都是一样的,都是用 Runnable 去创建的。

线程分类

线程的实现可以分为两类:
用户级线程(ULT,User-Level Thread)
内核级线程(KLT,Kernel-Level Thread)

在看线程分类之前先了解下我们的用户空间分类:用户空间,内核空间。如下图
在这里插入图片描述
ULT 级线程使用用户空间,KLT 级线程使用内核空间。
用户空间和内核空间之间是完全隔离的,想要交互可以通过交互接口。
ring0、ring3 是对 cpu 操作的特权级别,ring0 权限级别最高,ring3 最低。以前有 ring0、ring1、ring2、ring3 共 4 个级别,现在只有两个 ring0、ring3。
ULT 用有对 cpu 的 ring3 操作权限级别,KLT 拥有对 cpu 的 ring0 操作权限级别

一般情况下,线程执行的时候往往会在用户空间和内核空间进行切换,如执行下面代码时:

String str = "hello world";   // 在用户空间
file.write(str);              // 切换到内核空间
int i = 0;                    // 切换回用户空间

再来看下面这张图:
图片:
ULT 在内核空间没有线程的的概念,不能感知到线程,内核空间维护了一张进程表,而这些进程在用户空间,每个进程里面有一张线程表,同时也有很多线程。
KLT 在内核空间维护了一张进程表、一张线程表,可以感知到线程。进程,线程都在用户空间里面,当然线程被包含在进程里面。

java 线程与 KLT 的关系

java 1.2 及其之前 java 线程是 ULT,1.2 之后是 KLT。下面是 java 线程与内核线程的关系:
图片:

运行一个 java 程序时,是一个 jvm 进程,jvm 进程在用户空间创建多个线程(栈帧空间),每个线程再通过库调度器 在内核空间创建对应的线程,这些内核线程去调用 CPU 内核去处理。

java 线程生命周期

线程的状态有 5 个,新建、就绪、运行、终止、阻塞(先回到就绪状态继续往下进行)。java 的生命周期如下图:
图片:

为什么要用并发

原因
充分利用多核 CPU 的计算能力
方便进行业务拆分,提升应用性能

并发与并行
并行是并发,但是并发不一定是并行。
以前使用的电脑大多数是单核处理器,在这样的电脑上运行多线程代码时。宏观上来看是并行,可从微观上来看,其实是给每个线程分配了不同的时间片,每个时间片串行执行,某时刻实际上只有一个线程在执行。
硬件发展到今天,我们的电脑 cpu 一般是多核多处理器(线程数不大于 cpu 逻辑处理器数量),这样在执行并发代码的时候才是真正的并发(也是并行)。

问题
高并发场景下,导致频繁的上下文切换(用户空间和内核空间切换)。
临界区线程安全问题,容易出现死锁,产生死锁就会造成系统功能不可用。
线程安全,导致一些变量的值出现意外的修改。

案例:查看死锁

接下来我们模拟一个死锁,代码如下:

public class DeadLockDemo {
    public static final String resource_a = "aaaa";
    public static final String resource_b = "bbbb";
    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            synchronized (resource_a){
                System.out.println("get resource a");
                try{
                    Thread.sleep(2000);
                    synchronized (resource_b){
                        System.out.println("get resource b");
                    }
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        });

        Thread threadB = new Thread(() -> {
            synchronized (resource_b){
                System.out.println("get resource b");
                try{
                    Thread.sleep(2000);
                    synchronized (resource_a){
                        System.out.println("get resource a");
                    }
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        });
        threadA.start();
        threadB.start();
    }
}

启动该程序,控制台打印了输出内容,这时程序并没有结束,发生了死锁情况。如下图:
图片:

这时在dos窗口执行命令:jps,如下图:
图片:

我们刚才执行的程序进程号为3412(通过类名),这时执行命令:jstack 3412 (3412为进程号),定位死锁继而进行处理。如下图:
图片:
当然也可以在idea工具中直接查看堆栈信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值