Java 并发-上
进程数目和CPU数一致吗? 不一致,操作系统将CPU的时间分配给每一个进程,给人并行处理的感觉
多线程程序的特点: 扩展了多任务的概念:一个程序同时执行多个任务;
线程和任务的关系:通常,每一个任务称为一个线程,可以同时运行一个以上线程的程序称为多线程程序。
多线程和多进程的区别:每个进程拥自己的一整套变量。而线程则共享数据。
一.什么是线程
1.使用线程给其他任务提供机会
如果在网站上下载一个大图片过程中,改变了想法想关闭图片做其他的事,那就需要一个关闭按键或暂停按键中断下载,可以通过运行一个线程中的关键代码来保持用户对程序的控制权。
示例:在一个单独的线程中执行一个任务的简单过程
1)将任务代码移到实现了Runnable接口的类的run方法中,这个接口只有一个方法
public interface Runnable { void run(); }
//可以用lambda表达式创建一个实例:
Runnable r = ()->{task code};
2)由Runnable创建一个Thread 对象:
Thread t = new Thread(r);
3)启动线程:
t.start();
//警告:不要调用Thread类或Runnable对象的run方法,这样只会执行同一个线程的任务, Thread.start 方法才能创建执行run方法的新线程
二.中断线程
线程正常中止:①线程的run方法执行方法体最后一条语句后。经由执行return语句返回 ②出现了在方法中没有捕获的异常。
interrupt方法:强制线程终止。当一个线程调用interrupt方法时,线程的中断状态将被置位,每个线程都有这样的boolean标志,线程运行时应该时不时检查这个标志,判断线程是否终中止。
线程被阻塞时调用interrupt方法:产生InterruptedException异常。因为线程被阻塞时无法检测中断状态。
示例:线程将简单地将中断作为一个终止的请求
Runnable r =()->{ try { ... //Thread.currentThread()方法获得当前线程 while(!Thread.currentThread().isInterrupted()&&more work do)
{ do more work }
}
catch(InterruptedException e)
{ //线程在睡眠或等待期间中断 }
finally
{ cleanup,if required }
//退出这个run方法并终止这个线程
}
三. 线程状态
6种状态:①New(新创建)②Runnable(可运行)③Blocked(被阻塞)④Waiting(等待)⑤Timed waiting(计时等待)⑥Terminated(被终止)
3.1 新创建线程
new:new操作符可以创建一个新线程,如new Thread(r)
3.2 可运行线程
runnable:一旦调用start方法,线程处于runnable状态。可运行线程是否正在运行,取决于操作系统给线程提供运行时间。
3.3 被阻塞线程和等待线程
被阻塞线程和等待线程:处于这种状态的线程暂时不活动,它不运行任何代码且消耗最少的资源,直到线程调度器重新激活它
·当一个线程视图获取一个内部的对象锁。而该锁被其他线程持有,则该线程进入阻塞状态。
·当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。
·调用一些方法中的超时参数,使线程进入计时等待(timed waiting)状态。
3.4 被终止的线程
两个原因可以终止线程:①因为run方法正常退出而自然死亡。②因为一个没有捕获的异常终止了run方法而意外死亡。
四.线程属性
4.1 线程优先级
setPriority方法:可以提高或降低一个线程优先级,可以设置在MIN_PRIORITY(在Thread类中定义为1)与MAX_PRIORITY(定义为10)。默认情况再,一个线程继承父类的优先级。
注意:不要将程序构建为功能的正确性依赖于优先级
4.2 守护线程
t.setDaemon(true):将线程转换为守护线程,唯一用途是给其他线程提供服务,如计时线程。守护线程永远不去访问固有资源。
4.3 未捕获异常处理器
未捕获异常处理器:不需要任何catch子句处理非受查异常,在线程死亡前,异常就被传递到一个用于未捕获异常的处理器
该处理器必须属于一个实现Thread.UncaughtExceptionHandler接口的类,这个接口只有一个方法
void uncaughtException(Thread t,Throwable e)
setUncaughtExceptionHandler方法:为任何线程安装一个处理器 setDefaultUncaughtExceptionHandler静态方法:为所以线程安装一个默认的处理器
五.同步
5.1 竞争条件
线程在执行时可能出现误传
5.2 锁对象
两种机制当中代码块守并发访问干扰:synchronized关键字自动提供一个锁以及相关的“条件”,和ReentrantLock类。
运行机制:一旦一个线程封锁了对象,其他线程都无法通过lock语句,直到第一个线程释放锁对象前,都是阻塞状态。
5.3 条件对象
条件对象(又称条件变量:conditional variable):线程进入临界区,需要某一条件满足之后才能执行,于是使用一个条件对象来管理那些已经获得一个锁但是不能有用工作的线程。
newCondition方法:获得一个条件对象,一个锁对象可以有一个或多个相关的条件对象
5.4 synchronized关键字
synchronized关键字:Java中的每一个对象都有一个内部锁,使用这个关键字声明一个方法时,这个对象的内部锁将保护整个方法。
public synchronized void method() { method body } //等价于以下显示锁: public void method() { this.intrinsicLock.lock(); try { method body } finally { this.instrinsiLock.unlock();} }
建议:最好既不使用Lock/Condition 也不使用synchronized关键字,在许多情况下可以使用java.util.concurrent 包中的一种机制,它会为你处理所有的加锁。
如果synchronized关键字适合程序,那就尽量使用它,除非特别需要Lock/Condition结构提供的独有特性,否则不使用Lock/Condition。
5.5 同步阻塞
同步阻塞:线程除了调用同步方法获得内部锁,还可以通过进入一个同步阻塞获得内部锁。
synchronized(obj) { 关键部分 }
5.6 监视器概念
监视器monitor:可以保证多线程的安全性,在不需要程序员考虑如何加锁的情况下。
监视器特点:①如果一个方法用synchronized关键字声明,那么它表现的就像一个监视器方法,但是下面三点java对象不同于监视器
②域不要求必须是private
③方法不要求必须是synchronized
④内部锁对客户是可用的
5.7 Volatile域
volatile关键字:为实例域的同步访问提供了一种免锁机制,如果声明一个域为volatile,那么编译器和虚拟机就知道该域可能是被另一个线程并发更新的。
5.8 final 变量
声明一个域为final:这个方法可以安全地访问一个共享域
5.9 原子性
可以提供一个lambda表达式更新变量,它会为你完成更新。
largest.updateAndGet(x ->Math.max(x,observed)); //或 largest.accumulateAndGet(observed,Math::max);//accumulateAndGet方法利用一个二元操作符合并原子值和所提供的参数
5.10 死锁
死锁:因为多个线程互相等待,导致所有的线程都阻塞。
5.11 线程局部变量
可以构造局部SimpleDateFormat对象
为每个线程构造一个示例,可以用下的代码:
public static final ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)