首页 > 编程语言 >【Java 并发编程】详解

【Java 并发编程】详解

时间:2024-12-28 18:58:01浏览次数:6  
标签:Java lock 编程 并发 详解 线程 执行 等待 public

Java 并发编程

在当今的软件开发领域,随着多核处理器的广泛应用以及对系统性能要求的不断提高,Java
并发编程变得愈发重要。它允许我们充分利用计算机的多核资源,同时处理多个任务,提高程序的执行效率和响应能力。然而,并发编程并非易事,它涉及到诸多复杂的概念、机制以及需要注意的细节问题。

一、并发编程的基本概念

进程与线程

  • 进程(Process)
    进程是操作系统进行资源分配和调度的基本单位,它拥有独立的内存空间、代码段、数据段等资源。每个进程都像是一个独立运行的程序实例,例如我们在操作系统中同时打开的浏览器、文本编辑器等应用程序,它们各自在独立的进程中运行,彼此之间相对隔离,互不干扰。
  • 线程(Thread)
    线程是进程内部的执行单元,一个进程可以包含多个线程,这些线程共享进程的内存空间(包括代码段、数据段、堆等)以及一些系统资源(如文件描述符等)。线程相较于进程更加轻量级,创建和销毁的开销相对较小,它们可以并发地执行不同的任务,从而实现多任务并行处理的效果。比如在一个 Web 服务器应用中,主线程负责接收客户端的请求,而多个工作线程可以同时处理这些请求,提高服务器的响应速度和并发处理能力。

并发与并行

  • 并发(Concurrency)
    并发指的是在一段时间内,多个任务交替执行,宏观上看起来好像是同时在进行,但在微观层面,在单个 CPU 核心上,实际上是通过时间片轮转等调度机制,使得各个任务轮流获得 CPU 时间来执行。例如,单核 CPU 的计算机上同时运行多个程序,操作系统会快速地在这些程序间切换,让每个程序都能得到执行机会,给用户造成一种多个程序同时运行的错觉。
  • 并行(Parallelism)
    并行则是真正意义上的同时执行多个任务,它要求有多个 CPU 核心或者多个处理器,每个任务可以分配到不同的核心上,在同一时刻同时进行。例如,在多核 CPU 的服务器上,多个线程可以分别在不同的核心上同步执行,大大提高了整体的计算效率,常用于大数据处理、图形渲染等对计算性能要求较高的场景。

二、Java 并发编程的基础工具

(一)线程类(Thread)

在 Java 中,Thread 类是用于创建和操作线程的核心类。我们可以通过继承 Thread 类并重写 run 方法来定义线程的执行逻辑,示例:

class MyThread extends Thread {
    @Override
    public void run() {
        // 这里编写线程要执行的具体任务代码
        System.out.println("线程正在执行");
    }
}
public class ThreadExample {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); // 启动线程,会自动调用 run 方法
    }
}

需要注意的是,不能直接调用 run 方法来启动线程,而是要使用 start 方法,start 方法会由 Java 虚拟机去调度线程,使其进入就绪状态,等待获取 CPU 时间片后开始执行 run 方法中的逻辑。

(二)Runnable 接口

除了继承 Thread 类,还可以通过实现 Runnable 接口来定义线程的执行逻辑,这种方式更加灵活,因为 Java 是单继承的,实现接口可以避免继承 Thread 类带来的一些限制,并且方便实现资源共享等功能。示例代码如下:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("通过实现 Runnable 接口的线程在执行");
    }
}
public class RunnableExample {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

(三)Callable 接口与 Future 机制

Callable 接口类似于 Runnable 接口,也是用于定义线程的执行逻辑,但它有一些独特的优势。Callable 接口的 call 方法可以有返回值,并且能够抛出受检异常,而不像 Runnable 的 run 方法只能无返回值且不能抛出受检异常。
当使用 Callable 接口创建线程任务时,通常会结合 Future 机制来获取线程执行的结果。Future 接口代表一个异步计算的结果,通过它可以在未来某个时间点获取线程执行完成后的返回值,还能进行诸如取消任务等操作。
简单示例:

import java.util.concurrent.*;

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 42; // 简单返回一个整数作为示例结果
    }
}
public class CallableFutureExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(1);
        Future<Integer> future = executor.submit(new MyCallable());
        Integer result = future.get(); // 阻塞等待线程执行完成并获取结果
        System.out.println("线程执行结果: " + result);
        executor.shutdown();
    }
}

三、线程的状态与生命周期

(一)线程的状态

在 Java 中,线程有以下几种主要状态:

  • 新建(New):当通过 new 关键字创建一个 Thread 对象或者实现了 Runnable、Callable 的任务对象被包装成线程相关对象后,线程就处于新建状态,此时它还没有开始执行。
    就绪(Runnable):线程对象调用 start 方法后,它就进入就绪状态,意味着它已经准备好可以被 CPU 调度执行了,等待获取 CPU 时间片。需要注意的是,处于这个状态的线程可能会在就绪队列中等待一段时间,直到轮到它获得 CPU 资源。
  • 运行(Running):当线程获得 CPU 时间片,开始执行 run 方法(或者 call 方法)中的逻辑时,就处于运行状态,在这个状态下,线程会执行具体的任务代码。
  • 阻塞(Blocked):线程在执行过程中,可能会因为某些原因暂时停止执行,进入阻塞状态,比如等待获取某个锁(synchronized 关键字实现的锁或者 Lock 接口实现的锁)、等待某个条件满足(通过 Object 的 wait 方法等待条件)、执行了阻塞式的 I/O 操作等。处于阻塞状态的线程无法获得 CPU 时间片,直到阻塞原因解除,它才会重新回到就绪状态,等待再次被调度执行。
  • 等待(Waiting):线程调用了一些特定的方法(如 Object 类的 wait 方法、Thread 类的 join 方法等)后,会进入等待状态,此时它会释放持有的锁等资源,等待其他线程通过相应的通知机制(如 Object 的 notify 或 notifyAll 方法)来唤醒它,唤醒后它会进入就绪状态。
  • 超时等待(Timed Waiting):与等待状态类似,但它是有时间限制的等待,线程调用了带有超时参数的等待方法(如 Thread.sleep 方法、Object 类的 wait 方法传入超时时间等)后,会进入超时等待状态,在超时时间到达后,如果还没有被提前唤醒,就会自动回到就绪状态,继续等待被调度执行。
  • 终止(Terminated):线程执行完 run 方法(或者 call 方法)中的所有逻辑,或者因为出现异常等原因导致线程提前结束,就会进入终止状态,此时线程的生命周期结束,无法再被重新启动。

(二)线程状态转换

线程在不同状态之间会根据特定的条件和操作进行转换

  • 新建状态的线程调用 start 方法后,会转换到就绪状态,等待 CPU 调度进入运行状态。
    运行状态的线程如果遇到阻塞原因(如等待锁、执行阻塞 I/O 等),会转换到阻塞状态,阻塞原因解除后回到就绪状态。
    运行状态的线程调用 wait 等等待相关方法后,会进入等待状态,被唤醒后回到就绪状态;若调用带有超时参数的等待方法,则进入超时等待状态,超时后或提前被唤醒回到就绪状态。
  • 线程正常执行完任务或者因异常结束,就从运行状态转换到终止状态。

四、并发编程中的同步机制

(一)synchronized 关键字

方法级同步

在 Java 中,可以使用 synchronized 关键字修饰方法,使得该方法在同一时刻只能被一个线程访问,从而保证了方法执行的原子性。
例如:

public class SynchronizedMethodExample {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

在上述代码中,increment 方法被 synchronized 修饰,当多个线程同时调用这个方法时,只有一个线程能够进入方法内部执行,其他线程需要等待,这样就避免了多个线程同时对 count 变量进行操作导致的数据不一致问题。

代码块级同步

除了修饰方法,synchronized 关键字还可以修饰代码块,通过指定一个对象作为锁对象,来实现对特定代码块的同步控制。
示例:

public class SynchronizedBlockExample {
    private Object lock = new Object();
    private int count = 0;

    public void increment() {
        synchronized (lock) {
            count++;
        }
    }

    public int getCount() {
        return count;
    }
}

这里以 lock 对象作为锁,当线程进入 synchronized 代码块时,需要获取对应的锁,同一时刻只有获取到锁的线程可以执行代码块内的代码,实现了对 count 变量操作的同步保护。

(二)Lock 接口及其实现类

Java 还提供了 Lock 接口及其一系列实现类(如 ReentrantLock)来实现更灵活的锁机制。与 synchronized 关键字相比,Lock 接口提供了更多的功能,比如可以实现可中断锁(线程在等待锁的过程中可以被中断)、可轮询的锁(可以通过循环不断尝试获取锁)、公平锁(按照线程请求锁的先后顺序来分配锁,避免线程长时间等待)等。
ReentrantLock 的示例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockExample {
    private Lock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock(); // 一定要在 finally 块中释放锁,确保锁能被正确释放
        }
    }

    public int getCount() {
        return count;
    }
}

使用 Lock 接口时,要特别注意在合适的地方通过 unlock 方法释放锁,通常放在 finally 块中,防止因为异常等原因导致锁未被释放,造成死锁等问题。

(三)线程间通信机制

在并发编程中,线程之间往往需要进行通信和协作,常见的线程间通信机制有以下几种:

  • Object 类的 wait、notify 和 notifyAll 方法
    线程可以调用 Object 类的 wait 方法进入等待状态,释放持有的锁,等待其他线程通过调用同一个对象的 notify 方法(唤醒单个等待线程)或者 notifyAll 方法(唤醒所有等待线程)来唤醒它。

例如:

public class ThreadCommunicationExample {
    private Object lock = new Object();
    private boolean flag = false;

    public void producer() {
        synchronized (lock) {
            flag = true;
            lock.notifyAll(); // 通知消费者线程可以继续执行了
        }
    }

    public void consumer() {
        synchronized (lock) {
            while (!flag) {
                try {
                    lock.wait(); // 等待生产者线程通知
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("消费者线程开始执行,接收到生产者的通知");
        }
    }
}

在这个示例中,生产者线程生产出数据(通过设置 flag 为 true)后,通过 notifyAll 方法唤醒等待的消费者线程,消费者线程在 while 循环中不断检查 flag 状态,等待被唤醒后继续执行后续逻辑。

  • Condition 接口
    Condition 接口是在 Lock 接口基础上提供的更灵活的线程间通信机制,它可以实现类似于 Object 的 wait、notify 等功能,但可以针对不同的条件创建多个 Condition 对象,实现更细粒度的线程等待和唤醒控制。

例如:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionExample {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private boolean flag = false;

    public void producer() {
        lock.lock();
        try {
            flag = true;
            condition.signalAll(); // 唤醒等待在这个条件上的所有线程
        } finally {
            lock.unlock();
        }
    }

    public void consumer() {
        lock.lock();
        try {
            while (!flag) {
                condition.await(); // 等待条件满足
            }
            System.out.println("消费者线程开始执行,接收到生产者的通知");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

通过使用 Condition 接口,我们可以根据不同的业务逻辑条件,更精准地控制线程的等待和唤醒,提高并发程序的灵活性和可维护性。

五、Java 并发容器

(一)并发容器概述

  • 在并发编程中,使用普通的集合类(如 ArrayList、HashMap 等)往往会出现线程安全问题,因为它们在设计时并没有考虑多线程并发访问的情况。Java 提供了一系列并发容器来解决这个问题,这些并发容器在保证高效性能的同时,能够支持多线程安全地进行读写等操作。

(二)常见并发容器介绍

  • ConcurrentHashMap 是线程安全的哈希表实现,它在 Java 8 及之后的版本中采用了更加高效的分段锁(在早期版本是分段数组加链表的结构,通过对不同段加锁来实现并发控制,后来改进为基于 CAS 和 synchronized 关键字结合的方式实现更细粒度的并发控制),允许多个线程同时对不同的桶(bucket)进行读写操作,大大提高了并发读写的性能,常用于多线程环境下的键值对数据存储和访问场景,例如缓存系统等。
  • CopyOnWriteArrayList 是一种线程安全的动态数组实现,它的特点是在进行修改操作(如添加、删除元素)时,会先复制一份原数组,然后在新的副本上进行修改操作,修改完成后再将原数组的引用指向新的数组,这样在进行读操作时可以不加锁,保证了读操作的高效性,适用于读多写少的场景,比如一些配置信息的缓存列表,经常会被多个线程读取,但修改操作相对较少。
  • BlockingQueue 是一种支持阻塞操作的队列接口,它有多个实现类,常用的如 ArrayBlockingQueue(基于数组实现的有界阻塞队列)、LinkedBlockingQueue(基于链表实现的阻塞队列,可指定容量为有界或无界)等。它提供了诸如 put 方法(当队列满时,阻塞插入线程,直到队列有空间)、take 方法(当队列空时,阻塞获取线程,直到队列中有元素)等阻塞操作,常用于生产者 - 消费者模式中,方便线程间的数据传递和同步,例如线程池中的任务队列就是基于 BlockingQueue 实现的。

标签:Java,lock,编程,并发,详解,线程,执行,等待,public
From: https://blog.csdn.net/MISSXIAOJJ/article/details/144756378

相关文章

  • Java面试知识点总结:从基础到高级的全面指南
    在准备Java面试时,系统地覆盖从基础到高级的知识点是至关重要的。以下是一个详细的Java面试知识点总结,帮助你有针对性地准备面试。也算是我自己总结的知识点,先记录下来,说不定下次准备面试的时候,能再用上。1. Java基础在Java面试中,基础知识是考核的核心部分。掌握这些基础知识......
  • Java 课程一至六章综合复习总结
    姓名:李忠学号:202402150626《Java课程一至六章综合复习总结》第一章:初始Java与面向对象程序设计核心概念:Java语言的特点,如跨平台性、面向对象、安全性等。类与对象的基本概念,包括类的定义、对象的创建和使用。知识点:Java程序的基本结构,包含package语句、import语句......
  • 202412 电子学会 图形化编程 一级真题
    2024年12月Scratch图形化编程等级考试一级真题试卷题目总数:37  总分数:100选择题第1题  单选题点击下列哪个按钮,可以将红框处的Scratch程序放大?( )A.B.C.D.第2题  单选题下列哪个按钮可以让scratch舞台区变为小舞台模式?( )A.B.C.D.......
  • BCSP-X 2024 图形化编程 小学高年级组 真题
    BCSP-X2024图形化编程小学高年级组真题题目总数:40  总分数:100选择题第1题  单选题下图为scratch声音编辑界面,以下哪个选项可以把声音的声波曲线变成一条直线?( )A.B.C.D.第2题  单选题下面哪组scratch积木可以让角色只在舞台的左......
  • 【JAVA篇】------ spring aop
    文章目录AOP(面向切面编程)前言一、AOP的概念二、AOP的核心概念三、AOP在Java中的应用场景1.整体介绍2.静态代理模式3.动态代理模式(JDK动态代理)总结AOP(面向切面编程)......
  • JAVA 7~8次题目集总结
    本次完成了7~8次的题目集是接着上次的家居强电电路模拟程序-1和家居强电电路模拟程序-2后续迭代功能拓展完成了家居强电电路模拟程序-3和家居强电电路模拟程序-4家居强电电路模拟程序-3相比较之前的升级了电路其中线路中包含多个串联起来的并联电路以及增加了新的受控电路元件......
  • 工业相机镜头选型知识详解
    工业相机在机器视觉、自动化生产和检测等领域扮演着重要角色,而镜头作为工业相机的关键组件,其选型直接影响到成像效果和系统的整体性能。在本篇博客中,我们将详细讲解工业相机镜头选型的相关知识,帮助您在实际应用中选择最合适的镜头。工业相机镜头的基本概念工业相机镜头是光学......
  • Java难绷知识01——IO流的对象流
    Java难绷知识01之对象流本篇文章会探讨一些JavaIO流中比较容易被忽视的对象流,而且会相对的探讨其中的一些细节其中对于对象流的操作讲解会少一些,主要讨论的是一些细节在JavaIO流中,对象流(ObjectInputStream对象输入流和ObjectOutputStream对象输出流)用于将对象进行序列化和......
  • java三阶段总结(家用电路模拟)
    前言第六次题目集知识点:抽象类,抽象类不能被直接实例化!有抽象方法,抽象方法没有方法体,由继承它的子类提供具体实现。抽象类可以看作是一种模板,子类在实现接口方法时,必须实现接口中的所有抽象方法,除非子类也是抽象类在抽象类和接口中,多态体现为父类引用变量可以指向子类......
  • HTML&CSS&JavaScript&DOM 之间的关系?
    一、HTML中文名:超文本标记语言   英文名:HyperText Markup LanguageHTML是一种用来结构化Web网页及其内容的标记语言。HTML由一系列的元素组成,这些元素可以用来包围不同部分的内容,使其以某种方式呈现或者工作。图Ⅰ每个元素中都可以有自己的一些属性图Ⅱ......