当前位置: 首页 > news >正文

JAVA小白学习日记Day10

1.线程锁

使用Runnable接口和Lambda表达式:

在 EasyThreadA 类的 mainA 方法中,通过创建 Runnable 实例 run,并使用Lambda表达式。 EasyThreadA::method 绑定到 run 上。然后创建两个线程 a 和 b,分别启动它们,它们会并发地执行 method 方法向共享的 list 中添加元素。这里的 list 是一个静态的 ArrayList,可能存在线程安全问题。
继承Thread类:

ThreadA 类继承自 Thread 类,重写了 run 方法,在其中向自己的 list 中添加元素。在 mainB 方法中创建了两个 ThreadA 实例 a 和 b,启动它们分别执行。每个线程拥有独立的 list,不存在直接的线程安全问题。
实现Runnable接口:

RunA 类实现了 Runnable 接口,在 run 方法中也向自己的 list 中添加元素。在 main 方法中创建了两个 RunA 实例作为 Thread 的任务,分别启动它们。每个 RunA 实例拥有独立的 list,不存在直接的线程安全问题。
需要注意的知识点:
多线程的实现方式:

可以通过实现 Runnable 接口或者继承 Thread 类来创建线程。推荐优先使用实现 Runnable 接口,因为Java中类只能单继承,而实现接口可以更灵活地组合多个接口实现不同的功能。
线程同步和等待:

使用 Thread.sleep(1000) 来模拟线程执行过程中的等待时间。在实际应用中,需要根据具体需求使用合适的线程同步机制,如 synchronized 关键字、Lock 接口、Atomic 类等来确保线程安全性。
Lambda表达式:

在 mainA 方法中使用了Lambda表达式 EasyThreadA::method,简化了匿名内部类的写法,提升了代码的简洁性和可读性。

package com.easy725;import java.util.ArrayList;
import java.util.List;public class EasyThreadA {static List list=new ArrayList();public static void method(){for (int i = 0; i <10 ; i++) {list.add(i+"A"+Thread.currentThread().getName());}}public static void mainA(String[] args) {Runnable run=EasyThreadA::method;Thread a=new Thread(run);Thread b=new Thread(run);a.start();b.start();try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(list.size());}public static void mainB(String[] args) {ThreadA a=new ThreadA();ThreadA b=new ThreadA();a.start();b.start();try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(a.list);System.out.println(b.list);}public static void main(String[] args) {RunA run=new RunA();Thread a=new Thread(run);Thread b=new Thread(run);a.start();b.start();try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(run.list.size());}}
class ThreadA extends Thread{public List list=new ArrayList();@Overridepublic void run() {for (int i = 0; i <10 ; i++) {list.add("a");}}
}
class RunA implements Runnable{public List list=new ArrayList();@Overridepublic void run() {for (int i = 0; i <10 ; i++) {list.add("a");}}
}

Lock对象的创建和使用:

Lock lock = new ReentrantLock();:创建了一个 ReentrantLock 类型的锁对象 lock。ReentrantLock 是可重入锁,支持公平和非公平性选择,默认是非公平锁(false)。

public void method() 方法中的逻辑:
if (lock.tryLock()) {:尝试获取锁。如果获取成功(返回 true),否则打印当前线程名并输出 "进入方法",然后输出 "结束方法",最后解锁 lock。
else {:如果尝试加锁失败,输出 "加锁未成功-----去执行别的代码",然后通过 Thread.sleep(1000) 模拟执行其他代码的情况,随后递归调用 method() 方法再次尝试获取锁。
main方法中的线程创建和启动:

Runnable run = new EasyThreadB()::method;:创建一个 Runnable 实例,通过方法引用绑定到 EasyThreadB 的 method() 方法上。
Thread a = new Thread(run); 和 Thread b = new Thread(run);:创建两个线程 a 和 b,它们共享同一个 run 实例,即同一个 method() 方法。
a.start(); 和 b.start();:启动两个线程并发执行 method() 方法。
需要注意的知识点:
Lock接口与ReentrantLock类:

Lock 接口提供了比传统的 synchronized 块和方法更广泛的锁定操作。ReentrantLock 是 Lock 接口的实现类,具有可重入特性,允许线程在同一个线程中多次获取同一个锁,避免死锁情况。
tryLock() 方法是 Lock 接口的一部分,尝试获取锁,如果成功则返回 true,否则立即返回 false,不会阻塞线程。这在避免线程长时间等待锁的情况下很有用。最后要unLock()方法解锁。
多线程同步与并发控制:

使用 ReentrantLock 可以更精确地控制多线程的并发访问,可以在需要的时候尝试获取锁,也可以实现公平性或非公平性的锁分配策略。
锁的释放应该始终放在 try 块的 finally 块中,以确保即使在获取锁期间发生异常,锁也能够被安全地释放。

死锁:

进程死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

线程死锁是指由于两个或者两个以上的线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。

死锁的产生还涉及到一些具体的条件,这些条件可以被看作是死锁产生的必要条件,包括:

互斥条件:资源不能被多个进程或线程同时访问,即资源是互斥的。
请求保持条件:进程或线程在请求资源时,已经持有其他资源,并且不愿意释放已经持有的资源。
不可剥夺条件:已经分配给进程或线程的资源,在未使用完之前不能被其他进程或线程剥夺。
循环等待条件:多个进程或线程之间形成一个循环等待链,每个进程或线程都在等待链中下一个进程或线程释放资源。

package com.easy725;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class EasyThreadB {//锁对象  LockLock lock=new ReentrantLock();//创建锁对象,在()中选择是否是公平锁,默认false(非公平锁)。public void method(){//lock.lock();//加锁//lock.trylock()  尝试加锁,加锁成功返回true,失败返回false。if (lock.tryLock()) {System.out.println(Thread.currentThread().getName() + "进入方法");System.out.println(Thread.currentThread().getName() + "结束方法");lock.unlock();//解锁}else {System.out.println("加锁未成功-----去执行别的代码");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}method();}}public static void main(String[] args) {Runnable run=new EasyThreadB()::method;Thread a=new Thread(run);Thread b=new Thread(run);a.start();b.start();}
}

读写锁 (ReentrantReadWriteLock):
ReentrantReadWriteLock 是一个锁容器,包含了读锁和写锁,能够提供比普通的互斥锁(如 ReentrantLock)更高的并发性。
读锁允许多个线程同时获取,适合对共享资源进行读取操作。
写锁是排他的,只允许一个线程获取,用于对共享资源进行写操作。
在代码中,通过 rrwl.readLock() 获取读锁,通过 rrwl.writeLock() 获取写锁,确保对共享资源的安全访问。

线程的启动和执行:
使用 Thread 类和 Runnable 接口创建多线程,通过 Thread.start() 启动线程,实现并发执行多个方法。
在 main 方法中,创建了多个读线程和写线程,分别调用 method() 和 methodWrite() 方法。
线程同步和锁的释放:
使用 lock.lock() 和 lock.unlock() 进行锁的获取和释放,确保在多线程环境下对共享资源的安全访问。
一般使用 try-finally 块确保在出现异常时能够正确释放锁,避免死锁或资源泄漏问题。

package com.easy725;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class EasyThreadC {//ReentrantReadWriteLock是个锁容器public static ReentrantReadWriteLock rrwl=new ReentrantReadWriteLock();public static ReentrantLock rl=new ReentrantLock();public static void method(){System.out.println(Thread.currentThread().getName()+"进入方法");Lock lock= rrwl.readLock();lock.lock();System.out.println(Thread.currentThread().getName()+"加锁成功----读锁");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"方法结束");lock.unlock();}public static void methodWrite(){System.out.println(Thread.currentThread().getName()+"进入方法");Lock lock= rrwl.writeLock();lock.lock();System.out.println(Thread.currentThread().getName()+"加锁成功----写锁");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"方法结束");lock.unlock();}public static void main(String[] args) {Runnable run=EasyThreadC::method;Runnable runWrite=EasyThreadC::methodWrite;Thread a=new Thread(run);a.start();Thread b=new Thread(run);b.start();Thread c=new Thread(run);c.start();Thread d=new Thread(run);d.start();Thread e=new Thread(run);e.start();Thread f=new Thread(runWrite);f.start();Thread g=new Thread(runWrite);g.start();Thread h=new Thread(runWrite);h.start();Thread i=new Thread(runWrite);i.start();System.out.println("main线程结束");}
}

同步和对象锁
synchronized 同步块:使用 synchronized (OBJ) 对象锁,确保多个线程在访问共享资源时的安全性。
wait 和 notify:通过 OBJ.wait() 和 OBJ.notify() 方法实现线程的等待和唤醒机制。
wait():使当前线程进入等待状态,并释放对象锁,直到其他线程调用对象的 notify() 或 notifyAll() 方法唤醒它。
notify() 和 notifyAll():唤醒等待在该对象上的一个或多个线程,使它们从等待池中进入就绪状态。
线程生命周期和状态
线程等待和重新运行:展示了线程如何在 wait() 被调用后进入等待状态,并在被唤醒后重新运行。
wait和sleep的区别:
wait是Object中定义的方法,可以由锁对象调用,让执行到该代码的线程进入到等待状态。
sleep是Thread类中定义的静态方法,也可以让执行到该行的线程进入等待状态。
区别:
1.sleep需要传入一个毫秒数,到达时间后会自动唤醒。wait不能自动唤醒,必须调用notify或者notifyALL方法。
2.sleep方法保持锁状态进入等待状态。wait方法会解除锁状态,其他线程可以进入运行。

package com.easy725;public class EasyThreadD {public static final Object OBJ =new Object();public static void method(){System.out.println(Thread.currentThread().getName()+"进入方法");synchronized (OBJ){OBJ.notify();//唤醒一条被该锁对象wait的线程//OBJ.notifyAll();//唤醒全部被锁对象wait的线程System.out.println(Thread.currentThread().getName()+"进入同步代码块");try {try {System.out.println(Thread.currentThread().getName()+"进入等待状态");OBJ.wait();//让执行到改代码的线程进入到等待状态,在等待池中。System.out.println(Thread.currentThread().getName()+"重新运行");} catch (InterruptedException e) {e.printStackTrace();}Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"结束同步代码块");OBJ.notify();}}public static void main(String[] args) {Runnable run = EasyThreadD::method;Thread a=new Thread(run);a.start();Thread b=new Thread(run);b.start();Thread c=new Thread(run);c.start();Thread d=new Thread(run);d.start();}//wait和sleep的区别://wait是Object中定义的方法,可以由锁对象调用,让执行到该代码的线程进入到等待状态。//sleep是Thread类中定义的静态方法,也可以让执行到该行的线程进入等待状态。//区别://1.sleep需要传入一个毫秒数,到达时间后会自动唤醒。wait不能自动唤醒,必须调用notify或者notifyALL方法。//2.sleep方法保持锁状态进入等待状态。wait方法会解除锁状态,其他线程可以进入运行。
}

2.线程池

线程池:

ThreadPoolExecutor 是 Java 中用于管理线程池的类,通过它可以有效地重用线程,完成线程的创建管理和销毁工作。
在代码中,通过 ThreadPoolExecutor 的构造方法创建了一个线程池 tpe。
在这里,使用了一个 ArrayBlockingQueue 作为任务队列 qu,它限制了队列的容量为 12。
任务提交与执行:

线程池可以执行两种类型的任务:Runnable 和 Callable。
Runnable run = EasyExecuters::method; 定义了一个简单的 Runnable 任务,它会调用 method 方法。
tpe.execute(run); 提交 run 任务给线程池执行。
Callable<String> call = EasyExecuters::methodCall; 定义了一个 Callable 任务,它会调用 methodCall 方法,并返回一个结果。
Future<String> f = tpe.submit(call); 提交 call 任务给线程池执行,并获取一个 Future 对象,用于获取任务的执行结果。
任务执行结果获取:

System.out.println(f.get()); 使用 Future 对象的 get() 方法来获取 call 任务的执行结果。这是一个阻塞方法,会等待任务执行完毕并返回结果。
线程池的关闭:

tpe.shutdown(); 调用 shutdown() 方法关闭线程池。这会使线程池停止接受新的任务,并尝试将现有的任务执行完毕后关闭。

package com.easy725;import java.util.Collection;
import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.*;public class EasyExecuters {//线程池   池==重用//完成线程创建,管理,销毁工作public static void main(String[] args) throws ExecutionException, InterruptedException {BlockingQueue qu = new ArrayBlockingQueue(12);ThreadPoolExecutor tpe=new ThreadPoolExecutor(5,10,10,TimeUnit.SECONDS,qu,Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());//线程任务:Runnable,CallableRunnable run=EasyExecuters::method;tpe.execute(run);Callable<String> call=EasyExecuters::methodCall;Future<String> f=tpe.submit(call);//f.cancel(true);//是否取消任务,一种中断线程的方式//tpe.submit(run);System.out.println(f.get());//会等待线程执行完毕//关闭线程池对象tpe.shutdown();}public static void method(){System.out.println(Thread.currentThread().getName()+"执行代码");}public static String methodCall(){System.out.println(Thread.currentThread().getName()+"执行代码call");return "callResult";}
}

1. 线程池的七个参数解释
在代码中,通过 ThreadPoolExecutor 的构造方法指定了以下七个参数:

corePoolSize: 核心线程数,即线程池中保持活跃的线程数量,即使它们是空闲的也会保留在池中。
maximumPoolSize: 最大线程数,线程池中允许的最大线程数量。当活跃线程数达到核心线程数,并且工作队列已满时,会创建新的线程,直到达到这个最大值。
keepAliveTime: 空闲线程的存活时间。超过核心线程数的线程在空闲超过这个时间后会被销毁,直到线程池的大小重新变为核心线程数为止。
unit: keepAliveTime 的时间单位。
workQueue: 工作队列,用于保存等待执行的任务。在本例中使用了 ArrayBlockingQueue,其容量为 12。
threadFactory: 线程工厂,用于创建新线程。
handler: 回绝策略,用于处理无法执行的任务。在本例中使用了 DiscardPolicy,即直接丢弃新任务而不抛出异常。

2.四种回绝策略(可以自定义)
AbortPolicy(默认):放弃该任务并会抛出一个异常,RejectedExecutionException。
CallerRunsPolicy:调用者执行,让传递任务的线程执行此任务。
DiscardOldestPolicy:放弃队列中时间最长的任务,不会抛出异常。
DiscardPolicy:直接放弃新的任务,不会抛异常。


3. 线程池的工作原理
当任务提交给线程池时,线程池会按照以下步骤处理任务:
如果当前活跃线程数小于核心线程数,创建新的线程来执行任务。
如果当前活跃线程数等于核心线程数,将任务加入工作队列。
如果工作队列已满但未达到最大线程数,创建新线程来执行任务。(占最大线程数)
如果工作队列已满且线程数已达到最大值,执行指定的回绝策略来处理新任务。


4. 内置的线程池对象
Java 提供了几种常见的内置线程池对象,可以根据应用的需要选择合适的线程池:

Executors.newCachedThreadPool(): 可根据需要(没有空闲就创建新线程)的线程池,空闲线程会在 60 秒后被回收。
Executors.newFixedThreadPool(int n): 固定大小的线程池,可以控制最大线程数。
Executors.newScheduledThreadPool(int corePoolSize): 支持定时和周期性的处理方案。
Executors.newSingleThreadExecutor(): 只有一个线程的线程池,确保所有任务按顺序执行。


5. 任务执行与线程池关闭
在代码中,通过 execute() 方法提交 Runnable 任务给线程池执行。
最后调用 shutdown() 方法关闭线程池,这会使线程池停止接受新任务,并尝试将已有的任务执行完毕后关闭。

package com.easy725;import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.*;public class EasyExecutersA {public static void main(String[] args) {//1.说明线程池的七个参数//2.四种回绝策略(可以自定义)//AbortPolicy(默认):放弃该任务并会抛出一个异常,RejectedExecutionException。//CallerRunsPolicy:调用者执行,让传递任务的线程执行此任务。//DiscardOldestPolicy:放弃队列中时间最长的任务,不会抛出异常。//DiscardPolicy:直接放弃新的任务,不会抛异常。//3.线程池的工作原理://  任务放置在工作队列中//1>池中是否有空闲的线程,如果有,让该线程执行任务。//2>如果池中没有空闲的线程,判断线程数量是否达到核心线程数。//3>如果没有达到,创建新的线程执行任务,直到填满核心数。如果已经达到,优先在队列中存储,直到队列填满。//4>工作队列填满之后再添加新的任务,判断是否达到最大线程数,如果没有,创建新的线程执行任务,直到填满最大线程数。//5>如果填满最大线程数,队列也已经填满,没有空闲的线程,就执行回绝策略。//线程池中的线程达到(超过)核心线程数,超出的数量会根据存活时间进行销毁。直到数量达到核心线程数。如果线程的数量少于核心线程数,不会消亡。//java中内置的线程池对象//可以根据工作任务创建线程,如果没有空闲的线程,就创建新的线程。线程存活时间60s。//Executors.newCachedThreadPool();//设定最大线程数量//Executors.newFixedThreadPool(10);//提供定时运行的处理方案//Executors.newScheduledThreadPool(10);//创建一个具有单个线程的线程池,保证任务队列完全按照顺序执行//Executors.newSingleThreadExecutor();BlockingQueue queue=new ArrayBlockingQueue(12);ThreadPoolExecutor tpe=new ThreadPoolExecutor(5,8,10,TimeUnit.SECONDS,queue,Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardPolicy());Runnable run =()->{System.out.println(Thread.currentThread().getName()+"执行run方法");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"执行结束");};for (int i = 0; i <21 ; i++) {tpe.execute(run);}tpe.shutdown();}
}

3.枚举

枚举类 默认继承Enum类
首行必须枚举所有的实例。Enum的实例是可比较的(根据实例出来的顺序)。但是不可序列化,不能被new,克隆等操作。
当枚举中只有一个实例时,这个类就是单例的。

package com.easy725;public enum  EasyColor {//枚举类 默认继承Enum类//首行必须枚举所有的实例。Enum的实例是可比较的(根据实例出来的顺序)。但是不可序列化,不能被new,克隆等操作。//当枚举中只有一个实例时,这个类就是单例的。RED,YELLOW,GREEN,BLUE,PINK;public void printColor(){System.out.println(this.name());System.out.println(this.ordinal());}}
class Test{public static void main(String[] args) {EasyColor.PINK.printColor();}
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • maven引入了jar包但在class文件里找不到jar包里的类
  • windows上启动Kafka
  • 暑期C++ 缺省参数
  • 视觉-语言大模型应用
  • 大厂面经:大疆嵌入式面试题及参考答案(4万字长文:持续更新)
  • 20240730 每日AI必读资讯
  • 使用easypoi读取Excel模板
  • JAVA8中的Stream API是什么及其用法
  • 数据结构(二叉树-2)
  • TCP/IP的三次握手和四次握手
  • 【MetaGPT系列】【MetaGPT完全实践宝典——多智能体实践】
  • 【Opencv】色彩空间 color space
  • CSS布局:左侧一个固定元素, 右侧元素数量不定, 要求右侧元素数量多时直接另起一行, 左侧元素单独一行
  • vscode搭建rust开发环境
  • 【Langchain大语言模型开发教程】评估
  • python3.6+scrapy+mysql 爬虫实战
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • 「面试题」如何实现一个圣杯布局?
  • Angular4 模板式表单用法以及验证
  • IDEA 插件开发入门教程
  • Java 23种设计模式 之单例模式 7种实现方式
  • JavaScript 奇技淫巧
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • opencv python Meanshift 和 Camshift
  • Python进阶细节
  • Python利用正则抓取网页内容保存到本地
  • spring + angular 实现导出excel
  • Vue2.0 实现互斥
  • yii2中session跨域名的问题
  • 反思总结然后整装待发
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 解析 Webpack中import、require、按需加载的执行过程
  • 前端相关框架总和
  • 微信公众号开发小记——5.python微信红包
  • 微信小程序--------语音识别(前端自己也能玩)
  • 我是如何设计 Upload 上传组件的
  • 延迟脚本的方式
  • k8s使用glusterfs实现动态持久化存储
  • 长三角G60科创走廊智能驾驶产业联盟揭牌成立,近80家企业助力智能驾驶行业发展 ...
  • 通过调用文摘列表API获取文摘
  • #if 1...#endif
  • $.ajax中的eval及dataType
  • $分析了六十多年间100万字的政府工作报告,我看到了这样的变迁
  • (1)Jupyter Notebook 下载及安装
  • (20)目标检测算法之YOLOv5计算预选框、详解anchor计算
  • (C语言)strcpy与strcpy详解,与模拟实现
  • (day6) 319. 灯泡开关
  • (web自动化测试+python)1
  • (附源码)springboot掌上博客系统 毕业设计063131
  • (简单) HDU 2612 Find a way,BFS。
  • (一)Dubbo快速入门、介绍、使用
  • **《Linux/Unix系统编程手册》读书笔记24章**
  • **登录+JWT+异常处理+拦截器+ThreadLocal-开发思想与代码实现**
  • .“空心村”成因分析及解决对策122344
  • .bashrc在哪里,alias妙用