多线程笔记

news2024/11/10 17:04:09

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 {
        @Override
        public 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 状态时,会释放锁。

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

public class TestThreadState {
    public static void main(String[] args) throws InterruptedException {
        //线程的六种状态
        test1();
        //线程状态间的状态转换:NEW->RUNNABLE->TIME_WAITING->RUNNABLE->TERMINATED
        test2();
        //线程状态间的状态转换:NEW->RUNNABLE->WAITING->RUNNABLE->TERMINATED
        test3();
        //线程状态间的状态转换:NEW->RUNNABLE->BLOCKED->RUNNABLE->TERMINATED
        test4();
    }

    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()); // RUNNABLE
            synchronized (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 {
        @Override
        public 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{
        @Override
        public 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> {
        @Override
        public 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() {
                    @Override
                    public 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{
        @Override
        public void happyMary() {
            System.out.println("fatfish 结婚了……");
        }
    }
    
  • 静态代理:

    静态代理类:

    public class WeddingCompany implements Marry {
        private Marry target;   // 代理--->真实目标角色,帮谁结婚
    
        public WeddingCompany(Marry target){
            this.target = target;
        }
    
        @Override
        public 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;
        }
    
        @Override
        public 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;
        }
    
        @Override
        public 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 {
        @Override
        public 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("线程该停止了");
                }
            }
        }
    
        @Override
        public 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对比

  • 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 work
        latch.countDown(); // Signaling the task to start
    }
}

16. AQSReentrantLock

参考

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

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

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

相关文章

变异系数法

前言 变异系数法是一种根据统计学方法计算系统各指标变化程度的客观赋权法&#xff0c; 变异系数法在金融行业主要应用于风险评估、资产配置和绩效评价。 变异系数法是通过计算数据中包含的信息来确定各指标的权重。该方法认为&#xff0c;变化差异较大的指标应该被赋予较大的…

消息回复及时,客户不流失!这个微信自动回复设置快快码住!

你是不是也遇到过由于回复不及时&#xff0c;导致客户流失的情况发生&#xff1f;或是好友申请太多&#xff0c;来不及通过&#xff1f; 别担心&#xff0c;试试个微管理系统&#xff0c;让你实现自动回复&#xff0c;提高回复效率&#xff01; 1、自动通过好友 当有新的好友…

Qt QScript 之 C++/JavaScript相互调用

文章目录 Qt Script什么是ECMAScriptQt 中JavaScriptclass 详解Basic UsageQObject对脚本引擎可用使用信号槽connect 三种模式访问属性, 子对象使c++对象可用于用Qt Script编写的脚本C++ 类成员函数可用于脚本C++ 类属性可用于脚本对脚本中的c++对象信号的反应函数对象和本机函…

Day37 代码随想录打卡|二叉树篇---对称二叉树

题目&#xff1a; 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 方法&#xff1a;本体可以用递归和迭代两种方法&#xff0c;但我更喜欢迭代的方式&#xff0c;因此使用迭代的方式做一下。首先我们分析一下不对称的情况。因为对称的情况很简单&#xff0c;即两…

新购入的读码器该如何测试呢?

物联网技术的飞速发展&#xff0c;条码二维码作为一种高效、便捷的数据传输方式&#xff0c;已经广泛应用于仓储、物流配送、零售与结算、MES系统等生活和工业领域。新购的条码二维码读码器&#xff0c;在使用前要了解它的使用方法和性能&#xff0c;以确保其性能稳定、读取准确…

【练手项目】基于STM32的智能空调系统

项目设计说明&#xff1a; 所用到的知识点&#xff1a; GPIO、串口通信、 定时器、ADC采样、 LCD显示屏、 DHT11的通信协议。 功能概述&#xff1a; LCD显示屏&#xff1a;开机显示开启界面&#xff0c;设备自检成功后显示温湿度&#xff0c; 风机开关情况 &#xff0c;制冷片…

python数据处理与分析入门-Pandas数据可视化例子

相关内容 Matplotlib可视化练习 Pandas 数据可视化总结 柱状图 reviews[points].value_counts().sort_index().plot.bar()散点图 reviews[reviews[price] < 100].sample(100).plot.scatter(xprice, ypoints)蜂窝图 reviews[reviews[price] < 100].plot.hexbin(xprice…

Day08:CSS 高级

目标&#xff1a;掌握定位的作用及特点&#xff1b;掌握 CSS 高级技巧 一、定位 作用&#xff1a;灵活的改变盒子在网页中的位置 实现&#xff1a; 1.定位模式&#xff1a;position 2.边偏移&#xff1a;设置盒子的位置 leftrighttopbottom 水平方向偏移&#xff1a;left、…

图论(四)—最短路问题(Dijkstra)

一、最短路 概念&#xff1a;从某个点 A 到另一个点B的最短距离&#xff08;或路径&#xff09;。从点 A 到 B 可能有多条路线&#xff0c;多种距离&#xff0c;求其中最短的距离和相应路径。 最短路径分类&#xff1a; 单源最短路&#xff1a;图中的一个点到其余各点的最短路径…

成功案例(IF=7.4)| 代谢组+16s联合分析助力房颤代谢重构的潜在机制研究

研究背景 心房颤动&#xff08;AF&#xff09;是临床上最常见的持续性心律失常&#xff0c;具有显著的发病率和死亡率。高龄是房颤发病率、患病率和进展最显著的危险因素。与年龄在50-59岁之间的参与者相比&#xff0c;80-89岁之间的参与者患房颤的风险增加了9.33倍。目前尚不…

【第4章】SpringBoot整合Lombok

文章目录 前言一、准备1. 安装插件2. 引入库 二、使用1.实体类2.测试类3. 输出 总结 前言 Project Lombok是一个java库&#xff0c;它可以自动插入编辑器和构建工具&#xff0c;为您的java程序锦上添花。 再也不要写另一个getter或equals方法了&#xff0c;只要有一个注释&…

国内AI大模型的下半场-「百模大战免费篇」上线,让我们直接梦回十年前

| 我们正在经历第九次「烧钱」大战。 这个故事&#xff0c;大概要从2024年5月6号&#xff0c;一个叫DeepSeek-V2的模型开始说起。 那一天&#xff0c;DeepSeek宣布开源他们的第二代MoE大模型——DeepSeek-V2。根据披露的信息显示&#xff0c;该模型在性能上比肩GPT-4 Turbo。刚…

加仓硬核科技最好的时刻来了!

5月27日消息&#xff0c;港股三大指数V型转涨&#xff0c;恒指涨1.17%&#xff0c;科指涨1.72%&#xff0c;国指涨1.25%。板块方面&#xff0c;大型科技股走势分化&#xff0c;美团、阿里、腾讯小幅上涨&#xff0c;网易跌1.97%&#xff0c;京东跌0.67%&#xff0c;联想股价创历…

算术运算符

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 算术运算符是处理四则运算的符号&#xff0c;在数字的处理中应用得最多。常用的算术运算符如表4所示。 表4 常用的算术运算符 运 算 符 说 明…

Java:IO

首 java.io中有百万计的类&#xff0c;如何找到自己需要的部分&#xff1f; 流 IO涉及到一个“流”stream的概念&#xff0c;可以简单理解成数据从一个源头到一个目的地。明白数据从哪来&#xff0c;要到哪里去&#xff0c;数据流中是字节还是字符之后&#xff0c;才能找到自…

家政预约小程序06服务展示

目录 1 首页展示2 团购详情总结 在家政小程序中&#xff0c;最重要的信息就是各项服务的内容。顾客通过服务的信息&#xff0c;了解家政公司可以提供什么样的服务以及相关的收费。本篇我们介绍一下服务展示功能如何开发。 1 首页展示 在首页我们已经开发了活动展示、服务分类展…

来自Java的“菱形继承“,你听说过吗?

一、菱形继承的概念 菱形继承又叫做钻石继承&#xff0c;指的是不同的类同时继承自相同的父类&#xff0c;存在一个子类同时继承这些不同的类&#xff0c;即我们常说的“多继承”问题。 例如&#xff1a;B类和C类分别继承A类&#xff0c;而D类同时继承B类和C类。 如此图所示 二…

输出相关命令

什么是输入输出重定向&#xff0c;就是用另外一个位置来代替它&#xff0c;默认输入为键盘&#xff0c;默认输出为终端窗口 管道能把一系列的命令连起来&#xff0c;&#xff5c;为命令符 cat file 历史查询 history 回车可以查到用过的命令。上下左右键可以回到之前命令或…

深入理解深度学习中的激活层:Sigmoid和Softmax作为非终结层的应用

深入理解深度学习中的激活层&#xff1a;Sigmoid和Softmax作为非终结层的应用Sigmoid 和 Softmax 激活函数简介Sigmoid函数Softmax函数 Sigmoid 和 Softmax 作为非终结层多任务学习特征变换增加网络的非线性实际案例 注意事项结论 深入理解深度学习中的激活层&#xff1a;Sigmo…

【算法专题】双指针算法之 移动零

欢迎来到CILMY23的博客 &#x1f3c6;本篇主题为&#xff1a;双指针算法之移动零 &#x1f3c6;个人主页&#xff1a;CILMY23-CSDN博客 &#x1f3c6;系列专栏&#xff1a;Python | C | C语言 | 数据结构与算法 | 贪心算法 | Linux &#x1f3c6;感谢观看&#xff0c;支持的…