(二)Java并行程序基础

本文介绍Java并发编程的基础概念,包括线程与进程的区别、线程的生命周期、线程的创建方式,以及线程间的同步机制如中断、等待、通知等。还介绍了volatile关键字的作用、线程组的使用、守护线程的概念、synchronized关键字的应用和一些常见的隐蔽错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


本节是《Java高并发程序设计》第二章的读书笔记,为尊重原创,需要详细阅读的建议购买原著。

2.1 线程和进程

1、进程是操作系统调度资源分配和调度的基本单位,进程相当于线程的容器,线程是轻量级进程,是程序执行的最小单位,使用多线程并行,是因为线程比进程的切换和调度的成本低。
2、线程的生命周期,
new创建线程,start()后,线程进入runnable状态,没有获得临界区资源进入BLOCKED阻塞状态,调用wait()等方法,进入waiting状态,等待某个预期状态出现后重新回到runnable,调用sleep()等方法,进入TIME_WATING状态,时间片用完或异常报错,进入TERMINATED状态。

2.2 初始线程

2.2.1 新建线程

1、继承Thread类,重写run方法,使用start()调用线程的run()
2、实现Runnable,可以用匿名内部类,实现run方法,将Runnable接口传入Thead的构造函数,因为Thread中有一个传入接口的构造函数,会调用接口的run方法,使用start()调用线程的run()。

2.2.2 线程中断

1、如果出现了wait()或sleep()方法,需要用Thread的中断函数

Thread.interrupt()	//中断线程,置标志位为true
Thread.isInterrupted()	//判断是否中断
Thread.interrupter()	//判断是否中断,并清除当前中断中断状态

使用sleep()或wait()方法时遇到中断会抛出checked异常,需要捕获此异常,设置Thread.interrupt()中断状态,通过代码中Thread.isInterrupted()检查中断状态来判断中断。

2.2.3 等待(wait)和通知(notify)

1、wait和notify是属于object方法的,二者都必须在sychronized代码块中使用,因为调用前必须要获取监控对象(monitor)。
2、wait()方法调用后,线程进入wait等待队列,notify()是随机唤醒从等待队列唤醒线程,notifyAll()是唤醒所有。
3、wait()方法调用后会释放所有的锁,保证其他线程能不被阻塞继续执行。sleep不释放锁。

2.2.4 挂起(suspend)和继续执行(resume)线程

1、suspend挂起后,不会释放锁,直到遇到resume()后线程才会继续执行,如果resume()在suspend()方法前执行了,那么系统可能工作异常,因此,这两个方法已经被丢弃。

2.2.5 等待线程结束(join)和谦让(yield)

1、join有两个方法

public final void join() throws InterruptedException
public final synchronized void join(long mills) throws InterruptedException

第一个方法是无限等待,直到目标线程执行完成
第二个方法是到指定时间后,线程继续执行
2、join的底层原理
本质也是调用了wait(),在目标线程执行完成后,调用notifyAll()唤醒所有的线程,因此,不要在Thread对象使用wait和notify等方法,避免引起API工作异常。
3、yield()是让线程让出CPU,但是让出后,同样会加入线程竞争。

2.3 volatile关键字

1、volatile保证了可见性、有序性和原子性
2、可见性和有序性不再赘述。
3、原子性可以理解为是sychronized的轻量级线程,不能保证复合操作和线程的原子性。
4、能避免重排序。

2.4 线程组

ThreadGroup tg = new ThreadGroup("PrintGroup");

1、将功能相同的线程放在一个线程中统一的管理,如使用activeCout()方法,stop()注意和Thread.stop()一样,可能会有数据不一致问题,list()方法等
2、注意的是,创建线程和线程组的命名要尽可能便于理解。

2.5 守护线程

1、用户线程是系统工作的线程,守护线程只有在守护对象结束,才退出。
2、设置守护线程,要在start()前

Thread t = new DaemonT();
t.setDaemon(true);

Java中线程的优先级是1-10,线程.setPriority(优先级),设置线程优先级。

2.6 sychronized关键字

1、使用方法,可以用于对象,实例方法,或者静态方法,代码段
2、加锁对象、方法和代码段的原理(略)
3、给静态方法加锁,本质是给当前的类加锁,因此,可访问实例的非静态方法。
4、注意的是,在创建线程中,要注意使用同一把锁。
5、sychronized保证原子性、可见性、有序性(因为每次只有一个线程执行,因此,只要保证串行化语意就能保证执行结果的一致,因此,有序性并不能防止指令重排序,和volatile不同)。
补充:
synchronized有两种形式上锁,一个是对方法上锁,一个是构造同步代码块。他们的底层实现其实都一样,在进入同步代码之前先获取锁,获取到锁之后锁的计数器+1,同步代码执行完锁的计数器-1,如果获取失败就阻塞式等待锁的释放。只是他们在同步块识别方式上有所不一样,从class字节码文件可以表现出来,一个是通过方法flags标志,一个是monitorenter和monitorexit指令操作。

2.7 隐蔽的错误

1、使用线程不安全的容器,如:ArrayList、HashMap等。
2、使用Integer对象i作为锁对象,如果出现i++,则会因为i++会创建Integer对象,将引用赋值给i,导致加锁对象出现不一致。

### Java 并行编程与多线程并发的设计模式及最佳实践 #### 1. 并发与并行的区别 在设计高效并发系统时,需明确“并发”与“并行”的差异。前者表示任务在同一时间段内交替执行,后者则依赖于多核处理器支持真正的同时运行[^3]。 #### 2. 创建线程的方式 Java 提供多种创建线程的方法,其中继承 `Thread` 类是最基础的一种。然而,在实际开发中推荐使用实现 `Runnable` 接口或 `Callable` 接口的方式来创建线程,因为这种方式更具灵活性且不会受到单一继承的限制。 #### 3. 原子性和可见性的保障 在多线程环境中,变量更新可能引发数据一致性问题。例如,`inc++` 操作并非原子操作,它由读取、修改和写入三个步骤组成[^4]。因此,仅靠 `volatile` 关键字无法保证其原子性,尽管它可以提供内存可见性。为了确保线程安全,可采用以下几种方式: - **锁机制 (`synchronized`):** 使用同步块或方法锁定共享资源访问权限,防止多个线程同时修改同一对象的状态。 - **显式锁 (`Lock` 接口):** 提供更灵活的功能,如尝试获取锁、定时等待锁释放等。 - **无锁算法:** 利用 CAS(Compare-and-Swap)技术实现高吞吐量下的线程安全性,典型代表为 `AtomicInteger` 和其他 `java.util.concurrent.atomic` 包中的类。 #### 4. 阻塞队列的应用场景 阻塞队列是一组重要的工具类,用于协调生产者消费者之间的关系。常见的阻塞队列有: - `ArrayBlockingQueue`: 基于数组结构的固定大小阻塞队列; - `LinkedBlockingQueue`: 基于链表结构的容量可选阻塞队列; - `SynchronousQueue`: 不存储元素的交接队列,生产和消费必须同步发生; - `PriorityBlockingQueue`: 支持优先级排序的无界阻塞队列; - `DelayQueue`: 只允许延迟到期的任务被取出的特殊队列[^5]。 这些队列广泛应用于线程池管理以及异步任务调度等领域。 #### 5. 线程池的最佳实践 合理配置线程池参数能显著提升应用程序性能。建议遵循如下原则: - 控制核心线程数与最大线程数的比例,避免因过度创建线程而导致上下文切换开销过大; - 设置合理的超时时间以便及时回收闲置资源; - 结合具体业务需求选用合适的拒绝策略(AbortPolicy, CallerRunsPolicy 等)。 以下是基于 `Executors` 工厂方法构建固定大小线程池的一个实例: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(10); Runnable task = () -> System.out.println("Executing Task"); for(int i=0;i<10;i++) { executor.submit(task); } executor.shutdown(); } } ``` #### 6. 设计模式在并发环境中的应用 一些经典设计模式经过改造后适用于复杂的并发场景,比如: - **生产者-消费者模式:** 解决了生产速度与消费能力不平衡的问题; - **工作窃取模式:** 让空闲的工作线程从繁忙线程的任务队列中偷取部分任务来分担压力。 以上内容涵盖了 Java 并行程序设计的核心知识点及其优化技巧[^1][^2]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值