java初阶多线程4

1.  synchronized 关键字

 

- 基本用法和特点: synchronized 用于实现线程同步,保证同一时刻只有一个线程能访问被同步的代码块或方法。它可修饰方法和代码块,如 synchronized (锁对象) { }  ,锁对象可以是任意对象。

- 可重入性:指的是同一个线程在持有锁的情况下,再次进入被该锁同步的代码块或方法是允许的。例如,一个递归方法被 synchronized 修饰,在递归调用过程中,线程不需要重新获取锁。

- 死锁问题

- 死锁场景举例

- 一个线程连续两次尝试获取同一把锁,会导致死锁。

- 两个线程分别持有一把锁,并且都尝试获取对方持有的锁,比如线程1持有锁a,尝试获取锁b;线程2持有锁b,尝试获取锁a ,就会陷入死锁。

-  M 个线程竞争 N 把锁( M > N  ),也可能出现死锁。经典的哲学家就餐问题就是此类场景,多个哲学家(线程)竞争有限的筷子(锁)。

- 死锁的必要条件:互斥、不可抢占、请求和保持、循环等待 。只有当这四个条件同时满足时,才会发生死锁。

 

2.  volatile 关键字

 

- 作用:主要解决内存可见性引起的线程安全问题。当一个变量被声明为 volatile  ,它会禁止编译器对该变量相关操作的优化,保证不同线程对该变量的读取是最新值,而不是从各自的缓存中读取旧值。

- 举例:假设有一个共享变量 boolean flag = false  ,线程A修改 flag 为 true  ,如果没有 volatile 修饰,线程B可能无法及时感知到 flag 的变化,继续使用旧值。使用 volatile 修饰 flag 后,就能保证线程B能及时看到线程A对 flag 的修改。

 

3.  wait 和 notify 方法

 

- 所属类及原理: wait 和 notify 是 Object 类的方法,用于协调线程执行顺序,避免线程饿死。 wait 方法使当前线程进入等待状态,并且会释放持有的锁 ; notify 方法用于唤醒在同一个对象上等待的某个线程, notifyAll 则唤醒所有等待的线程。

- 使用示例

 

public class WaitNotifyExample {

    private static final Object lock = new Object();

    public static void main(String[] args) {

        Thread thread1 = new Thread(() -> {

            synchronized (lock) {

                try {

                    System.out.println("线程1开始等待");

                    lock.wait();

                    System.out.println("线程1被唤醒");

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

        });

 

        Thread thread2 = new Thread(() -> {

            synchronized (lock) {

                System.out.println("线程2准备唤醒其他线程");

                lock.notify();

            }

        });

 

        thread1.start();

        try {

            Thread.sleep(1000);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        thread2.start();

    }

}

 

 

- 注意事项: wait 方法必须在 synchronized 代码块内使用,否则会抛出 IllegalMonitorStateException 异常 。因为 wait 方法内部做的第一件事就是释放锁,所以需要先持有锁才能释放。

 

4. 单例模式

 

- 饿汉式单例

- 实现原理:在类加载时就创建实例,天生是线程安全的。

- 代码示例

 

public class Singleton {

    private static final Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {

        return instance;

    }

}

 

 

- 懒汉式单例

- 普通懒汉式(线程不安全):在第一次调用 getInstance 方法时创建实例,但在多线程环境下可能创建多个实例。

- 代码示例

 

public class SingletonLazy {

    private static SingletonLazy instance;

    private SingletonLazy() {}

    public static SingletonLazy getInstance() {

        if (instance == null) {

            instance = new SingletonLazy();

        }

        return instance;

    }

}

 

 

- 线程安全的懒汉式(双重检查锁定):通过双重检查和 synchronized 关键字保证线程安全,同时延迟实例创建。

- 代码示例

 

public class SingletonLazySafe {

    private static volatile SingletonLazySafe instance;

    private static final Object locker = new Object();

    private SingletonLazySafe() {}

    public static SingletonLazySafe getInstance() {

        if (instance == null) {

            synchronized (locker) {

                if (instance == null) {

                    instance = new SingletonLazySafe();

                }

            }

        }

        return instance;

    }

}

 

 

- 加入 volatile 的原因:防止指令重排。在创建对象时,可能会出现指令重排情况,导致还未完成对象初始化就被其他线程获取到,使用 volatile 修饰 instance 可以禁止指令重排,保证对象初始化的完整性和可见性。

 

5. 指令重排序

 

- 概念:是编译器优化的一种手段,在确保逻辑一致的前提下,调整代码的顺序以提高效率。但在多线程环境下,指令重排序可能会导致线程安全问题。

- 举例:创建对象的操作可以抽象为申请内存空间、在内存空间上进行初始化(构造方法)、将内存地址保存到引用变量中这三个步骤。指令重排序可能会使顺序变为1 - 3 - 2 ,如果此时另一个线程判断对象引用不为空就直接使用,而对象还未完成初始化,就会出错。在单例模式的双重检查锁定实现中,使用 volatile 修饰实例变量就是为了防止指令重排序带来的问题。

 

这些知识点在Java多线程编程和设计模式中非常关键,理解并正确应用它们能有效避免多线程环境下的各种问题,设计出高效、安全的程序。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值