多线程——基础

张开发
2026/4/3 14:34:03 15 分钟阅读
多线程——基础
普通线程与多线程示意图通常系统中运行的程序/软件当做一个进程[迅雷]迅雷里面多个任务看做多个线程。总结一个程序一个进程一个进程可多个线程。线程是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博客

更多文章