JAVA并发编程

本文详细探讨了Java中的多线程概念,包括进程与线程的定义、并发与并行的区别、线程的创建与运行方式、线程的状态转换以及同步锁、线程安全等问题。还介绍了Java提供的API如synchronized、wait/notify、ReentrantLock等在多线程中的应用,并分析了线程池的工作原理和拒绝策略。此外,文章提到了volatile关键字、CAS无锁编程以及死锁的避免。通过对这些概念的解析,读者可以深入理解Java并发编程的核心知识。
摘要由CSDN通过智能技术生成

什么是java多线程?

进程与线程

进程

当一个程序被运行,就开启了一个进程, 比如启动了qq,word
程序由指令和数据组成,指令要运行,数据要加载,指令被cpu加载运行,数据被加载到内存,指令运行时可由cpu调度硬盘、网络等设备
线程

一个进程内可分为多个线程
一个线程就是一个指令流,cpu调度的最小单位,由cpu一条一条执行指令

并行与并发

并发:单核cpu运行多线程时,时间片进行很快的切换。线程轮流执行cpu

并行:多核cpu运行 多线程时,真正的在同一时刻运行
在这里插入图片描述
java提供了丰富的api来支持多线程。

为什么用多线程?
多线程能实现的都可以用单线程来完成,那单线程运行的好好的,为什么java要引入多线程的概念呢?

多线程的好处:

程序运行的更快!快!快!

充分利用cpu资源,目前几乎没有线上的cpu是单核的,发挥多核cpu强大的能力
在这里插入图片描述

多线程难在哪里?

单线程只有一条执行线,过程容易理解,可以在大脑中清晰的勾勒出代码的执行流程

多线程却是多条线,而且一般多条线之间有交互,多条线之间需要通信,一般难点有以下几点

多线程的执行结果不确定,受到cpu调度的影响
多线程的安全问题
线程资源宝贵,依赖线程池操作线程,线程池的参数设置问题
多线程执行是动态的,同时的,难以追踪过程
多线程的底层是操作系统层面的,源码难度大
ava多线程的基本使用
定义任务、创建和运行线程
任务: 线程的执行体。也就是我们的核心代码逻辑

定义任务

继承Thread类 (可以说是 将任务和线程合并在一起)
实现Runnable接口 (可以说是 将任务和线程分开了)
实现Callable接口 (利用FutureTask执行任务)
Thread实现任务的局限性

任务逻辑写在Thread类的run方法中,有单继承的局限性
创建多线程时,每个任务有成员变量时不共享,必须加static才能做到共享
Runnable和Callable解决了Thread的局限性

但是Runbale相比Callable有以下的局限性

任务没有返回值
任务无法抛异常给调用方
如下代码 几种定义线程的方式

在这里插入代码片@Slf4j
class T extends Thread {
   
    @Override
    public void run() {
   
        log.info("我是继承Thread的任务");
    }
}
@Slf4j
class R implements Runnable {
   

    @Override
    public void run() {
   
        log.info("我是实现Runnable的任务");
    }
}
@Slf4j
class C implements Callable<String> {
   

    @Override
    public String call() throws Exception {
   
        log.info("我是实现Callable的任务");
        return "success";
    }
}

创建线程的方式

通过Thread类直接创建线程
利用线程池内部创建线程
启动线程的方式

调用线程的start()方法

在这里插入代码片// 启动继承Thread类的任务
new T().start();

// 启动继承Thread匿名内部类的任务 可用lambda优化
Thread t = new Thread(){
   
  @Override
  public void run() {
   
    log.info("我是Thread匿名内部类的任务");
  }
};

//  启动实现Runnable接口的任务
new Thread(new R()).start();

//  启动实现Runnable匿名实现类的任务
new Thread(new Runnable() {
   
    @Override
    public void run() {
   
        log.info("我是Runnable匿名内部类的任务");
    }
}).start();

//  启动实现Runnable的lambda简化后的任务
new Thread(() -> log.info("我是Runnable的lambda简化后的任务")).start();

// 启动实现了Callable接口的任务 结合FutureTask 可以获取线程执行的结果
FutureTask<String> target = new FutureTask<>(new C());
new Thread(target).start();
log.info(target.get());


以上各个线程相关的类的类图如下

在这里插入图片描述
上下文切换
多核cpu下,多线程是并行工作的,如果线程数多,单个核又会并发的调度线程,运行时会有上下文切换的概念

cpu执行线程的任务时,会为线程分配时间片,以下几种情况会发生上下文切换。

线程的cpu时间片用完
垃圾回收
线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
当发生上下文切换时,操作系统会保存当前线程的状态,并恢复另一个线程的状态,jvm中有块内存地址叫程序计数器,用于记录线程执行到哪一行代码,是线程私有的。

idea打断点的时候可以设置为Thread模式,idea的debug模式可以看出栈帧的变化
在这里插入图片描述

线程的礼让-yield()&线程的优先级

yield()方法会让运行中的线程切换到就绪状态,重新争抢cpu的时间片,争抢时是否获取到时间片看cpu的分配。

代码如下

在这里插入代码片// 方法的定义
public static native void yield();

Runnable r1 = () -> {
   
    int count = 0;
    for (;;){
   
       log.info("---- 1>" + count++);
    }
};
Runnable r2 = () -> {
   
    int count = 0;
    for (;;){
   
        Thread.yield();
        log.info("            ---- 2>" + count++);
    }
};
Thread t1 = new Thread(r1,"t1");
Thread t2 = new Thread(r2,"t2");
t1.start();
t2.start();

// 运行结果
11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129504
11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129505
11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129506
11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129507
11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129508
11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129509
11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129510
11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129511
11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129512
11:49:15.798 [t2] INFO thread.TestYield -             ---- 2>293
11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129513
11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129514
11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129515
11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129516
11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129517
11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129518

如上述结果所示,t2线程每次执行时进行了yield(),线程1执行的机会明显比线程2要多。

线程的优先级

​ 线程内部用1~10的数来调整线程的优先级,默认的线程优先级为NORM_PRIORITY:5

​ cpu比较忙时,优先级高的线程获取更多的时间片

​ cpu比较闲时,优先级设置基本没用

 public final static int MIN_PRIORITY = 1;

 public final static int NORM_PRIORITY = 5;

 public final static int MAX_PRIORITY = 10;
 
 // 方法的定义
 public final void setPriority(int newPriority) {
   
 }

cpu比较忙时

在这里插入代码片Runnable r1 = () -> {
   
    int count = 0;
    for (;;){
   
       log.info("---- 1>" + count++);
    }
};
Runnable r2 = () -> {
   
    int count = 0;
    for (;;){
   
        log.info("            ---- 2>" + count++);
    }
};
Thread t1 = new Thread(r1,"t1");
Thread t2 = new Thread(r2,"t2");
t1.setPriority(Thread.NORM_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();

// 可能的运行结果
11:59:00.696 [t1] INFO thread.TestYieldPriority - ---- 1>44102
11:59:00.696 [t2] INFO thread.TestYieldPriority -             ---- 2>135903
11:59:00.696 [t2] INFO thread.TestYieldPriority -             ---- 2>135904
11:59:00.696 [t2] INFO thread.TestYieldPriority -             ---- 2>135905
11:59:00.696 [t2] INFO thread.TestYieldPriority -             ---- 2>135906

cpu比较闲时

在这里插入代码片
```Runnable r1 = () -> {
   
    int count = 0;
    for (int i = 0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值