java-07 多线程-并发编程(synchronized、原子变量、ThreadLocal、CountDownLatch、Future和CompletableFuture、volatile、线程池)

  并发编程是指在一个程序中同时执行多个任务或线程。这通常涉及到多线程编程、线程同步、并发容器等技术。这些技术可以用来解决多线程环境中的问题,如线程安全、资源竞争、死锁等问题。在实际的Java并发编程中,还需要考虑到线程池、Future、Callable、ExecutorService等概念。

在另一篇文章介绍了多线程、进程、并发、并行等基本概念,并分析了线程安全问题产生的原因,同时也整理了线程实现的4种方式,并做了对比,请参考 java-06 多线程-4种实现方式

如果你觉得我分享的内容或者我的努力对你有帮助,或者你只是想表达对我的支持和鼓励,请考虑给我点赞、评论、收藏。您的鼓励是我前进的动力,让我感到非常感激。

1 线程同步

1.1 锁

StampedLock 在某些场景下可以提供更好的并发性能,但也需要注意合理的使用,以避免过于复杂的代码结构和潜在的死锁情况。

synchronized 和 ReentrantLock 都是基于悲观锁思想实现的,意味着它们假定在执行临界区代码期间会发生并发冲突。在高并发场景下,激烈的锁竞争可能会导致线程阻塞,从而降低性能。特别是在多读场景下,悲观锁可能引入大量的额外并发开销,因为每个读操作都需要获得独占锁。

相比之下,StampedLock 的乐观锁思想更适合多读场景。乐观锁假定数据操作不存在并发冲突,因此不会引起锁竞争,也不会导致线程阻塞和死锁。乐观锁通常在提交修改时才验证资源是否被其他线程修改。不过在多写场景下乐观锁会频繁失败和重试,这同样会对性能造成一定影响。

1.1.1 synchronized

synchronized 是 Java 中用于实现线程同步的关键字,它主要用于创建同步代码块或同步方法,以确保在多线程环境下对共享资源的访问是安全的。通过使用 synchronized 可以避免多个线程同时访问共享资源而引发的并发问题,如竞态条件和数据不一致等。

1.1.1.1 同步代码块

通过在代码块内使用 synchronized 关键字来创建同步代码块,它可以用来保护代码块,确保在同一时刻只有一个线程能够进入同步代码块。一个典型的用法是将需要同步的代码放在同步代码块中,并指定一个锁对象作为同步的依据。

虽然任意一个唯一的对象(比如一个字符串)都可以作为同步代码块的锁对象,但锁的粒度过大会导致并发安全问题,粒度过小会导致性能下降。类比同步方法,一般情况下,对于实例方法,通常使用 this 作为锁对象,对于静态方法,通常使用类的字节码对象 类名.class 作为锁对象。

同步代码块的基本使用示例:

    private static int counter;
    private static final Object lock = new Object();

    public static void main(String[] args) {
        Runnable incrementTask = () -> {
            for (int i = 0; i < 10000; i++) {
                // 同步代码块
                synchronized (lock) {
                    counter++;
                }
            }
        };

        Thread thread1 = new Thread(incrementTask);
        Thread thread2 = new Thread(incrementTask);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final counter value: " + counter);
    }

此外,在同步代码块中还可以通过 wait() 方法让当前线程进入等待状态,直到其他线程调用相同对象的 notify() 或 notifyAll() 方法来唤醒它。这三个方法的实现也同样依赖于 monitor 机制,因此需要被绑定到指定的锁对象上。

wait() 和 notify() 的基本使用示例:

    public static void main(String[] args) {
        final Object lock = new Object();

        // 等待线程
        Thread waiter = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Waiter: Waiting for a notification...");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Waiter: Got a notification!");
            }
        });

        // 通知线程
        Thread notifier = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Notifier: Performing some work...");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Notifier: Work done, notifying the waiter...");
                lock.notify();
            }
        });

        waiter.start();
        notifier.start();
    }

在这里插入图片描述

1.1.1.2 同步方法

通过在方法定义处使用 synchronized 关键字来创建同步方法,它可以将整个方法体都变成一个同步代码块。同步方法底层通过隐式锁对象实现,只是锁的范围是整个方法代码。如果方法是实例方法,同步方法默认用 this 作为的锁对象。如果方法是静态方法,同步方法默认用 类名.class 作为的锁对象。

同步方法的优点是简单,可以很方便地实现线程同步。不过锁的范围较大,可能影响性能,因为其他不需要同步的代码也会被锁住。

同步方法的基本使用示例:

public class Test {
    private static int counter;

    public static synchronized void increment() {
        for (int i = 0; i < 10000; i++) {
            counter++;
        }
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(Test::increment);
        Thread thread2 = new Thread(Test::increment);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final counter value: " + counter);
    }
}

1.1.2 ReentrantLock

ReentrantLock 是 Java 提供的一个可重入锁,默认为非公平锁,它相比于使用 synchronized 关键字具有更大的灵活性。通过 ReentrantLock,你可以显式地获取锁和释放锁,从而精确地控制同步范围。

ReentrantLock 提供了更多的功能,比如可重入性、可定时的锁等待、公平性设置等。但需要注意,使用 ReentrantLock 需要手动释放锁,因此务必在 finally 块中释放锁,以防止死锁情况的发生。

ReentrantLock 的基本使用示例:

public class Test {
    private static final Lock lock = new ReentrantLock();
    private static int counter;

    public static void main(String[] args) {
        Runnable incrementTask = () -> {
            for (int i = 0; i < 10000; i++) {
                lock.lock();
                try {
                    counter++;
                    System.out.println(Thread.currentThread().getName());
                } finally {
                    // 放在finally块中保证锁一定能被释放
                    lock.unlock();
                }
            }
        };

        Thread thread1 = new Thread(incrementTask);
        Thread thread2 = new Thread(incrementTask);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final counter value: " + counter);
    }
}

1.1.3 StampedLock

StampedLock 是 Java 提供的一个支持乐观读、悲观读和写操作的锁机制。它在 Java 8 中引入,通过使用乐观读锁来提供更高的并发性,同时支持升级为悲观读锁或写锁。不过它不可重入且不支持条件变量 Conditon。

StampedLock 提供了三种读写控制模式:

  1. 乐观读锁:乐观读锁是一种无锁操作,它假设没有写操作会发生。线程可以直接读取数据而无需获取锁,读取完成后通过校验版本信息来判断数据是否有效。如果数据有效,操作成功;如果数据无效,需要尝试其他方式来获取锁。乐观读锁适用于读多写少的场景。
  2. 悲观读锁:悲观读锁是常规的读锁,它会阻塞写操作,但不会阻塞其他读操作。悲观读锁适用于读多写多的场景,可以保证读操作之间的数据一致性。
  3. 写锁:写锁会阻塞其他的读操作和写操作,用于保护共享资源的写操作。

StampedLock 的基本使用示例:

public class Main {
    private static final StampedLock lock = new StampedLock();
    private static int counter;

    public static void main(String[] args) {
        Runnable incrementTask = () -> {
            for (int i = 0; i < 10000; i++) {
                long stamp = lock.writeLock(); // 获取写锁
                try {
                    counter++;
                } finally {
                    lock.unlockWrite(stamp); // 释放写锁
                }
            }
        };

        Thread thread1 = new Thread(incrementTask);
        Thread thread2 = new Thread(incrementTask);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final counter value: " + counter);
    }
}

1.2 原子变量

java-07 多线程-并发编程(原子变量、CountDownLatch、Future和CompletableFuture、volatile)

1.3 ThreadLocal

java-07 多线程-并发编程(ThreadLocal)

1.4 CountDownLatch

java-07 多线程-并发编程(原子变量、CountDownLatch、Future和CompletableFuture、volatile)

1.5 Future 和 CompletableFuture

java-07 多线程-并发编程(原子变量、CountDownLatch、Future和CompletableFuture、volatile)

1.6 volatile

java-07 多线程-并发编程(原子变量、CountDownLatch、Future和CompletableFuture、volatile)

2 线程池

java-07 多线程-并发编程(线程池,线程状态)

3 线程状态

java-07 多线程-并发编程(线程池,线程状态)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值