Java并发编程基础(一篇入门)

本文介绍了Java并发编程的基础知识,包括并发编程的定义、重要性及其特性。详细讲解了操作系统的并发、线程内存模型、多线程的创建、join方法的使用以及yield方法的区别。此外,还探讨了线程上下文切换、死锁的产生条件和避免方法,以及守护线程与用户线程的定义和使用场景。最后,详细阐述了ThreadLocal的用途、操作方法及其在多线程环境下的应用。
摘要由CSDN通过智能技术生成

1 并发编程简介

1.1 什么是并发编程

所谓并发编程是指在一台处理器上 “同时” 处理多个任务。并发是在同一实体上的多个事件。多个事件在同一时间间隔发生。

并发编程,从程序设计的角度来说,是希望通过某些机制让计算机可以在一个时间段内,执行多个任务。从计算机 CPU 硬件层面来说,是一个或多个物理 CPU 在多个程序之间多路复用,提高对计算机资源的利用率。从调度算法角度来说,当任务数量多于 CPU 的核数时,并发编程能够通过操作系统的任务调度算法,实现多个任务一起执行。

1.2 并发编程的重要性

对于一个 Java 程序员而言,能否熟练掌握并发编程是判断他优秀与否的重要标准之一。因为并发编程是 Java 语言中最为晦涩的知识点,它涉及操作系统、内存、CPU、编程语言等多方面的基础能力,更为考验一个程序员的内功。

1.3 并发编程的特性

并发编程有三大特性:

  • 原子性;
  • 可见性;
  • 有序性。

2 操作系统的并发

2.1 并发编程的定义

定义: 并发编程是指在一台处理器上 “同时” 处理多个任务。并发是在同一实体上的多个事件,多个事件在同一时间间隔发生

意义:开发者通过使用不同的语言,实现并发编程,充分的利用处理器(CPU)的每一个核,以达到最高的处理性能,提升服务器的资源利用率,提升数据的处理速度。

2.2 从 CPU 谈并发编程

下图展示了最简单的 CPU 核心通过缓存与主存进行通讯的模型。
在这里插入图片描述
在缓存出现后不久,系统变得越来越复杂,缓存与主存之间的速度差异被拉大,由于 CPU 的频率太快了,快到主存跟不上,这样在线程处理器时钟周期内,CPU 常常需要等待主存,这样就会浪费资源。从我们的感官上,计算机可以同时运行多个任务,但是从 CPU 硬件层面上来说,其实是 CPU 执行线程的切换,由于切换频率非常快,致使我们从感官上感觉计算机可以同时运行多个程序。

为了避免长时间的线程等待,我们一方面提升硬件指标(如多级高速缓存的诞生,这里不做讨论),另一方面引入了并发概念,充分的利用处理器(CPU)的每一个核,减少 CPU 资源等待的时间,以达到最高的处理性能。

2.3 操作系统,进程,线程之间的联系与区别

操作系统是包含多个进程的容器,而每个进程又是容纳多个线程的容器。
在这里插入图片描述
a. 什么是进程?
官方定义: 进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。

Tips:操作系统分配的资源和调度对象其实就是 CPU 时间片。

b. 什么是线程?
官方定义: 线程是操作系统能够进行资源调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,每个线程执行的都是进程代码的某个片段,特定的线程总是在执行特定的任务。

c. 线程与进程的区别?
诞生起源:先有进程,后有线程。进程由于资源利用率、公平性和便利性诞生,线程则是为了提高 CPU 的利用率、提高程序的执行效率而诞生。

概念:进程是资源分配的最小单位。 线程是程序执行的最小单位(线程是操作系统能够进行资源调度的最小单位,同个进程中的线程也可以被同时调度到多个 CPU 上运行),线程也被称为轻量级进程;

内存共享:默认情况下,进程的内存无法与其他进程共享(进程间通信通过 IPC 进行)。 线程共享由操作系统分配给其父进程的内存块。

2.4 串行,并行与并发

串行:顺序执行,按步就搬。在 A 任务执行完之前不可以执行 B。

并行:同时执行,多管齐下。指两个或两个以上事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同 CPU 核心上同时执行。

并发:穿插执行,减少等待。指多个线程轮流穿插着执行,并发的实质是一个物理 CPU 在若干道程序之间多路复用,其目的是提高有限物理资源的运行效率。
在这里插入图片描述

3 Java 线程内存模型

3.1 什么是 Java 的内存模型

Java 内存模型(即 Java Memory Model,简称 JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。

3.2 Java 线程的私有内存和主内存

下图展示了Java 的内存模型。
在这里插入图片描述
工作内存(私有):由于JVM 运行程序的实体是线程,而每个线程创建时 JVM 都会为其创建一个工作内存(栈区和PC寄存器),用于存储线程私有的数据。线程私有的数据只能供自己使用,其他线程不能够访问到当前线程私有的内存空间,保证了不同的线程在处理自己的数据时,不受其他线程的影响。

主内存(共享):Java 内存模型中规定所有变量都存储在主内存(堆区和方法区),主内存是共享内存区域,所有线程都可以访问。从上图中可以看到,Java 的并发内存模型与操作系统的 CPU 运行方式极其相似,这就是 Java 的并发编程模型。通过创建多条线程,并发的进行操作,充分利用系统资源,达到高效的并发运算。

关于工作内存和主内存,详见JVM运行时数据区

3.3 主内存操作共享变量需要注意的事项

  • 确定是否是多线程环境:多线程环境下操作共享变量需要考虑线程的安全性;
  • 确定是否有增删改操作:多线程环境下,如果对共享数据有增加,删除或者修改的操作,需要谨慎。为了保证线程的同步性,必须对该共享数据进行加锁操作,保证多线程环境下,所有的线程能够获取到正确的数据;
  • 多线程下的读操作:如果是只读操作,对共享数据不需要进行锁操作,因为数据本身未发生增删改操作,不会影响获取数据的准确性。

4 Java 多线程的创建

4.1 Thread 类介绍

位于 java.lang 包下的 Thread 类是非常重要的线程类。学习 Thread 类的使用是学习多线程并发编程的基础。它实现了 Runnable 接口,其包集成结构如下图所示。
在这里插入图片描述

Thread 类的常用方法介绍:

方法 作用
start() 启动当前的线程,调用当前线程的 run ()
run() 通常需要重写 Thread 类中的此方法,将创建要执行的操作声明在此方法中。
currentThread() 静态方法,返回代码执行的线程。
getName() 获取当前线程的名字。
setName() 设置当前线程的名字。
sleep(long millitime) 让当前进程睡眠指定的毫秒数,在指定时间内,线程是阻塞状态。
isAlive() 判断进程是否存活。
wait() 线程等待。
notify() 线程唤醒。

4.2 多线程的三种创建方式

Java 多线程有 3 种创建方式如下:

  • 方式一:继承 Thread 类的方式创建线程;
  • 方式二:实现 java.lang.Runnable 接口;
  • 方式三:实现 Callable 接口。

4.3 多线程实现之继承 Thread 类

  • 步骤 1:继承 Thread 类 extends Thread;
  • 步骤 2:复写 run () 方法,run () 方法是线程具体逻辑的实现方法。

实例

/**
 * 方式一:继承Thread类的方式创建线程
 */
public class ThreadExtendTest extends Thread{
    //步骤 1
    @Override
    public void run() {
    //步骤 2
	    //run方法内为具体的逻辑实现
        System.out.println("create thread by thread extend");
    }
    public static void main(String[] args) {
   
        new ThreadExtendTest(). start();
    }
}

4.4 多线程实现之实现 Runnable 接口

Tips:由于 Java 是面向接口编程,且可进行多接口实现,相比 Java 的单继承特性更加灵活,易于扩展,所以相比方式一,更推荐使用方式二进行线程的创建。

  • 步骤 1:实现 Runnable 接口,implements Runnable;
  • 步骤 2:复写 run () 方法,run () 方法是线程具体逻辑的实现方法。

实例:

/**
 * 方式二:实现java.lang.Runnable接口
 */
public class ThreadRunnableTest implements Runnable{
   //步骤 1
    @Override
    public void run() {
   //步骤 2
		//run方法内为具体的逻辑实现
        System.out.println("create thread by runnable implements");
    }
    public static void main(String[] args) {
   
        new Thread(new ThreadRunnableTest()). start();
   }
}

4.5 多线程实现之实现 Callable 接口

Tips:方式一与方式二的创建方式都是复写 run 方法,都是 void 形式的,没有返回值。但是对于方式三来说,实现 Callable 接口,能够有返回值类型。

  • 步骤 1:实现 Callable 接口,implements Callable;
  • 步骤 2:复写 call () 方法,call () 方法是线程具体逻辑的实现方法。

实例

/**
 * 方式三:实现Callable接口
 */
public class ThreadCallableTest implements Callable<String> {
   //步骤 1
    @Override
    public String call() throws Exception {
    //步骤 2
	    //call 方法的返回值类型是 String
	    //call 方法是线程具体逻辑的实现方法
        return "create thread by implements Callable";
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException{
   
        FutureTask<String> future1 = new FutureTask<String>(new ThreadCallableTest());
        Thread thread1 = new Thread(future1);
        thread1. start();
        System.out.println(future1.get());
    }
}

4.6 匿名内部类创建 Thread

首先确认,这并不是线程创建的第四种方式,先来看如何创建。

实例

Thread t = new Thread(new Runnable() {
   
            @Override
            public void run() {
   
                System.out.println("通过匿名内部类创建Thread");
            }
        });

从代码中可以看出,还是进行了一个 Runnable 接口的使用,所以这并不是新的 Thread 创建方式,只不过是通过方式二实现的一个内部类创建。

4.7 Thread 编程测验实验

实验目的:对 Thread 的创建方式进行练习,巩固本节重点内容,并在练习的过程中,使用常用的 start 方法和 sleep 方法以及 线程的 setName 方法。

实验步骤

  • a.使用 Runnable 接口创建两条线程 :t1 和 t2;
  • b.设置线程 t1 和 t2 的线程名称分别为 “ThreadOne” 和 “ThreadTwo”;
  • c.线程 t1 执行完 run () 方法后,线程睡眠 5 秒;
  • d.线程 t2 执行完 run () 方法后,线程睡眠 1 秒。

实现:

public class ThreadTest implements Runnable{
   

    @Override
    public void run() {
   
        System.out.println("线程:"+Thread.currentThread()+" 正在执行...");
    }
    public static void main(String[] args) throws InterruptedException {
   
        Thread t1 = new Thread(new ThreadTest());
        t1.setName("ThreadOne");
        Thread t2 = new Thread(new ThreadTest());
        t2.setName("ThreadTwo");
        t1. start();
        t1.sleep(5000);
        t2. start();
        t1.sleep(1000);
        System.out.println("线程执行结束。");
    }</
  • 14
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值