多线程笔记

1. run() VS start()

在这里插入图片描述

  1. run()方法:
  • run()方法是java.lang.Runnable接口中定义的一个方法。当一个类实现了Runnable接口,并创建了一个线程对象时,你需要覆盖run()方法来定义线程要执行的任务。
  • run()方法定义了线程的主体逻辑,当线程被启动后,run()方法会被调用,线程会执行run()方法中的代码。
  • 如果直接调用run()方法,那么线程的执行就相当于普通的方法调用,不会创建新的线程,而是在当前线程中执行run()方法。
  1. start()方法:
  • start()方法是java.lang.Thread类中定义的一个方法。当你创建一个线程对象后,通过调用start()方法来启动线程。
  • 调用start()方法后,会创建一个新的线程,并且自动调用线程对象的run()方法来执行线程的主体逻辑。
  • start()方法会在后台启动一个新线程,并让该线程执行run()方法中的代码。

简而言之,run()方法用于定义线程的任务逻辑,而start()方法用于启动线程并执行run()方法中的任务逻辑。直接调用run()方法只是普通的方法调用,不会创建新线程;而调用start()方法会创建新线程并执行其中的任务逻辑。

public class Test {static class MyThread implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + ": " + i);try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}}public static void main(String[] args) {System.out.println("==================myThread.run():==================");MyThread myThread = new MyThread();myThread.run(); // 只有 run(),没有 start()System.out.println("==================thread.run():==================");Thread thread = new Thread(myThread);thread.run();System.out.println("==================thread.start():==================");thread.start();}
}

输出:

==================myThread.run():==================
main: 0
main: 1
main: 2
main: 3
main: 4
main: 5
main: 6
main: 7
main: 8
main: 9
==================thread.run():==================
main: 0
main: 1
main: 2
main: 3
main: 4
main: 5
main: 6
main: 7
main: 8
main: 9
==================thread.start():==================
Thread-0: 0
Thread-0: 1
Thread-0: 2
Thread-0: 3
Thread-0: 4
Thread-0: 5
Thread-0: 6
Thread-0: 7
Thread-0: 8
Thread-0: 9进程已结束,退出代码 0

2. 程序进程线程

  • 程序是静态的代码集合,进程是程序的执行实例,而线程是进程内部的执行单元。

  • 进程是操作系统进行资源分配和调度的基本单位,而线程是操作系统进行(CPU)调度和执行的基本单位。

  1. 程序(Program):
  • 程序是一组指令的集合,用于完成特定的任务或实现特定的功能。
  • 程序可以是编程语言中的源代码,也可以是已编译或已解释的可执行文件。
  • 在计算机中,程序通常被存储在磁盘上,并在需要时加载到内存中执行。
  1. 进程(Process):
  • 进程是计算机中运行的一个程序的实例。
  • 它是操作系统进行资源分配和调度的基本单位,包括内存空间、文件和设备的分配。
  • 每个进程都有独立的内存空间,可以在其中执行程序代码和保存数据。
  • 进程之间相互独立,通过进程间通信(IPC)来进行数据交换和协作。
  1. 线程(Thread):
  • 线程是进程内的一个执行单元,它共享了进程的内存空间和资源。
  • 一个进程可以包含多个线程,这些线程共享进程的上下文,但每个线程有自己的执行路径和栈。
  • 线程可以看作是轻量级的进程,它可以更高效地完成并发任务,提高系统的响应速度和资源利用率。
  • 多线程编程可以使程序更灵活、更高效,但也需要考虑线程同步和资源竞争等问题。

3. 单核多线程 VS 多核多线程

  1. 单核多线程(Single-Core Multi-Threading):
  • 在单核处理器上,多线程可以通过时间分片的方式实现并发执行。
  • 单核处理器通过在不同的线程之间快速切换来模拟并发执行,每个线程在一段时间内执行一小部分任务,然后切换到另一个线程。
  • 单核多线程可以提高系统的响应速度和资源利用率,但由于线程共享单一的处理器核心,实际上并不能同时执行多个线程。
  1. 多核多线程(Multi-Core Multi-Threading):
  • 多核处理器具有多个物理处理器核心,每个核心都可以独立执行指令。
  • 在多核处理器上,可以通过同时在多个核心上执行多个线程来实现并行处理。
  • 多核多线程可以实现真正的并行执行,每个核心都可以同时执行一个线程,从而加速整体计算速度。
  • 多核多线程可以更好地利用硬件资源,提高系统的性能和吞吐量,特别是对于需要大量计算的任务或对并行处理有较高要求的应用程序来说,具有明显的优势。

4. 注意

  • 线程就是独立的执行路径。
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,比如主线程,GC线程。
  • main()称之为主线程,为系统的入口,用于执行整个程序。
  • 在一个进程中,如果开辟了多个线程,线程的运行是由调度器(cpu)安排调度的,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
  • 对同一份资源操作时mm会存在资源抢夺的问题,需要加入并发控制。
  • 线程会带来额外的开销,如CPU调度时间,并发控制开销。
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。

5. 线程状态以及他们之间的转换

在这里插入图片描述

  • 在 Java 中,当一个线程在持有 synchronized 同步块的锁的情况下,从运行态切换到 WAITINGTIMED_WAITING 状态时,会释放锁。

  • 当一个线程处于持有 synchronized 同步块的锁的情况下,调用 wait() 方法会释放锁,而调用 sleep() 方法不会释放锁。

  • 但Lock锁需要手动 unlock()。

public class TestThreadState {public static void main(String[] args) throws InterruptedException {//线程的六种状态test1();//线程状态间的状态转换:NEW->RUNNABLE->TIME_WAITING->RUNNABLE->TERMINATEDtest2();//线程状态间的状态转换:NEW->RUNNABLE->WAITING->RUNNABLE->TERMINATEDtest3();//线程状态间的状态转换:NEW->RUNNABLE->BLOCKED->RUNNABLE->TERMINATEDtest4();}private static void test4() throws InterruptedException {System.out.println("======线程状态间的状态转换NEW->RUNNABLE->BLOCKED->RUNNABLE->TERMINATED======");
//定义一个对象,用来加锁和解锁AtomicBoolean obj2 = new AtomicBoolean(false);
//定义一个线程,先抢占了obj2对象的锁new Thread(() -> {synchronized (obj2) {try {//第一个线程要持有锁100毫秒Thread.sleep(100);//然后通过wait()方法进行等待状态,并释放obj2的对象锁obj2.wait();} catch (InterruptedException e) {e.printStackTrace();}}}).start();
//定义目标线程,获取等待获取obj2的锁Thread thread3 = new Thread(() -> {System.out.println("2.执行thread.start()之后,线程的状态:" + Thread.currentThread().getState());synchronized (obj2) {try {//thread3要持有对象锁100毫秒Thread.sleep(100);//然后通过notify()方法唤醒所有在ojb2上等待的线程继续执行后续操作obj2.notify();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("4.阻塞结束后,线程的状态:" + Thread.currentThread().getState());});
//获取start()之前的状态System.out.println("1.通过new初始化一个线程,但是还没有thread.start()之前,线程的状态:" + thread3.getState());
//启动线程thread3.start();
//先等100毫秒Thread.sleep(50);
//第一个线程释放锁至少需要100毫秒,所以在第50毫秒时,thread3正在因等待obj的对象锁而阻塞System.out.println("3.因为等待锁而阻塞时,线程的状态:" + thread3.getState());
//再等300毫秒Thread.sleep(300);
//两个线程的执行时间加上之前等待的50毫秒以供250毫秒,所以第300毫秒,所有的线程都已经执行完毕System.out.println("5.线程执行完毕之后,线程的状态:" + thread3.getState());}private static void test3() throws InterruptedException {System.out.println("======线程状态间的状态转换NEW->RUNNABLE->WAITING->RUNNABLE->TERMINATED======");//定义一个对象,用来加锁和解锁AtomicBoolean obj = new AtomicBoolean(false);//定义一个内部线程Thread thread1 = new Thread(() -> {System.out.println("2.执行thread.start()之后,线程的状态:" + Thread.currentThread().getState());synchronized (obj) {try {//thread1需要休眠100毫秒Thread.sleep(100);//thread1100毫秒之后,通过wait()方法释放obj对象是锁obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("4.被object.notify()方法唤醒之后,线程的状态:" + Thread.currentThread().getState());});
//获取start()之前的状态System.out.println("1.通过new初始化一个线程,但是还没有start()之前,线程的状态:" + thread1.getState());
//启动线程thread1.start();
//main线程休眠150毫秒Thread.sleep(150);
//因为thread1在第100毫秒进入wait等待状态,所以第150秒肯定可以获取其状态System.out.println("3.执行object.wait()时,线程的状态:" + thread1.getState());
//声明另一个线程进行解锁new Thread(() -> {synchronized (obj) {//唤醒等待的线程obj.notify();}}).start();
//main线程休眠10毫秒等待thread1线程能够苏醒Thread.sleep(10);
//获取thread1运行结束之后的状态System.out.println("5.线程执行完毕之后,线程的状态:" + thread1.getState() + "\n");}private static void test2() throws InterruptedException {System.out.println("======线程状态间的状态转换NEW->RUNNABLE->TIME_WAITING->RUNNABLE->TERMINATED======");
//定义一个内部线程AtomicBoolean obj = new AtomicBoolean(true);Thread thread = new Thread(() -> {System.out.println("2.执行thread.start()之后,线程的状态:" + Thread.currentThread().getState()); // RUNNABLEsynchronized (obj) {try {//休眠100毫秒
//                    Thread.sleep(100);obj.wait(100);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("4.执行Thread.sleep(long)完成之后,线程的状态:" + Thread.currentThread().getState()); // RUNNABLE});
//获取start()之前的状态System.out.println("1.通过new初始化一个线程,但是还没有start()之前,线程的状态:" + thread.getState()); // NEW
//启动线程thread.start();
//休眠50毫秒Thread.sleep(50);
//因为thread1需要休眠100毫秒,所以在第50毫秒,thread1处于sleep状态System.out.println("3.执行Thread.sleep(long)时,线程的状态:" + thread.getState()); // TIME_WAITING
//thread1和main线程主动休眠150毫秒,所以在第150毫秒,thread1早已执行完毕Thread.sleep(100);System.out.println("5.线程执行完毕之后,线程的状态:" + thread.getState() + "\n"); // TERMINATED}private static void test1() {System.out.println("======线程的六种状态======");System.out.println("线程-初始状态:" + Thread.State.NEW);System.out.println("线程-就绪状态:" + Thread.State.RUNNABLE);System.out.println("线程-阻塞状态:" + Thread.State.BLOCKED);System.out.println("线程-等待状态:" + Thread.State.WAITING);System.out.println("线程-限时等待状态:" + Thread.State.TIMED_WAITING);System.out.println("线程-终止状态:" + Thread.State.TERMINATED + "\n");}
}

Java并发10:线程的状态Thread.State及其线程状态之间的转换 - 姚春辉 - 博客园

6. 四种创建线程的方法

  • 继承 Thread 类

    继承Thread类,重写 run() 方法,实例化该类并调用 start() 方法启动线程

    public class ExtendThread extends Thread {@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}public static void main(String[] args) {ExtendThread extendThread = new ExtendThread();extendThread.start();}
    }
    
  • 实现 Runnable 接口

    实现 Runnable 接口,并实现 run() 方法,将该类的实例传递给 Thread 类的构造函数,调用Thread 类的实例的 start() 方法启动线程

    public class ImplementsRunnable implements  Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}public static void main(String[] args) {ImplementsRunnable runnable = new ImplementsRunnable();Thread thread = new Thread(runnable, "Thread name");thread.start();}
    }
    
  • 实现 Callable 接口

    实现 Callable 接口,并实现 call() 方法(带返回值),将该类的实例传递给 ExecutorService 实例的 submit() 方法,可以获得 Future 对象,通过这个对象可以获得线程执行结果

    public class ImplementsCallable implements Callable<String> {@Overridepublic String call() {return Thread.currentThread().getName();}public static void main(String[] args) throws ExecutionException, InterruptedException {ImplementsCallable callable1 = new ImplementsCallable();ExecutorService executorService = Executors.newFixedThreadPool(3);Future<String> submit1 = executorService.submit(callable1);String result1 = submit1.get();System.out.println(result1);}
    }
    
  • 线程池

    Java 提供了 java.util.concurrent.Executor 接口和 java.util.concurrent.ExecutorService 接口,以及它们的实现类 ThreadPoolExecutorScheduledThreadPoolExecutor

    可以通过工厂方法 Executors 来创建不同类型的线程池,如 newFixedThreadPool(int)newCachedThreadPool()newSingleThreadExecutor()

    public class ThreadPool {public static void main(String[] args) {ThreadPoolExecutor threadPoolExecutor 
    = new ThreadPoolExecutor(9, 17, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));for (int i = 0; i < 50; i++) {threadPoolExecutor.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}});}threadPoolExecutor.shutdown();}
    }
    

7. 静态代理与动态代理;基于接口/类的动态代理

  1. 静态代理:
  • 在静态代理中,代理类和目标类在编译期间就已经确定。代理类负责将请求转发给目标对象,并可以在转发请求前后执行额外的操作。静态代理的一个典型应用场景是在不修改目标对象的情况下,增加目标对象的功能或者控制目标对象的访问。
  • 静态代理的缺点是每个代理类只能代理一个接口或类,如果需要代理多个类或接口,就需要创建多个代理类,导致代码重复和维护成本增加。
  1. 动态代理:
  • 动态代理是在运行时动态生成代理类,而不是在编译时确定。Java 中的动态代理主要是通过 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口实现的。
  • 动态代理可以在运行时动态地创建代理类,根据需要代理的接口或者类动态生成代理对象,无需为每个接口或类创建单独的代理类。这样可以减少代码的重复性,提高代码的灵活性和可维护性。
  • 通过动态代理,可以在运行时动态地处理目标对象的方法调用,包括在调用目标对象方法前后进行一些操作,比如记录日志、性能监控、事务管理等。
  • 基于接口的动态代理:这种动态代理是针对接口实现的代理,它要求被代理的类必须实现一个或多个接口。Java 中的 java.lang.reflect.Proxy 就是基于接口的动态代理实现方式。

  • 基于类的动态代理:这种动态代理是针对类的继承关系实现的代理,它可以代理类的所有方法,包括继承自父类的方法。在 Java 中,常用的基于类的动态代理库是 CGLIB。

代码演示:

  • 基类:

    Interface: Marry.java

    public interface Marry{void happyMary();
    }
    

    接口的实现类:You.java

    public class You implements Marry{@Overridepublic void happyMary() {System.out.println("fatfish 结婚了……");}
    }
    
  • 静态代理:

    静态代理类:

    public class WeddingCompany implements Marry {private Marry target;   // 代理--->真实目标角色,帮谁结婚public WeddingCompany(Marry target){this.target = target;}@Overridepublic void happyMary() {before();this.target.happyMary();after();}private void after(){System.out.println("结婚后,收尾款!");}private void before(){System.out.println("结婚前,布置现场!");}
    }
    

    静态代理测试类:

    public class TestStaticProxy {public static void main(String[] args) {new Thread(()-> System.out.println("因为爱情")).start();WeddingCompany weddingCompany = new WeddingCompany(new You());weddingCompany.happyMary();}
    }
    

    输出:

    因为爱情
    结婚前,布置现场!
    fatfish 结婚了……
    结婚后,收尾款!
    
  • JDK 动态代理

    JDK 动态代理类:

    public class JDKDynamicProxyHandler implements InvocationHandler {private Object target;public JDKDynamicProxyHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {before();Object result = method.invoke(target, args);after();return result;}private void after(){System.out.println("结婚后,收尾款!");}private void before(){System.out.println("结婚前,布置现场!");}
    }
    

    JDK 动态代理测试类:

    public class JDKTestDynamicProxy {public static void main(String[] args) {new Thread(()-> System.out.println("因为爱情")).start();Marry you = new You(); // 接口ClassLoader classLoader = you.getClass().getClassLoader();Class<?>[] interfaces = you.getClass().getInterfaces();JDKDynamicProxyHandler jDKDynamicProxyHandler = new JDKDynamicProxyHandler(you);Marry proxy = (Marry) Proxy.newProxyInstance(classLoader, interfaces, jDKDynamicProxyHandler);proxy.happyMary();}
    }
    
  • CGLIB 动态代理

    CGLIB 动态代理类:

    public class CglibDynamicProxyHandler implements MethodInterceptor {private Object target;public CglibDynamicProxyHandler(Object target) {this.target = target;}@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {before();Object result = methodProxy.invokeSuper(o, args);after();return result;}private void after(){System.out.println("结婚后,收尾款!");}private void before(){System.out.println("结婚前,布置现场!");}
    }
    

    CGLIB 动态代理测试类:

    public class TestCglibDynamicProxy {public static void main(String[] args) {new Thread(()-> System.out.println("因为爱情")).start();You you = new You(); // 类CglibDynamicProxyHandler proxyHandler = new CglibDynamicProxyHandler(you);You proxyInstance = (You) Enhancer.create(You.class, proxyHandler);proxyInstance.happyMary();}
    }
    

8. join(), join(long millis), yield()

join() 方法用于等待目标线程执行完毕,然后再继续执行当前线程。

join(long millis) 方法是 join() 方法的一个重载版本,它允许设置等待的最大时间。如果目标线程在指定的时间内未执行完毕,当前线程会继续执行。

yield() 方法使当前执行的线程让出 CPU 执行权,从而让线程调度器重新选择其他线程来执行。yield() 方法通常用于在同一优先级的线程中,让出执行权给其他线程。

public class Test {static class MyThread implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "开始执行");Thread.yield();System.out.println(Thread.currentThread().getName() + "完成");}}public static void main(String[] args) throws InterruptedException {new Thread(new MyThread(), "线程A").start();Thread thread = new Thread(new MyThread(), "线程B");thread.start();thread.join(2000); // 主线程等待thread执行2秒钟System.out.println("主线程执行完毕");}
}
线程A开始执行
线程A完成
线程B开始执行
线程B完成
主线程执行完毕

9. stop(), interrupt()

  • stop() 方法被用于停止线程的执行。它是一个过时的方法,不推荐在实际开发中使用,因为它可能会导致线程不可预料的状态,比如无法释放的锁,数据不一致等问题。这是因为 stop() 方法会直接终止线程,而不会进行清理工作。因此,一般情况下应该避免使用 stop() 方法。

    当调用 stop() 方法时,线程的状态会直接转变为 TERMINATED,即终止状态。

    改进:标志位:

    /*** 测试stop* 1.建议线程正常停止-->利用次数,不建议死循环* 2.建议使用标志位-->设置一个标志位* 3.不要使用stop或者destroy等过时或者JDK不建议使用的方法*/public class TestStopThread implements Runnable{// 1.设置一个标志位private boolean flag = true;public static void main(String[] args) {TestStopThread stop = new TestStopThread();new Thread(stop).start();for (int i = 0; i < 1000; i++) {System.out.println("main...." + i);if(i==900){// 调用stop()切换标志位,让线程终止stop.stop();System.out.println("线程该停止了");}}}@Overridepublic void run() {int i = 0;while(flag){System.out.println("run……Thread" + i++);}}// 2.设置一个公开的方法停止线程,转换标志位public void stop(){this.flag = false;}
    }
    
  • interrupt() 方法用于中断线程的执行。它不会直接停止线程,而是向线程发送一个中断信号,线程可以在合适的时机检测到这个信号并作出相应的处理。通常情况下,被中断的线程会抛出 InterruptedException 异常,从而退出执行。

    调用 interrupt() 方法会将线程的中断状态设置为 true,但并不意味着线程会立即停止执行。线程需要在适当的时机检查中断状态,并根据情况自行决定是否终止执行。

10. 守护线程

在Java中,守护线程(Daemon Thread)是一种在后台提供服务的线程,它的存在不会阻止 JVM 退出。当所有的非守护线程结束时,JVM 会退出,不会等待守护线程执行完毕。

要将一个线程设置为守护线程,可以通过调用 setDaemon(true) 方法来实现。默认情况下,线程是非守护线程。

由于守护线程是后台线程,当主线程结束后,守护线程也会随之结束

11. 锁升级

synchronized 关键字在内部会根据竞争情况自动进行锁升级。具体来说,当一个线程尝试获取锁时,会经历以下阶段:

  1. 无锁状态(无锁): 初始状态,对象没有被锁定,任何线程都可以访问。
  2. 偏向锁(偏向锁):只有一个线程访问同步块时,锁会升级为偏向锁,这个线程将作为偏向线程持有锁。其他线程进入同步块时,不需要竞争,可以直接获取锁。这个过程是为了优化同步操作,减少无竞争情况下的开销。
  3. 轻量级锁(轻量级锁): 当多个线程尝试竞争同一个锁时,偏向锁会升级为轻量级锁。线程会尝试使用CAS(Compare and Swap)操作来获取锁,如果获取失败,则会膨胀为重量级锁。
  4. 重量级锁(重量级锁): 当多个线程竞争同一个锁并且无法获取到锁时,锁会升级为重量级锁。此时,线程会进入阻塞状态,会有较大的性能开销,因为涉及到操作系统层面的线程阻塞和唤醒。

12. 线程安全的容器

不安全:

List list = new ArrayList<>();

list.add(“”);

  • 使用线程安全的容器,如 Vector(synchronized 方法)、CopyOnWriteArrayList(ReentrantLock) 或者通过 Collections.synchronizedList()(Synchronized 代码块) 方法包装 ArrayList

    Vector list = new Vector<>();

    CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();

    List list = Collections.synchronizedList(new ArrayList<>());

  • 使用显式的同步控制,比如在访问 ArrayList 时使用 synchronized 关键字保证线程安全。

    synchronized (list) {
    list.add(Thread.currentThread().getName());
    }

  • 使用并发集合类,如 java.util.concurrent 包下的 ConcurrentHashMapConcurrentSkipListSet 等。

13. 死锁

  • 产生死锁的四个必要条件
    • 1.互斥条件:一个资源毎次只能被一个进程使用。
    • 2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    • 3.不剥夺条件∶进程已获得的资源,在末使用完之前,不能强行剥夺。
    • 4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

14. synchroized与Lock对比

  • synchronized 内置的Java关键字, Lock 是一个Java类
  • Lock是显式锁 (手动开启和关闭锁,别忘记关闭锁), synchronized是隐式锁, 出了作用域自动释放。
  • Lock只有代码块锁, synchronized有代码块锁和方法锁。
  • 使用Lock锁,JVM将花费较少的时间来调度线程, 性能更好。并且具有更好的扩展性 (提供更多的子类)。
  • 优先使用顺序:
    • Lock > 同步代码块 (已经进入了方法体,分配了相应资源 ) > )> )> 同步方法 (在方法体之外)

15. 线程通信方法

  1. 共享内存
  • 共享内存是最常见的线程间通信方式。多个线程共享同一块内存区域,它们通过读写共享内存来进行通信。在 Java 中,共享内存通常通过共享对象的方式实现,例如共享一个对象的属性或集合。
  • 通过共享内存通信时,需要确保对共享数据的访问是线程安全的,可以使用 synchronized 关键字、Lock、volatile 等机制来实现线程安全。
public class SharedMemoryExample {private static int sharedData = 0;public static void main(String[] args) {Runnable producer = () -> {synchronized (SharedMemoryExample.class) {for (int i = 0; i < 5; i++) {sharedData++;System.out.println("Producer produced: " + sharedData);}}};Runnable consumer = () -> {synchronized (SharedMemoryExample.class) {for (int i = 0; i < 5; i++) {sharedData--;System.out.println("Consumer consumed: " + sharedData);}}};new Thread(producer).start();new Thread(consumer).start();}
}
  1. 管道通信
  • 管道通信是一种基于 I/O 流的线程间通信方式。在 Java 中,可以使用 PipedInputStreamPipedOutputStream 或者 PipedWriterPipedReader 来实现管道通信。
  • 管道通信适用于在两个线程之间传输数据,其中一个线程充当数据的生产者,另一个线程充当数据的消费者。
import java.io.*;public class PipeExample {public static void main(String[] args) throws IOException {PipedInputStream inputStream = new PipedInputStream();PipedOutputStream outputStream = new PipedOutputStream();inputStream.connect(outputStream);Runnable producer = () -> {try {for (int i = 0; i < 5; i++) {outputStream.write(i);System.out.println("Producer produced: " + i);Thread.sleep(1000);}} catch (IOException | InterruptedException e) {e.printStackTrace();}};Runnable consumer = () -> {try {int data;while ((data = inputStream.read()) != -1) {System.out.println("Consumer consumed: " + data);}} catch (IOException e) {e.printStackTrace();}};new Thread(producer).start();new Thread(consumer).start();}
}
  1. wait() 和 notify() / notifyAll() 方法
  • Object 类提供了 wait()notify()notifyAll() 方法,用于在多线程环境下进行等待和通知。
  • wait() 方法使当前线程进入等待状态,并释放对象的锁,直到其他线程调用相同对象的 notify()notifyAll() 方法来唤醒等待的线程。
  • notify() 方法用于唤醒等待在该对象上的一个线程,而 notifyAll() 方法则会唤醒所有等待在该对象上的线程。
public class WaitNotifyExample {private static final Object lock = new Object();private static boolean isReady = false;public static void main(String[] args) {Runnable waiter = () -> {synchronized (lock) {while (!isReady) {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("Waiter: Received notification");}};Runnable notifier = () -> {synchronized (lock) {System.out.println("Notifier: Sending notification");isReady = true;lock.notify();}};new Thread(waiter).start();new Thread(notifier).start();}
}
  1. Condition 条件
  • java.util.concurrent.locks.Condition 接口提供了更灵活的线程通信机制,它通常与 Lock 对象一起使用。
  • 使用 Condition,可以通过调用 await() 方法使线程等待某个条件,而其他线程可以通过调用 signal()signalAll() 方法来通知等待的线程。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class ConditionExample {private static final Lock lock = new ReentrantLock();private static final Condition condition = lock.newCondition();private static boolean isReady = false;public static void main(String[] args) {Runnable waiter = () -> {lock.lock();try {while (!isReady) {try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("Waiter: Received notification");} finally {lock.unlock();}};Runnable notifier = () -> {lock.lock();try {System.out.println("Notifier: Sending notification");isReady = true;condition.signal();} finally {lock.unlock();}};new Thread(waiter).start();new Thread(notifier).start();}
}
  1. CountDownLatch、CyclicBarrier、Semaphore 等并发工具
  • Java 并发包中提供了一些并发工具类,如 CountDownLatchCyclicBarrierSemaphore 等,它们可以帮助线程协调和通信。
import java.util.concurrent.CountDownLatch;public class CountDownLatchExample {public static void main(String[] args) throws InterruptedException {CountDownLatch latch = new CountDownLatch(1);Runnable task = () -> {try {System.out.println("Task is waiting...");latch.await();System.out.println("Task is running...");} catch (InterruptedException e) {e.printStackTrace();}};new Thread(task).start();Thread.sleep(2000); // Simulating some worklatch.countDown(); // Signaling the task to start}
}

16. AQSReentrantLock

参考

狂神说笔记——多线程05 - subeiLY - 博客园

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/17291.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【详细介绍下PostgreSQL】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

Java面试八股之Synchronized锁升级的原理

Synchronized锁升级的原理 Synchronized锁升级是Java为了提高并发性能而引入的一项优化措施&#xff0c;这一机制主要发生在JDK 1.6及之后的版本中。Synchronized锁升级旨在减少锁带来的性能开销&#xff0c;通过从低开销的锁逐步升级到高开销的锁&#xff0c;以适应不同的竞争…

I/O '24|学习资源焕新,技术灵感升级

2024 年 5 月 15 日凌晨举行的 Google I/O 大会为各地的开发者们带来了新的灵感。面对技术革新&#xff0c;相信各位开发者们都迫不及待想要自己上手试一试。 别急&#xff0c;Google 谷歌今年为中国的开发者们准备了一份特别的学习资源&#xff0c;让开发者们自由探索新知。 G…

Angular安装与基础语法

安装Angular cli npm i -g angular-cli 创建项目 npm new project_name(项目名称) 启动项目 cd project_name(j进入新创建的项目) ng serve --open 创建组件 ng g c xxx 会在根组件的统计生成一个组件xxx文件夹&#xff0c;比如ng g c hello 就会生成一个hello组件文件…

夜骑,是探索城市的另一种维度,是与自我对话的静谧时光

月光下的骑行浪漫主义者 想象一下&#xff0c;当白日的喧嚣退去&#xff0c;城市的霓虹开始闪烁&#xff0c;你跨上心爱的自行车&#xff0c;迎着凉爽的晚风&#xff0c;穿梭于灯火阑珊之中。夜骑&#xff0c;不仅仅是一场运动&#xff0c;更是一种逃离日常、探索未知的冒险。但…

【Ambari】Docker 安装Ambari 大数据单机版本

目录 一、前期准备 1.1 部署 docker 1.2 部署 docker-compose 1.3 版本说明 二 、镜像构建启动 2.1 系统镜像构建 2.2 安装包源镜像构建 2.3 kdc镜像构建 2.4 集群安装 2.5 容器导出为镜像 三、Ubuntu环境安装测试 3.1 环境准备 3.2 集群容器启动 一、前期准备 1.…

如何在OrangePi AIpro智能小车上实现安全强化学习算法

随着人工智能和智能移动机器人的广泛应用&#xff0c;智能机器人的安全性和高效性问题受到了广泛关注。在实际应用中&#xff0c;智能小车需要在复杂的环境中自主导航和决策&#xff0c;这对算法的安全性和可靠性提出了很高的要求。传统的强化学习算法在处理安全约束时存在一定…

享受当下,还是留待未来?一项fMRI与眼动追踪技术的联合研究

摘要 时间贴现(temporal discount)是指个体对奖励的估计会随着时间流逝而下降的心理现象。具体而言&#xff0c;当获得奖励的时间以日期(日期条件&#xff1b;例如&#xff0c;2023年6月8日)而不是延迟(延迟条件&#xff1b;例如&#xff0c;30天)呈现时&#xff0c;贴现率较低…

免费,Python蓝桥杯等级考试真题--第14级(含答案解析和代码)

Python蓝桥杯等级考试真题–第14级 一、 选择题 答案&#xff1a;B 解析&#xff1a;键为‘B’对应的值为602&#xff0c;故答案为B。 答案&#xff1a;A 解析&#xff1a;字典的符合为花括号&#xff0c;先键后值&#xff0c;故答案为A。 答案&#xff1a;C 解析&#xff1a…

Stable Diffusion 字体和场景结合【把四季藏在海里】

把四季藏在海里&#xff0c;话不多说&#xff0c;上教程。 一、花瓣找字 打开花瓣找下字&#xff0c;也可以自己做&#xff0c;我不会&#xff0c;谢谢。本文用到的字&#xff0c;版权归原作者所有。 白底黑字&#xff1a;那么就是浪花占主要部分&#xff0c;字就是海的效果&…

【改进】YOLOv8 AND YOLOv9 总目录

说明&#xff1a;本专栏为YOLOV8和YOLOV9的使用以及改进的方法。平时比较忙&#xff0c;只能随缘回答问题哈&#xff0c;谨慎订阅&#xff01; &#x1f49b; &#x1f499; &#x1f49c; ❤️ &#x1f49a; &#x1f49b; &#x1f499; &#x1f49c; ❤️ &#x1f49a; &…

C++:STL容器的学习-->string

C:STL容器的学习-->string 1. 构造方法2. string的赋值操作3. string字符串的拼接4. string 查找和替换5. string字符串的比较6. string字符存取7. string 插入和删除8. string截取 需要添加头文件#include <string> 1. 构造方法 string() 创建空的字符串 string(c…

C++学习/复习6---内存管理(数据的位置/大小)/new、delete/内存相关面试题(malloc与new/内存泄漏)

一、内存中区域 1.不同数据的存储位置 2.数据占用内存大小 二、动态内存函数 三、new与delete 1.操作内置类型 2.操作自定义类型 四、operator new与operator delete 1.底层源码&#xff08;malloc/free&#xff09; 2.内置/自定义与构造和析构 3.举例 五、定位new表达式 1.举…

使用 Django 和 RabbitMQ 构建高效的消息队列系统

文章目录 RabbitMQ 简介Django 中使用 RabbitMQ总结与拓展 在现代的 Web 应用程序开发中&#xff0c;构建一个高效的消息队列系统变得越来越重要。使用消息队列可以帮助我们解耦系统中不同模块的任务&#xff0c;并提高系统的性能和可扩展性。本文将介绍如何结合 Django 和 Rab…

LeetCode994腐烂的橘子

题目描述 在给定的 m x n 网格 grid 中&#xff0c;每个单元格可以有以下三个值之一&#xff1a;值 0 代表空单元格&#xff1b;值 1 代表新鲜橘子&#xff1b;值 2 代表腐烂的橘子。每分钟&#xff0c;腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。返回 直到单元格中没…

C++ std::reference_wrapper:让引用更强大

std::reference_wrapper 的通俗易懂解释 一、简介二、std::reference_wrapper 的初衷三、常用示例3.1、与 make_pair 和 make_tuple 一起使用3.2、引用容器3.3、通过 std::thread 按引用传递参数给启动函数3.4、引用作为类成员3.5、按引用传递函数对象3.6、与绑定表达式一起使用…

重新安装 Windows 10/11 后如何恢复丢失的数据?

“嗨&#xff0c;我的 Windows 10/11 崩溃了&#xff0c;所以我不得不重新安装它。我使用 USB 可启动驱动器重新安装了操作系统。但是&#xff0c;重新安装后&#xff0c;C 盘上的所有先前文件都丢失了。有什么方法可以恢复丢失的文件吗&#xff1f;” - 孙雯 在大多数情况下&…

软考 系统架构设计师系列知识点之SOME/IP与DDS(3)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之SOME/IP与DDS&#xff08;2&#xff09; 本文内容参考&#xff1a; 车载以太网 - SOME/IP简介_someip-CSDN博客 https://zhuanlan.zhihu.com/p/369422441 什么是SOME/IP?_someip-CSDN博客 SOME/IP 详解系列&#…

day16--集合进阶(Set、Map集合)

day16——集合进阶&#xff08;Set、Map集合&#xff09; 一、Set系列集合 1.1 认识Set集合的特点 Set集合是属于Collection体系下的另一个分支&#xff0c;它的特点如下图所示 下面我们用代码简单演示一下&#xff0c;每一种Set集合的特点。 //Set<Integer> set ne…