Java并发学习笔记(一)简介

本文深入探讨Java并发编程的关键概念,包括线程状态、多线程实现、synchronized与volatile关键字、线程间的等待与唤醒机制、线程让步与休眠、线程的join与interrupt方法,以及线程优先级和守护线程的概念。通过详细的解释和示例,帮助读者理解并发编程的复杂性和最佳实践。

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

                                        Java 并发(一)简介


目录

 Java 多线程(一)

一 进程和多线程简介

1.1 概念

1.2 线程的状态

1.3 多线程

1.4 一些常用方法

1.5 线程的优先级

二  synchronized关键词

2.1 synchronized基本原则

2.2 synchronized方法和代码块

2.3 实例锁和全局锁

三 volatile关键字

3.1 Java内存模型—— JMM   Java Memory Model

3.2 基本概念

3.3 Volatile原理

四  线程等待和唤醒

五  线程让步和休眠

5.1 线程让步

5.2  线程休眠

六  join()方法和interrupt()方法以及线程终止方法

6.1 join()

6.2 interrupt()

6.3 线程终止的方法

6.3.1 终止处于 阻塞状态 的线程

6.3.2 终止处于 运行状态 的线程

七  线程优先级和守护线程


一 进程和多线程简介

1.1 概念

并行:多个CPU实例或者多台机器同时执行一段处理逻辑,真正的同时。

并发:通过CPU调度算法,不是真的同时。   通常是提高运行在单处理器上的程序的性能

线程:线程是比进程更小的执行单位。是进程中执行运算的最小单位。是种轻量级进程。包含堆栈,寄存器,优先权。

进程:进程是程序的一次执行过程,是系统运行程序的基本单位。是动态的,一旦程序被载入内存中并准备执行是就是一个进程。

           进程是表示资源分配的基本概念,是调度运行的基本单位,是系统中并发执行的单位。

区别:一个线程只属于一个进程,但一个进程可以拥有多个线程。各进程是独立的。

           所有的线程共享进程的内存和资源。

           同一进程的多个线程共享代码段(代码和常量),数据段(全局和静态变量),扩展段(堆存储)。

1.2 线程的状态

线程状态转换

Blocked 阻塞(se)态

       等待阻塞:通过调用线程的wait()方法

       同步阻塞:线程获取synchronized同步锁失败

       其他阻塞:sleep()、join()等

 

1.3 多线程

多线程就是几乎同时执行多个线程。

优点:更好滴利用CPU资源。

public class MyRunnable implements Runnable {
	@Override
	public void run() {
		System.out.println("MyRunnable");
	}
}

public class MyRunnable implements Runnable {
	@Override
	public void run() {
		System.out.println("MyRunnable");
	}
}

线程状态切换的代价:

java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在户态与核心态之间切换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作。

由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级 :用户态 和 内核态 

内核态

CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序

用户态

只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺, CPU资源可以被其他程序获取

 


1.4 一些常用方法

currentThread() :  返回执行线程对象的引用;

getId() : 返回线程的标识符; 

getName(): 返回线程名称;

getPriority() :返回线程优先级;

isAlive(): 测试线程是否处于活动状态;

interrupt(): 中断线程;但实际上只是给线程设置一个中断标志,线程仍会继续运行。

setName(String name);

interrupted() isinterrupted() : 测试是否处于中断状态;interrupted()还会清楚中断标记

isDaemon() : 测试是否守护线程;

用户线程:Java虚拟机在它所有非守护线程已经离开后自动离开。

守护线程:守护线程则是用来服务用户线程的,如果没有其他用户线程在运行,那么就没有可服务对象,也就没有理由继续下去。  例如:垃圾回收线程,一些检测线程

setDaemon(boolean on);

join(): 等待该线程终止,指的是主线程等待子线程的终止。

yield():放弃当前CPU资源;

setPriority(int newPriority): 更改优先级;

1.5 线程的优先级

1. 线程优先级具有继承性;

2. 线程优先级具有随机性。

Thread.MIN_PRIORITY(常数1)   

Thread.NORM_PRIORITY(常数5)-默认优先级

Thread.MAX_PRIORITY(常数10)

二  synchronized关键词

解决共享资源竞争的问题;

        共享资源:一般是以对象形式存在的内存片段,但也可以是文件、输入/输出端口,或者是打印机。

每个对象有且仅有一个同步锁。同步锁是依赖对象而存在的。不同线程对同步锁的访问时互斥的,这种机制称为互斥量(mutex)

2.1 synchronized基本原则

第一条:当一个线程访问某对象的synchronized方法或者synchronized代码块时,其他线程对该对象的该synchronized方法或者synchronized代码块的访问将被阻塞。 
第二条:当一个线程访问某对象的synchronized方法或者synchronized代码块时,其他线程仍然可以访问该对象的非同步代码块。 
第三条:当一个线程访问某对象的synchronized方法或者synchronized代码块时,其他线程对该对象其他的synchronized方法或者synchronized代码块的访问将被阻塞。


2.2 synchronized方法和代码块

synchronized方法是修饰方法,这是一种粗粒度锁;      public synchronized void method(){ }

synchronized代码块是修饰代码块,这是一种细粒度锁;    synchronized(this){ }

2.3 实例锁和全局锁

实例锁:锁在某个实例对象上;

全局锁:该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。 static synchronized

三 volatile关键字

  • adj. [化学] 挥发性的;不稳定的;爆炸性的;反复无常的
  • n. 挥发物;有翅的动物

3.1 Java内存模型—— JMM   Java Memory Model

Javaåå­æ¨¡åå¾

所有的变量都是存储在主内存中,每个线程都是独立的工作内存,里面保存该线程使用到的变量的副本。线程对共享共享变量的所有操作必须在自己的工作内存,不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值传递需要通过主内存来完成。例如,线程1对共享变量的修改,要想被线程2及时看到,必须经历如下两个过程:
(1)把工作内存1中更新过的变量刷新到主内存中。
(2)将主内存中最新的共享变量的值更新到线程2中。  


3.2 基本概念

可见性: 线程之间的可见性,一个线程修改状态对另一个线程是可见的。

原子性 :JVM执行的最小单位,具有不可分割性。

有序性:线程之间操作的有序性。

3.3 Volatile原理

当变量声明为volatile类型时, 在读取volatile变量时总会返回最新写入的值。

 

四  线程等待和唤醒

wait()  让当前线程进入等待状态;并且让当前线程释放持有的锁;

notify()  唤醒当前对象上的等待线程;

notifyAll()  唤醒所有线程;

  • notify    vt. 通告,通知;公布 
API接口API说明
notify() 唤醒在此对象监视器上等待的单个线程
notifyAll()唤醒在此对象监视器上等待的所有线程
wait() 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或是notifyAll()方法”,当前线程被唤醒(进入“就绪状态”)
wait(long timeout) 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或notifyAll()方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)
wait(long timeout,int nanos)  让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)
 

为什么notify()、wait()等函数定义在Object类中而不是Thread类中?

notify()、wait()依赖于“同步锁”,而同步锁是该对象持有,并且每个对象有且只有一个。所以,你可以把wait()方法放进任何同步控制方法里,而不用考虑这个类是继承自Thread还是实现了Runnable接口。这就是为什么notify()、wait()等函数定义在Object类,而不是Thread类中的原因。实际上,只能在同步控制方法或同步控制块里调用wait()、notify()和notifyAll()(因为不用操作锁,所以sleep()可以在非同步控制方法里调用)。如果在非同步控制方法里调用这些方法,程序可以通过编译,但运行时将得到IllegalMonitorStateException异常,异常的大概是当前线程不是拥有者。意思是,调用wait()、notify()和notifyAll()的任务在调用这些方法前必须获取对象的锁。


说明:

1、wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。

2、wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关键字使用,即,一般在synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法。

3、 由于 wait()、notify/notifyAll() 在synchronized 代码块执行,说明当前线程一定是获取了锁的。

当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。

只有当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。

也就是说,notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其他线程 

4、wait() 需要被try catch包围,中断也可以使wait等待的线程唤醒。

5、notify 和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。

6、notify 和 notifyAll的区别

notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。

7、在多线程中要测试某个条件的变化,使用if 还是while?

  要注意,notify唤醒沉睡的线程后,线程会接着上次的执行继续往下执行。所以在进行条件判断时候,可以先把 wait 语句忽略不计来进行考虑,显然,要确保程序一定要执行,并且要保证程序直到满足一定的条件再执行,要使用while来执行,以确保条件满足和一定执行。

 

五  线程让步和休眠

5.1 线程让步

yield  vt. 屈服;出产,产生;放弃      vi. 屈服,投降      n. 产量;收益

在Java线程中,yield()方法的作用是让步,能让当前线程由“运行状态”进入“就绪状态”,从而让其他具有相同优先级的等待线程获取执行权,但是,并不能保证在当前线程调用yield()之后,其他具有相同优先级的线程就一定能获得执行权;也有可能是当前线程有进入到“运行状态”继续运行。

wait()的作用是让当前线程由“运行状态”进入到“等待(阻塞)”的同时,也会释放同步锁。而yield()的作用是让步,它也是让当前线程离开“运行状态”。区别是:
(1)wait()是让线程由“运行状态”进入到“等待(阻塞)状态”,而yield()是让线程由“运行状态”进入到“就绪状态”。
(2)wait()是会让线程释放它所持有的对象的同步锁,而yield()方法不会释放对象的同步锁。


5.2  线程休眠

sleep()方法    : 该方法定义在Thread类中,作用是让当前线程休眠,即当前线程会从“远程状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待CPU的调度执行。不会释放同步锁。

 

六  join()方法和interrupt()方法以及线程终止方法

6.1 join()

一个线程可以在其他线程之上调用join()方法,其效果是等待一段时间直到第二个线程结束结束才继续执行。如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程 t 结束才恢复(即 t.isAlive()返回为假)。

也可以在调用join()时带上一个超时参数(单位可以是毫秒,或者纳秒),这样如果目标线程在这段时间到期时还没有结束的话,join()方法总能返回。

对join()方法的调用可以被中断,做法是在调用线程上调用interrupt()方法,这时需要用到try_catch子句。

6.2 interrupt()

interrupt() 的作用是中断本线程。本线程中断自己是被允许的;其他线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。

但实际上只是给线程设置一个中断标志,线程仍会继续运行。

interrupt()是给线程设置中断标志;

interrupted()是检测中断并清除中断状态;

isInterrupted()只检测中断。

还有重要的一点就是interrupted()作用于当前线程,interrupt()和isInterrupted()作用于此线程,即代码中调用此方法的实例所代表的线程。
 

6.3 线程终止的方法

Thread中的stop()和suspend()方法,由于固有的不安全性,已经不建议使用。

6.3.1 终止处于 阻塞状态 的线程

通常,我们通过“中断”方式终止处于“阻塞状态”的线程。当线程由于被调用了sleep(),wait(),join()等方法而进入阻塞状态;若此时调用线程的interrupt()将线程的中断标记设为true。由于处于阻塞状态,中断标记会被清除,同时产生InterruptedException异常。将InterruptedException放在适当的为止就能终止线程

@Override
public void run() {
    try {
        while (true) {
            // 执行任务...
        }
    } catch (InterruptedException ie) {  
        // 由于产生InterruptedException异常,退出while(true)循环,线程终止!
    }
}

说明:在while(true)中不断的执行任务,当线程处于阻塞状态时,调用线程的interrupt()产生InterruptedException异常中断。中断的捕获在while(true)之外,这样就退出while(true)循环。

6.3.2 终止处于 运行状态 的线程

通常,我们通过“标记”方式终止处于“运行状态”的线程。其中包括“中断标记”和“额外添加标记”。

(1)通过“中断标记”终止线程

@Override
public void run() {
    while (!isInterrupted()) {
        // 执行任务...
    }
}

说明:
isInterrupted()来判断线程中的中断标记是不是为true。当线程处于运行状态,并且我们需要终止它时,可以调用线程的interrupt()方法,使用线程的中断标记为true,即isInterrupted()方法会返回true。此时,就会退出while循环。
注意:interrupt()并不会终止处于“运行状态”的线程,它会将线程的中断标记设为true。

(1)通过“额外添加标记”

private volatile boolean flag= true;
protected void stopTask() {
    flag = false;
}

@Override
public void run() {
    while (flag) {
        // 执行任务...
    }
}

 

说明:线程中有一个flag标记,它的默认值是true;并且我们提供stopTask()来设置flag标记。当我们需要终止该线程时,调用该线程的stopTask()方法就可以让线程退出while循环。
注意:将flag定义为volatile类型,是为了保证flag的可见性。即其它线程通过stopTask()修改了flag之后,本线程能看到修改后的flag的值。
 

总结:通用的终止线程的形式:

@Override
public void run() {
    try {
        // 1. isInterrupted()保证,只要中断标记为true就终止线程。
        while (!isInterrupted()) {
            // 执行任务...
        }
    } catch (InterruptedException ie) {  
        // 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。
    }
}

 

Object.wait, Thread.sleep方法,会不断的轮询监听 interrupted 标志位,发现其设置为true后,会停止阻塞并抛出 InterruptedException异常。

七  线程优先级和守护线程

Java中线程的优先级的范围是1~10,默认的优先级为5。“高优先级线程”会优先于“低优先级线程”执行。而且Java中有两种线程:用户线程和守护线程。可以通过isDeamon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务。需要注意的是:JVM在“用户线程”都结束后会退出。
  每个线程都有一个优先级。“高优先级线程”会优先于“低优先级线程”执行。每个线程都可以被标记为一个守护进程或非守护进程。在一些运行的主线程中创建新的子线程时,子线程的优先级被设置为等于“创建它的主线程的优先级”,当且仅当“创建它的主线程是守护线程”时“子线程才会是守护线程”。

当Java虚拟机启动时,通常有一个单一的非守护线程(该线程通过是通过main()方法启动)。JVM会一直运行直到下面的任意一个条件发生,JVM就会终止运行:
(1) 调用了exit()方法,并且exit()有权限被正常执行。
(2) 所有的“非守护线程”都死了(即JVM中仅仅只有“守护线程”)。
 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值