多线程笔记

我的相关文章:

JavaSE 学习记录-CSDN博客

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

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

  • 但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对比

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

16. 两次 start() 同一个线程,产生java.lang.IllegalThreadStateException

原因:

threadStatus = 0;表示线程状态为 NEW,start() 只能在线程状态为 NEW 的时候调用

源代码:

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

17. AQSReentrantLock

参考

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

遇见狂神说的个人空间-遇见狂神说个人主页-哔哩哔哩视频

  • 22
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值