JUC笔记
Lock和synchronize的区别:
1.Lock是一个接口,synchronize是java中的一个关键字
2.synchronize在发生异常时,会自动释放线程占有的锁,因此不会导致死锁的发生,而Lock在发生异常时,
如果没有手动释放锁,则有可能发生死锁现象,因此在使用Lock时需要在finally块中释放锁
3.Lock可以让等待锁的线程响应中断,而synchronize不行
4.通过Lock可以知道有没有成功获取锁,而synchronize无法办到
5.Lock可以提高多个线程进行读操作的效率
多线程编程步骤
1.创建资源类,在资源类创建属性和操作方法
2.在资源类操作方法
2.1 判断
2.2 业务
2.3 通知
3.创建多个线程,条用资源类的操作方法
线程间通信
wait
notifyAll
虚假唤醒问题
如果在if条件中使用wait,会造成虚假唤醒,因为wait方法在哪里等待,唤醒后就在哪里继续执行,当唤醒后,程序继续执行,不会重新判断条件
解决:
改为在while循环中使用wait,这样每一次唤醒之后,都会再次判断一下条件
线程间的定制化通信
Synchronized实现同步的基础:Java中的每一个对象都可以作为锁
具体表现为以下3种形式:
对于普通同步方法,锁是当前实例对象
对于静态同步方法,锁是当前类的Class对象
对于同步方法块,锁是Synchronized括号里配置的对象
多线程锁
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
优点:所有的线程都能得到资源,不会饿死在队列中。
缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。
可重入锁
Callable接口
读写锁
Synchronized和ReentrantLock都是独占的,每次只能进行一个操作,读不能共享
ReentrantReadWriteLock
读可以共享,提升性能,同时多人进行读操作
读的时候,不能写,只有读完成之后,才能写
锁降级
将写锁降级为读锁
线程池
JUC提供的创建线程的方式
````
一池n线程
Executors.newFixedThreadPool(int)
一池一线程
Executors.newSingleThreadExecutor()
可扩容的线程池
Executors.newCachedThreadPool()
````
线程池参数详解
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:
线程池的核心线程数。在创建了线程池后,默认情况下,
线程池中并没有任何线程,当有任务来之后,就会创建一个线程去执行任务,
当线程池中的线程数目达到 corePoolSize 后,就会把到多出的任务放到队列当中
maximumPoolSize:
线程池允许创建的最大线程数。
如果队列满了,并且已创建的线程数小于最大线程数,
则线程池会再创建新的线程执行任务。
注意:如果使用了无界的任务队列这个参数就没什么效果。
keepAliveTime:
线程活动保持时间。线程池的工作线程空闲后,保持存活的时间。
如果任务很多,并且每个任务执行的时间比较短,
可以调大这个时间,提高线程的利用率。
unit:
参数 keepAliveTime 的时间单位,在TimeUnit枚举类中,有 7 种取值。
可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),
毫秒(MILLISECONDS),微秒(MICROSECONDS),纳秒(NANOSECONDS)
用来指定keepAliveTime的单位,比如秒:TimeUnit.SECONDS
workQueue:
任务队列。用于保存等待执行的任务的阻塞队列。
可以选择有界队列和无界队列
threadFactory:
线程工厂,用来创建线程。主要是为了给线程起名字,
默认工厂的线程名字:pool‐1‐thread‐1
Executors工具类中的defaultThreadFactory()方法可以获取到
用于创建新线程的默认线程工厂
handler:
拒绝策略,当线程池里线程被耗尽,且队列也满了的时候会调用
JDK内置的拒绝策略:
ThreadPoolExecutor.AbortPolicy 默认拒绝策略,直接抛出异常RejectedExecutionException
ThreadPoolExecutor.CallerRunsPolicy 该任务被线程池拒绝,由调用 execute方法的线程执行该任务
ThreadPoolExecutor.DiscardPolicy 直接拒绝任务,不做任何处理也不抛出异常
ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务,把当前任务加入到队列中,然后重新尝试执行任务
线程池执行流程

自定义线程池
实际开发中,线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。
如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题
实际开发中,线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式
FixedThreadPool 和 SingleThreadPool,允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
CachedThreadPool 和 ScheduledThreadPool,允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM
/** * 自定义线程池 */ ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 2, 5, 2L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性