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

多线程——基础

普通线程与多线程示意图通常系统中运行的程序/软件当做一个进程[迅雷]迅雷里面多个任务看做多个线程。总结一个程序一个进程一个进程可多个线程。线程是CPU调度和执行的的单位。多线程中至少一个为主线程注意真正多线程指多个cpu,即 多核。而学习中模拟出的多线程同一个时间点cpu只执行一个代码电脑cpu切换的快速让人有同时执行的错觉。核心概念线程就是独立的执行路径在程序运行时即使没有自己创建线程后台也会有多个线程如主线程gc线程main() 称之为主线程为系统的入口用于执行整个程序在一个进程中如果开辟了多个线程线程的运行由调度器安排调度调度器是与操作系统紧密相关的先后顺序是不能人为干预的。对同一份资源操作时会存在资源抢夺的问题需要加入并发控制线程会带来额外的开销如cpu调度时间并发控制开销。每个线程在自己的工作内存交互内存控制不当会造成数据不一致并行与并发并行多个CPU同时执行多个任务。 多个人各自建造各自房子 【不同事情并发一个CPU执行多个任务【采用时间片方式同时进行】 多个人秒杀一件物品 【同一件事线程创建方式继承Thread类 简单但java是单继承实现Runnable接口 无返回值实现Callable接口 有返回值继承Thread类/** * 多线程的创建,方式一:继承于Thread类 * 1、创建一个继承于Thread类的子类 * 2、重写Thread类的run()方法 * 3、创建Thread类的子类对象 * 4、通过此对象调用start() * 4.1、启动当前线程 * 4.2、调用当前线程的run() */ public class Thread1 { //线程开启不一定执行由cpu进行调度, public static void main(String[] args) { MyThread myThread new MyThread(); myThread.start(); try { Thread.sleep(200); //当前正在执行的线程休眠暂停执行但是该线程不会释放锁 //#注意此时sleep中资源无锁synchronized,或lock。结果不影响因为主线程又不是跟子线程同一份资源 //无sleep,主线程子线程交互输出。有时当前主线程暂停子线程执行完主线程执行 } catch (InterruptedException e) { e.printStackTrace(); } for (int i0;i200;i){ System.out.println(我是主线程i); } } } //主线程 子线程的执行顺序 由cpu进行调度cpu执行速度很快,一般先执行主线程 class MyThread extends Thread{ Override public void run() { for (int i0;i50;i){ System.out.println(我是子线程i); } } }无sleep时主、子线程交互输出。有了sleepmain方法中当前主线程暂停子线程执行完主线程接着执行。实现Runnable接口(推荐使用) 一份资源多个代理/** * 1、创建一个类实现Runnable接口 * 2、实现Runnable中的抽象方法run-编写方法体/线程执行体 * 3、创建实现类的对象 * 4、将此对象作为参数传递到Thread类的构造器中创建Thread类的对象 * 5、通过Thread类的对象调用start() */ //多线程抢票 public class ticket { public static void main(String[] args) { MyTicket myTicket new MyTicket();//同一份资源 //A为线程名称 new Thread(myTicket,A).start();//代理A new Thread(myTicket,B).start();//代理B } } class MyTicket implements Runnable { public int tickets 100; Override public void run() { //加上synchronized关键字 结果只会有一个线程执行到1.因为该资源始终占用中 while (tickets0) { System.out.println(Thread.currentThread().getName() 抢到了第 tickets-- 张票); } } }结果正确直到1实现Callable接口/** * 创建线程的方式三: 实现Callable接口 ---JDK1.5新增 * 对比Runnable接口可有返回值可抛出异常支持泛型 implements CallableBoolean!借助 * FutureTask类,比如获取返回结果 */ public class ThreadNew { public static void main(String[] args) { NewCall newCall new NewCall(); FutureTask task new FutureTask(newCall); new Thread(task).start(); //不需要返回值可省略 try { Object sum task.get(); System.out.println(sum); } catch (Exception e) { e.printStackTrace(); } } } class NewCall implements Callable{ Override public Object call() throws Exception { int sum 0; for (int i 1; i 100; i) { if (i % 2 0){ System.out.println(i); sum i; } } return sum; } }线程的生命周期JDK中用Thread.State类定义了线程的几种状态新建当一个Thread类或其子类的对象被声明并创建时新生的线程对象处于新建状态就绪处于新建状态的线程被start()后将进入线程队列等待cpu时间片此时它已具备了运行的条件只是没分配到cpu资源运行当就绪的线程被调用并获得cpu资源时便进入运行状态run()方法定义了线程的操作和功能阻塞在某种情况下被人为挂起或执行输入输出操作时让出cpu并临时中止自己的执行并进入阻塞状态死亡线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束静态代理/** * Author zz * Date 2021/3/12 15:27 */ //静态代理 多线程也是静态代理原理实现 public class Proxy { public static void main(String[] args) { //真实角色 you you new you(); //代理角色 Marry marry new WeddingCompany(you); //代理角色 帮助真实角色实现 marry.marride(); } } interface Marry{ //定义一个结婚接口 void marride(); } //真实角色 结婚 被代理类 class you implements Marry{ Override public void marride() { System.out.println(xxx结婚了); } } //婚庆公司 帮助我实现结婚 婚庆公司可以在结婚前后增加所需要的业务 代理类 class WeddingCompany implements Marry{ //引入真实角色 public Marry target; public WeddingCompany(Marry target){ this.targettarget; } Override public void marride() { before(); target.marride(); after(); } private void before() { System.out.println(结婚前进行场地的布置); } private void after() { System.out.println(结婚后收拾东西回公司); } } 结果 结婚前 xxx结婚了 结婚后总结被代理类和代理类都实现同一个接口或继承同一个类。静态程序运行前已存在代理类的字节码文件。/代理类事先定好的缺点由于静态代理在代码运行之前就已经存在代理类因此对于每一个代理对象都需要建一个代理类去代理当需要代理的对象很多时就需要创建很多的代理类严重降低程序的可维护性。用动态代理就可以解决这个问题Thread静态代理底层剖析Thread底层也是通过静态代理原理实现的通过我们开启子线程来定义真实角色Thread为代理角色Runnable为要实现得到接口run为接口中的方法Runnable接口以及里面的run方法动态代理动态代理:代理类在运行过程中产生的java提供了两种实现动态代理的方式分别是基于Jdk的动态代理【代理接口】和基于Cglib的动态代理【代理类】。/** * Author zz * Date 2021/3/12 18:45 */ public class ProxyDongTai { public static void main(String[] args) { //真实角色 MarryDT realRole new youDT(); //代理角色 注意这里不是实现接口InvocationHandler WeddingCompanyDT proxynew WeddingCompanyDT(realRole); //生成代理类 //MarryDT proxyRole (MarryDT) Proxy.newProxyInstance(proxy.class.getClassLoader(), realRole.getClass().getInterfaces(), proxy); //调用方法 生成代理类 MarryDT proxyRole (MarryDT)proxy.getProxy(); proxyRole.marride(); } } //代理此接口 interface MarryDT { //定义一个结婚接口 void marride(); } //真实角色 结婚 class youDT implements MarryDT { Override public void marride() { System.out.println(xxx结婚了); } } //动态代理类 要实现InvocationHandler接口 class WeddingCompanyDT implements InvocationHandler { //引入接口 真实对象 public MarryDT target; public WeddingCompanyDT(MarryDT target) { this.target target; } //生成得到的代理类 // loader 类加载器真实对象类加载器即可 // interfaces 代码要用来代理的接口真是对象实现的接口 // InvocationHandler 代理对象的调用处理程序 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this); } //proxy 代理对象 //method 对应于在代理对象上调用的接口方法的 Method 实例 //args 代理对象调用接口方法时传递的实际参数 Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); method.invoke(target,args); after(); return null; } private void after() { System.out.println(结婚后); } private void before() { System.out.println(结婚前); } } 结果 结婚前 xxx结婚了 结婚后public static Object newProxyInstance(ClassLoader loader, Class?[] interfaces, InvocationHandler h)loader一个classloader对象定义了由哪个classloader对象对生成的代理类进行加载interfaces一个interface对象数组表示我们将要给我们的代理对象提供一组什么样的接口代理类就可以调用接口中声明的所有方法。【返回一个Class?[]数组数组中包含了该对象实现的所有接口的Class对象】h一个InvocationHandler对象表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上并最终由其调用[实际执行者]//1.被代理类 public class SampleClass { public void test(){ System.out.println(hello world); } public static void main(String[] args) { Enhancer enhancer new Enhancer(); enhancer.setSuperclass(SampleClass.class); //2.MethodInterceptor 拦截器实现 匿名实现可另写一个类实现 enhancer.setCallback(new MethodInterceptor() { Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println(before method run...); Object result proxy.invokeSuper(obj, args); System.out.println(after method run...); return result; } }); SampleClass sample (SampleClass) enhancer.create(); //3.代理对象调用方法 sample.test(); } }JDK动态代理和cglib字节码生成的区别1、JDK动态代理只能对实现了接口的类生成代理而不能针对类2、Cglib是针对类实现代理主要是对指定的类生成一个子类覆盖其中的方法并覆盖其中方法的增强但是因为采用的是继承所以该类或方法最好不要生成final对于final类或方法是无法继承的线程安全1.synchronized 关键字synchronized是Java中最基本的同步机制之一它通过在代码块或方法上添加synchronized关键字来实现线程的同步和互斥线程安全synchronized可以确保多个线程在访问共享资源时不会发生冲突互斥访问同一时刻只能有一个线程访问共享资源。 原子性可重入性同一个线程可以多次获得同一把锁避免死锁内置锁每个java对象有个内置锁理解为synchronized 用的对象锁【对象的内置锁】如this,对象本身注意线程安全问题都是由全局变量及静态变量引起的。只读操作默认线程安全。有多个线程执行写操作要考虑线程同步为啥不是局部变量因为局部变量不能共享每次调用方法都是独立的。同步代码块synchronized (锁对象\任意对象) { 可能会产生线程安全问题的代码 } 如 //定义锁对象Objectlock new Object();Override public void run() { //模拟卖票 while(true){ //同步代码块synchronized (lock){if (ticket 0) { //模拟电影选坐的操作 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() 正在卖票: ticket--); } } } }同步方法public synchronized void method(){ 可能会产生线程安全问题的代码 } 如Object lock new Object();Override public void run() { //模拟卖票 while(true){ //同步方法method();} } //同步方法,锁对象this 注意锁对象是本类的对象 即 thispublic synchronized void method(){if (ticket 0) { //模拟选坐的操作 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() 正在卖票: ticket--); } }静态同步方法publicstaticsynchronized void method(){ 可能会产生线程安全问题的代码 }注意锁对象是类名.class 类对象唯一保证同一个锁。解释下synchronized实现原理Java对象头中有两个标志位用于存储synchronized锁的信息一个是表示当前对象是否被锁定的标志位另一个是表示持有锁的线程的标识符。当一个线程尝试获得一个被synchronized锁保护的资源时JVM会首先检查该对象的锁标志位。如果锁标志位为0表示该对象没有被锁定JVM会将锁标志位设置为1并将持有锁的线程标识符设置为0。如果锁标志位为1表示该对象已经被其他线程锁定当前线程会进入阻塞状态等待其他线程释放锁2.Lock接口我们使用Lock接口以及其中的lock()方法和unlock()方法替代同步对电影院卖票案例中Ticket类进行如下代码修改//创建Lock锁对象private finalLock ck new ReentrantLock();Override public void run() { //模拟卖票 while (true) { //synchronized (lock){ 此处不再用synchronized 关键字改为下列代码ck.lock(); // 获取/加上锁if (ticket 0) { //模拟选坐的操作 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() 正在卖票: ticket--); }ck.unlock();// 释放锁 此处锁为ck对象//} } }3.两者比较——synchronized与Lock的对比synchronizedLock隐式锁出了作用域、遇到异常等自动解锁显示锁手动开启和关闭锁有代码块锁和方法锁只有代码块锁使用起来简单JVM将花费较少的时间来调度线程性能更好。并且具有更好的扩展性提供更多的子类要么读写都加要么不加可以对读不加锁对写加锁不能从sleep的线程中抢到锁建议使用优先级Lock 同步代码块 同步方法静态4.同步方法与同步代码块效率取决于具体应用的场景和锁的粒度同步方法当整个方法需要同步时这是一个方便的选择。使用 synchronized 关键字锁定整个方法意味着任何调用该方法的线程都会获得同一个锁。因此如果方法内部的所有操作都需要同步那么使用同步方法会更简单。同步代码块如果在方法内只有一部分代码需要同步而其他部分不需要那么使用同步代码块可能会更有效率。同步代码块允许在一个方法内部锁定较小的代码段这样可以减小锁的范围增加并发度减少线程竞争。总结整个方法需要同步用同步方法否则同步代码块。5.volatile关键字线程有各自的线程栈Thread Stack用于存储局部变量、方法参数和调用堆栈等信息1. 线程的可见性volatile确保了被修饰的变量对所有线程的可见性【当一个线程A修改了一个volatile变量的值时\共享变量另外一个线程B能读到这个修改的值而不会使用B线程本地的缓存值。】2.顺序一致性禁止指令重排序public class VolatileExample { private volatile boolean flag false; // 使用 volatile 修饰变量 public void toggleFlag() { flag !flag; // 切换标志的值 } public void printFlag() { System.out.println(Flag: flag); // 输出标志的值 } public static void main(String[] args) { VolatileExample example new VolatileExample(); // 线程 A 不断切换标志的值 Thread threadA new Thread(() - { while (true) { example.toggleFlag(); } }); // 线程 B 持续打印标志的值 Thread threadB new Thread(() - { while (true) { example.printFlag(); } }); threadA.start(); threadB.start(); } } // 不加volatile某些情况下线程 B 可能会看不到线程 A 对 flag 的修改输出的是线程B的缓存值6.Volatile 和 Synchronized比较Volatile是轻量级的synchronized因为它不会引起上下文的切换和调度所以Volatile性能更好。Volatile只能修饰变量synchronized可以修饰方法静态方法代码。Volatile对任意单个变量的读/写具有原子性但是类似于i这种复合操作不具有原子性。而锁的互斥执行的特性可以确保对整个临界区代码执行具有原子性。多线程访问volatile不会发生阻塞而synchronized会发生阻塞。volatile是变量在多线程之间的可见性synchronize是多线程之间访问资源的同步性。多线程并发CAS技术概念CAS全称是 Compare and swap 直译过来就是比较并交换作用用于实现多线程同步的原子操作。也就是说CAS 是可以保证线程安全的在 Java 中java.util.concurrent.atomic包提供了一系列基于 CAS 的原子类例如AtomicInteger、AtomicLong、AtomicReference等它们可以在多线程环境下安全地执行自增、自减、设置值等操作而无需使用显式的锁。 【暂时理解为前面购票不是用的 synchronized或 Lock显示锁吗现在不用了给变量 int ticket 改为 private static AtomicInteger ticket new AtomicInteger(100); 】逻辑假设内存中的原数据为 V旧的预期值是 A需要修改的新值是 B (A和B是寄存器中的值)比较 A 与 V 是否相等 (compare)如果 A 与 V 相等就将 B 写入 V (swap)不相等则无事发生返回当前的操作是否成功public class Test { private static int count 0; public static void main(String[] args) throws InterruptedException { Thread t1 new Thread(() - { for (int i 0; i 5_0000; i) { count; } }); Thread t2 new Thread(() - { for (int i 0; i 5_0000; i) { count; } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(count); } } // 第一次执行结果: 64628 // 第二次执行结果: 62853 // 第三次执行结果: 53330 第一种想法 private static volatile int count 0; 结果 错误前面说过了 count; 这种形式包含了读取、递增和写回三个步骤不保证线程安全 第二种想法 synchronized关键字 将count操作包起来锁为类对象 synchronized (Test.class) { count; } 结果 正确 第三种做法使用 Java 提供的原子类保证上述代码的原子性 贴部分代码:\\ private static AtomicInteger count new AtomicInteger(0); // 初始值给 0 public static void main(String[] args) throws InterruptedException { Thread t1 new Thread(() - { for (int i 0; i 5_0000; i) { count.incrementAndGet(); } });原子类这里的实现就是每次修改 value(内存) 之前都会确认一下要修改的值是否改变了CAS 典型 ABA 问题CAS 在运行中的核心就是检查内存中V 和 预期值A是否一致如果一致就视为 V中途没有被改变所以就可以进行下一步的交换操作【将B写入V】。但是在并发环境中。它指的是在一个 CAS 操作中由于目标值在操作过程中被改变了两次最终值与预期值一致但实际上可能发生了意外的变化。举个例子来说明 ABA 问题假设有一个初始值为 A 的共享变量线程 T1 首先读取到了这个值为 A然后 T1 将其改为了 B但是又将其改回了 A此时线程 T2 也来进行 CAS 操作它发现目标值仍然是 A于是认为这个值没有被其他线程改变过于是成功地进行了交换。在这个过程中虽然最终值仍然是 A符合预期但实际上这个共享变量的状态已经发生了变化。为了解决 ABA 问题可以使用版本号或者标记位来标识变量的变化情况。例如Java 中的AtomicStampedReference类就是针对 ABA 问题的一种解决方案它不仅保存了要操作的对象的引用还保存了一个版本号当进行 CAS 操作时不仅比较值是否相等还要比较版本号是否相等以此来避免 ABA 问题的发生。乐观锁/悲观锁乐观锁线程安全问题不一定会发生因此它在读取数据时不会对数据加锁而是在更新数据时检查是否有其他事务已经修改了数据。【乐观锁会在数据表中引入一个版本号或时间戳字段当更新数据时它会比较当前读取到的版本号或时间戳与更新时的版本号或时间戳是否一致如果一致则执行更新操作否则认为数据已经被修改需要进行冲突解决】update table set stockstock-1, versionversion1 where id? and version?悲观锁线程安全问题一定会发生因此在操作数据之前先获取锁确保线程串行执行以防止其他事务同时修改数据。【数据库的锁机制来实现比如在读取数据时使用 SELECT ... FOR UPDATE 语句该语句会在读取数据的同时将数据行加锁直到事务结束才释放锁定。】select * from table where id? for update下图代码 先查询票/优惠卷剩余数量-判断 再去操作减库存 相当于 set numnum-1。//4. 判断库存是否充足 if (seckillVoucher.getStock() 1) { return Result.fail(优惠券已被抢光了哦下次记得手速快点); } //5. 扣减库存 boolean success seckillVoucherService.update().setSql(stock stock - 1).eq(voucher_id, voucherId).update(); if (!success) { return Result.fail(库存不足); }多个线程 并发执行出现了超卖问题。优惠卷剩余数目最后负数。【事务只保证多个数据库间的操作完整性不保证并发读取带来的问题】一般情况 事务下 更新A 再更新B ,没问题。 【没有读取】Transactional public void test(){ updateA(); // 纯更新 updateB(); // 纯更新 } 结论高并发下两个纯 UPDATE 不会互相影响不会乱 高并发下多个线程同时调用这个方法也不会互相干扰因为 没有读 没有判断 没有依赖旧值 只是两条独立 / 顺序更新 数据库会自动排队执行数据绝对安全。****高并发下 “读→判断→两个更新” 整体不安全会重复执行、覆盖、超卖。Transactional public void deductStock(Long productId) { // 1. 读 int stock mapper.getStock(productId); // 2. 判断 if (stock 0) { // 3. 修改 mapper.updateStock(productId, stock - 1); } }总结事务的原子性仅保证事务内所有SQL操作要么全部成功要么全部回滚但不控制并发事务之间的读写顺序。线程安全问题如超卖的本质是并发事务对共享数据执行的“读取-修改-写入”操作未能保证原子性导致中间状态被干扰。需通过隔离级别或锁机制解决。 简单说 有读有写的操作Transactional注解的核心是保证事务的原子性A和定义事务的隔离级别I。它确保事务内的操作要么全做要么全不做但它不会自动提升隔离级别到能够防止所有并发问题的程度如 SERIALIZABLE。因此在高并发场景下即使有Transactional仍需开发者根据业务需求考虑使用数据库锁悲观锁/乐观锁、原子操作语句如UPDATE ... WHERE condition等手段来主动防范竞态条件确保业务逻辑的正确性。场景是否线程安全原因解决方案纯读操作无共享数据安全读不修改数据无竞争无需处理纯读操作共享数据安全读不修改数据结果一致无需处理纯写操作独立数据安全写入不同数据无冲突无需处理纯写操作共享数据不安全并发写入可能导致数据错乱事务、乐观锁、悲观锁读写混合共享数据不安全读→改之间数据可能被其他事务修改隔离级别SERIALIZABLE、乐观锁纯写操作最简单办法本身不会导致数据丢失但可能导致负库存即业务违规UPDATE table SET a a - 1 WHERE id ? AND a 0;关键区别事务原子性 vs 业务逻辑原子性维度事务原子性Transactional保证业务逻辑原子性需额外设计范围数据库操作层面如多个SQL业务逻辑层面如读→判断→改并发问题类型事务内操作sql全部成功/失败幻读、不可重复读、并发竞争解决方案事务管理器回滚/提交锁机制乐观锁/悲观锁、分布式锁、事务隔离级别调整或者说数据库操作 不同于 应用层的业务逻辑死锁概念两个或多个进程线程在执行过程中因争夺资源而造成的一种互相等待的现象。【死锁发生在多个进程互相竞争有限资源的情况下。】synchronzied(A锁){ synchronized(B锁){ } } 死锁的产生有四个必要条件它们被称为死锁的四个必要条件互斥条件一个资源每次只能被一个进程使用。请求与保持条件一个进程因请求资源而阻塞时对已获得的资源保持不放。不剥夺条件资源只能由占有者主动释放不能被强行剥夺。循环等待条件多线程间形成一种头尾相接的循环等待资源关系。等待唤醒机制解释多个线程在处理同一个资源但是处理的动作线程的任务却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。等待唤醒机制所涉及到的方法wait :等待将正在执行的线程释放其执行资格 和 执行权并存储到线程池中。notify唤醒唤醒线程池中被wait的线程一次唤醒一个而且是任意的。【线程池中的线程具备执行资格】notifyAll 唤醒全部可以将线程池中的所有wait() 线程都唤醒。注意上述三个方法都是在 同步中才有效。同时这些方法在使用时必须标明所属锁这样才可以明确方法操作的到底是哪个锁上的线程。为什么这些操作线程的方法定义在Object类中因为这些方法在使用时必须要标明所属的锁而锁又可以是任意对象。能被任意对象调用的方法一定定义在Object类中。案例如上图说示输入线程向Resource中输入name ,sex , 输出线程从资源中输出先要完成的任务是1.当input发现Resource中没有数据时开始输入输入完成后叫output来输出。如果发现有数据就wait();2.当output发现Resource中没有数据时就wait() 当发现有数据时就输出然后叫醒input来输入数据。package thread; //模拟资源类 class Resource { private String name; private String sex; private boolean flag false; public synchronized void set(String name, String sex) { if (flag) try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } // 设置成员变量 this.name name; this.sex sex; // 设置之后Resource中有值将标记该为 true , flag true; // 唤醒output this.notify(); } public synchronized void out() { if (!flag) try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } // 输出线程将数据输出 System.out.println(姓名: name 性别: sex); // 改变标记以便输入线程输入数据 flag false; // 唤醒input进行数据输入 this.notify(); } } //输入线程任务类 class Input implements Runnable { private Resource r; public Input(Resource r) { this.r r; } Override public void run() { int count 0; while (true) { if (count 0) { r.set(小明, 男生); } else { r.set(小花, 女生); } // 在两个数据之间进行切换 count (count 1) % 2; } } } //输出线程任务类 class Output implements Runnable { private Resource r; public Output(Resource r) { this.r r; } Override public void run() { while (true) { r.out(); } } } //测试类 public class ThreadDemo07 { public static void main(String[] args) { // 资源对象 Resource r new Resource(); // 任务对象 Input in new Input(r); Output out new Output(r); // 线程对象 Thread t1 new Thread(in); Thread t2 new Thread(out); // 开启线程 t1.start(); t2.start(); } }多线程sleep、yield、wait、join方法的使用和区别sleep在指定时间内让当前正在执行的线程暂停执行但不会释放“锁标志”。不推荐使用。即当前线程进入阻塞状态在指定时间内不会执行。【注意一点不释放锁针对同一资源才有用】yield使当前线程让出CPU给其它线程执行的机会至于下次执行的线程是哪个取决于线程调度机制完全有可能还是原来的线程。即Thread.yield()使当前线程从执行状态运行状态变为可执行态就绪状态wait在其他线程调用对象的notify或notifyAll方法前导致当前线程等待。线程会释放掉它所占有的“锁标志”从而使别的线程有机会抢占该锁。【当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者会抛出IllegalMonitorStateException异常。唤醒当前对象锁的等待线程使用notify或notifyAll方法也必须拥有相同的对象锁否则也会抛出IllegalMonitorStateException异常。】join线程之间的并行执行变为串行执行。在A线程中调用了B线程的join()方法时表示只有当B线程执行完毕时A线程才能继续执行public class ThreadExample { public static void main(String[] args) { final Object lock new Object(); // 用于 wait 和 notify 的锁对象 // 线程 a 使用 sleep 方法 Thread a new Thread(() - { try { System.out.println(Thread a is starting...); Thread.sleep(2000); // 线程 a 暂停 2 秒钟 System.out.println(Thread a is done sleeping.); } catch (InterruptedException e) { e.printStackTrace(); } }); // 线程 b 使用 yield 方法 Thread b new Thread(() - { System.out.println(Thread b is starting...); Thread.yield(); // 线程 b 让出 CPU 时间片 System.out.println(Thread b is done yielding.); }); // 线程 c 使用 wait 和 notify 方法 Thread c new Thread(() - { synchronized (lock) { System.out.println(Thread c is starting...); try { lock.wait(); // 线程 c 进入等待状态直到被唤醒 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread c is done waiting.); } }); // 线程 d 使用 join 方法 Thread d new Thread(() - { System.out.println(Thread d is starting...); try { a.join(); // 线程 d 等待线程 a 执行完毕 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread d is done joining.); }); a.start(); b.start(); c.start(); d.start(); } }volatile关键字多线程详解完结_csdn 多线程_Chaffee_的博客-CSDN博客多线程 详解(掌握这篇就够了)_多线程详解_成长中的小boy的博客-CSDN博客多线程详解_牛娃_的博客-CSDN博客Java 多线程并发 CAS 技术详解_java cas-CSDN博客

相关文章:

多线程——基础

普通线程与多线程示意图 通常 系统中运行的程序/软件当做一个进程[迅雷],迅雷里面多个任务看做多个线程。 总结:一个程序一个进程,一个进程可多个线程。线程是CPU调度和执行的的单位。多线程中至少一个为主线程 注意:真正多线程…...

Verilog基础:task和function的使用(一)

相关文章 Verilog基础专栏https://blog.csdn.net/weixin_45791458/category_12263729.html 一、前言 任务(task)和函数(function)即提供了从不同位置执行公共过程的能力(因为这样可以实现代码共享),也提供了把大过程分解成小过程的能力&…...

从演示到实战:基于快马平台构建一个功能完整的AI绘画社区应用

今天想和大家分享一个很有意思的实战项目 - 在InsCode(快马)平台上构建一个功能完整的AI绘画社区应用。这个想法来源于阿里悟空官网展示的AI绘画应用场景,但我们要做的是更贴近真实产品的综合性解决方案。 项目整体规划 首先需要明确,一个完整的AI绘画社…...

新手零门槛部署openclaw:快马ai生成手把手配置教程与验证代码

最近在尝试部署openclaw这个开源爬虫框架时,发现网上资料比较零散,对新手不太友好。经过一番摸索,我总结了一套适合零基础同学的部署方案,整个过程在InsCode(快马)平台上测试通过,特别适合想快速上手的朋友。 硬件和系…...

手机怎么把deepseek对话导出

手机端 DeepSeek 对话怎么导出?原生功能缺口与三方工具全景对比摘要:根据 QuestMobile 2025年数据,DeepSeek 日活用户于2月1日突破3000万,成为史上最快达成该里程碑的应用。用户量激增后,“对话如何导出”"记录怎…...

从“只会聊天“到“全能员工“:2026年你需要了解的AI黑话(收藏版:小白程序员必备)

AI不再是一个聊天框。它已经进化成你的数字化同事。而你需要学会和它相处的"行话"。 引言:你的AI同事已经到岗还记得2023年人们第一次用ChatGPT的时候吗?大家的反应是:"哇,AI能写诗和画画!"然后就…...

【CW32无线抄表项目】W25Q+CW32程序示例

资料下载: https://telesky.yuque.com/bdys8w/01/zr02y6vd0r7mnzcl?singleDoc# 参考仓库: https://gitee.com/Armink/SFUD 一、程序分析 硬件总线映射(引脚与时钟的“避坑点”) #define FLASH_SPIx CW_SPI2 // 注意&…...

告别慢查询:用快马ai智能生成postgresql性能优化与索引方案

告别慢查询:用快马AI智能生成PostgreSQL性能优化与索引方案 在电商系统中,订单查询是最常见的操作之一。随着业务量的增长,数据库查询性能往往会成为瓶颈。最近我在优化一个电商平台的订单查询模块时,发现几个典型的性能问题&…...

SELinux 导致 K8s 日志 logrotate 无法轮询压缩

1. 问题现象在某 Linux 环境中,Kubernetes 日志无法自动轮询、无法压缩归档,具体表现如下:/var/log/kubernetes/kubelet.log 持续增大,达到 90MB 不再切割日志压缩包停留在某一时间点,之后不再生成新归档系统日志&…...

收藏必备!小白程序员轻松入门大模型,带你理清AI核心概念全框架

AI浪潮已经刮了一年多,身边越来越多人聊AI,张口就是“agent”“skill”,听得人只能点头附和,似懂非懂?其实不是听不懂,而是没有把这些概念串起来,告诉你它们到底是什么、彼此有啥关系。 咱不聊复…...

ObsPy地震学工具箱:从数据采集到科学发现的完整Python解决方案

ObsPy地震学工具箱:从数据采集到科学发现的完整Python解决方案 【免费下载链接】obspy ObsPy: A Python Toolbox for seismology/seismological observatories. 项目地址: https://gitcode.com/gh_mirrors/ob/obspy ObsPy是地震学领域的Python工具箱&#xf…...

React Native Boilerplate组件库终极指南:AssetByVariant与IconByVariant高级用法

React Native Boilerplate组件库终极指南:AssetByVariant与IconByVariant高级用法 【免费下载链接】react-native-boilerplate A React Native template for building solid applications 🐙, using JavaScript 💛 or Typescript &#x1f49…...

革命性终端网站构建工具LiveTerm:5分钟打造个性化网页终端

革命性终端网站构建工具LiveTerm:5分钟打造个性化网页终端 【免费下载链接】LiveTerm 💻 Build terminal styled websites in minutes! 项目地址: https://gitcode.com/gh_mirrors/li/LiveTerm LiveTerm是一款革命性的终端网站构建工具&#xff0…...

WireGuard排除私网地址聚类表(掩码形式)

事情缘由: 玩过WireGuard的都知道,它的配置文件是如下形式的: [Interface] PrivateKey *********************** Address **********/32 DNS 8.8.8.8 MTU1420 [Peer] PublicKey ************************ Endpoint 8.8.8.8:12345 A…...

绿联 安装SeaTable在线协同表格

绿联 安装SeaTable在线协同表格 1、镜像 seatable/seatable-developer:latest 2、安装 2.1、基础设置 重启策略:容器退出时总是重启容器。 2.2、网络 网络选择桥接(bridge)。 2.3、存储空间 装载路径/shared不可变更。 2.4、端口设置 容器端口固定80&#x…...

Pi0机器人控制实战:从模型下载到Web演示完整流程

Pi0机器人控制实战:从模型下载到Web演示完整流程 1. 项目概述与核心价值 Pi0是一个创新的视觉-语言-动作流模型,专为通用机器人控制设计。这个开源项目将深度学习与机器人技术相结合,通过自然语言指令和视觉输入来生成精确的机器人动作。项…...

DeepSeek架构深度解析:从原理到实践的完整指南

一、引言 2025年1月,DeepSeek-R1的发布在全球AI领域引发巨大震动——一个开源模型以远低于主流闭源模型的训练成本,实现了与之相匹敌的推理性能,直接导致英伟达股价单日下跌17%。在随后的时间里,DeepSeek团队持续迭代&#xff0c…...

数字记忆守护者:GetQzonehistory全攻略

数字记忆守护者:GetQzonehistory全攻略 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 一、数字记忆危机:我们正在失去什么? 在这个信息爆炸的时代&…...

nfs-subdir-external-provisioner核心配置参数详解:onDelete、archiveOnDelete、pathPattern

nfs-subdir-external-provisioner核心配置参数详解:onDelete、archiveOnDelete、pathPattern 【免费下载链接】nfs-subdir-external-provisioner Dynamic sub-dir volume provisioner on a remote NFS server. 项目地址: https://gitcode.com/gh_mirrors/nf/nfs-s…...

OpenClaw+Phi-3-vision-128k-instruct数据标注:半自动生成图像标签训练集

OpenClawPhi-3-vision-128k-instruct数据标注:半自动生成图像标签训练集 1. 为什么需要半自动数据标注 去年我在做一个宠物品种识别项目时,最头疼的就是数据标注环节。手动给5000多张猫狗图片打标签,不仅耗时耗力,还容易因为疲劳…...

Git-Credential-Manager-for-Windows安全存储机制深度解析:如何保护你的Git凭证安全 [特殊字符]

Git-Credential-Manager-for-Windows安全存储机制深度解析:如何保护你的Git凭证安全 🔐 【免费下载链接】Git-Credential-Manager-for-Windows Secure Git credential storage for Windows with support for Visual Studio Team Services, GitHub, and B…...

WebGLStudio.js虚拟文件系统完全指南:如何高效管理3D资源

WebGLStudio.js虚拟文件系统完全指南:如何高效管理3D资源 【免费下载链接】webglstudio.js A full open source 3D graphics editor in the browser, with scene editor, coding pad, graph editor, virtual file system, and many features more. 项目地址: http…...

文字的编码方式————不同UTF之间的区别

目录 1. 编码与字体 A. ASCII(American Standard Code for Information Interchange) B. ANSI C. UNICODE 2 . UNICODE 编码实现 (1)UTF-16 a. UTF-16 LE b. UTF-16 BE (2)UTF-8 (3&#xff…...

Protocol

在Python的世界里,Protocol这个概念,其实挺有意思的。它不是那种一上来就让人眼前一亮的语法糖,也不是什么解决具体问题的现成工具。它更像是一种约定,一种让代码“说清楚自己”的方式。如果你写过一段时间Python,尤其…...

TypeVar

## 关于Python里的TypeVar,你可能想知道的 最近在整理一些旧代码,翻到几年前写的一个通用缓存工具类,里面用到了TypeVar。当时注释里只简单写了一句“用于类型提示”,现在回头看,觉得可以展开聊聊这个东西。 TypeVar是…...

如何用Venera打造个性化漫画阅读体验?

如何用Venera打造个性化漫画阅读体验? 【免费下载链接】venera A comic app 项目地址: https://gitcode.com/gh_mirrors/ve/venera 你是否曾经感到市面上的漫画阅读应用千篇一律,界面设计缺乏个性?或者希望在深夜阅读时,应…...

全方位解析GBFR Logs:《碧蓝幻想:Relink》战斗数据分析平台

全方位解析GBFR Logs:《碧蓝幻想:Relink》战斗数据分析平台 【免费下载链接】gbfr-logs GBFR Logs lets you track damage statistics with a nice overlay DPS meter for Granblue Fantasy: Relink. 项目地址: https://gitcode.com/gh_mirrors/gb/gbf…...

性能测试中的“假阳性”:如何识别与避免?

在软件性能测试领域,“假阳性”是一个令测试团队既头疼又难以回避的挑战。它指的是测试报告或监控工具错误地发出性能警报,声称系统存在性能瓶颈或缺陷,但经过深入分析或在实际环境中验证,发现系统运行状态良好,并不存…...

Node.js企业级应用部署与运维完整方案:Google Cloud Platform实战指南

Node.js企业级应用部署与运维完整方案:Google Cloud Platform实战指南 【免费下载链接】nodejs-docs-samples Node.js samples for Google Cloud Platform products. 项目地址: https://gitcode.com/gh_mirrors/no/nodejs-docs-samples 想要构建稳定可靠的No…...

hello-uniapp自定义组件开发:打造属于你的UniApp组件库

hello-uniapp自定义组件开发:打造属于你的UniApp组件库 【免费下载链接】hello-uniapp uni-app框架演示示例 项目地址: https://gitcode.com/gh_mirrors/he/hello-uniapp UniApp作为一款优秀的跨平台开发框架,让开发者能够使用Vue.js语法编写一次…...