Java并发编程知识点总结

进程和线程

进程即程序的一次执行过程,各个进程之间是独立的。线程是更小的单位,一次进程中,可能会有多个线程,可能会相互影响,各个线程有自己的程序计数器,虚拟机栈和本地方法栈,同时共同使用堆和方法区资源

线程的生命周期和状态

NEW:线程被创建出来,但是未调用start()方法

RUNNABLE:线程调用了start()方法

BLOCKED:等待锁释放

WAITING:线程调用Object.wait()、Thread.join()或LockSupport.park()方法时,进入等待状态

TIME_WAITING:线程调用Thread.sleep()、Object.wait()或Thread.join()方法时,带有超时时间参数,进入计时等待状态。

TERMINATED:当线程执行完它的任务或发生异常时,它进入终止状态。

线程上下文切换

线程执行过程时有自己的运行状态(称作上下文),如程序计数器等信息,退出cpu状态,加载另一个cpu的上下文。

发生切换时机

主动让出cpu,调用sleep()或wait()方法

时间片用完

被终止或结束运行

死锁

什么是死锁

多个线程同时阻塞,互相等待对象释放资源

死锁产生条件

互斥条件:同一个资源在同一时刻只能被一个线程占有

不可剥夺条件:一个线程持有资源时,不能被其他线程剥夺

请求与保持条件:一个线程因请求资源阻塞时,会继续占用已有的资源

循环等待条件:若干线程形成首尾相接的循环等待关系

如果避免死锁

破坏不可剥夺条件:线程可以剥夺其他线程占用的资源

破坏请求与保持条件:一次性申请所有资源

破坏循环等待条件:按序申请资源,反序释放资源

wait和sleep方法

sleep()方法不会释放对象锁。当一个线程调用sleep()方法时,它会让当前线程休眠一段时间,但是它并不会释放对象锁。其他线程仍然无法获取该对象的锁。

wait()方法会释放对象锁。当一个线程调用wait()方法时,它会让当前线程等待,并释放对象锁,以便其他线程可以获取该对象的锁并执行。

sleep是Thread类的方法,而wait是Objec类的方法.

代码示例

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

        Thread waitingThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println(LocalTime.now() +  " " + Thread.currentThread().getName()   +  " Waiting thread is running");
                try {
                    lock.wait();     // 线程等待,释放对象锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(LocalTime.now() +  " " + Thread.currentThread().getName()   +  " Waiting thread is done");
            }
        });

        Thread notifyingThread = new Thread(() -> {
            synchronized (lock) {
                try {
                    Thread.sleep(1000);   //休眠1s
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(LocalTime.now() +  " " + Thread.currentThread().getName()   +  " Notifying thread is running");
                lock.notify(); // 唤醒等待中的线程
                System.out.println(LocalTime.now() +  " " + Thread.currentThread().getName()   +  " Notifying thread is done");
            }
        });

        waitingThread.start();
        notifyingThread.start();
    }

 输出结果

15:36:44.350 Thread-0 Waiting thread is running
15:36:45.352 Thread-1 Notifying thread is running
15:36:45.352 Thread-1 Notifying thread is done
15:36:45.352 Thread-0 Waiting thread is done

Synchronized

原理

每个对象都有一个与之关联的监视器(monitor),当一个线程进入一个对象的 synchronized 方法或代码块时,它会尝试获取这个对象的内置锁。如果这个锁已经被其他线程获取了,那么当前线程将被阻塞,直到获取到这个锁为止。

当一个线程获取到对象的内置锁后,它就可以执行 synchronized 方法或代码块中的操作。在执行完这些操作后,线程会释放对象的内置锁,这样其他线程就有机会获取到这个锁,

ReentrantLock

原理

ReentrantLock 是通过 lock() 和 unlock() 方法来实现锁的获取和释放,支持可重入特性和公平性选择,同时还提供了条件变量的支持。其底层实现是基于 AQS 框架的,通过这些机制来实现多线程的同步和互斥操作。

代码案例

ReentranReadWriteLock 使用案例-CSDN博客

ReentrantReadWriteLock

读写锁,读线程共享,写线程互斥

代码案例


/**
 * ReentranReadWriteLock 使用案例
 * 读线程共享
 * 写线程互斥
 */
public class ReentrantReadWriteLockExample {
    private String news;
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public String readNews() {
        lock.readLock().lock();
        try {
            // 读线程睡眠1秒, 观察打印时间
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("hh:mm:ss")) + " is reading the news: " + news);
            return news;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.readLock().unlock();
        }
    }

    public void writeNews(String updatedNews) {
        lock.writeLock().lock();
        try {
            // 让写线程睡眠两秒,这样可以观察到写线程阻塞
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + " " +  LocalDateTime.now().format(DateTimeFormatter.ofPattern("hh:mm:ss"))  + " is updating the news to: " + updatedNews);
            news = updatedNews;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.writeLock().unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantReadWriteLockExample newsData = new ReentrantReadWriteLockExample();

        // 创建多个读线程
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                newsData.readNews();
            }).start();
        }

        // 创建多个写线程
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                newsData.writeNews("Breaking news: JavaAssistant is awesome!");
            }).start();
        }
    }
}

输出结果

Thread-3 12:33:12 is reading the news: null
Thread-5 12:33:12 is reading the news: null
Thread-6 12:33:12 is reading the news: null
Thread-2 12:33:12 is reading the news: null
Thread-7 12:33:12 is reading the news: null
Thread-1 12:33:12 is reading the news: null
Thread-8 12:33:12 is reading the news: null
Thread-0 12:33:12 is reading the news: null
Thread-9 12:33:12 is reading the news: null
Thread-4 12:33:12 is reading the news: null
Thread-11 12:33:14 is updating the news to: Breaking news: JavaAssistant is awesome!
Thread-12 12:33:16 is updating the news to: Breaking news: JavaAssistant is awesome!
Thread-10 12:33:18 is updating the news to: Breaking news: JavaAssistant is awesome!
Thread-13 12:33:20 is updating the news to: Breaking news: JavaAssistant is awesome!
Thread-14 12:33:22 is updating the news to: Breaking news: JavaAssistant is awesome!

读锁可以升级成写锁吗?

在读锁被持有的情况下,其他线程也可能在读取共享资源,如果允许读锁升级为写锁,那么可能会导致其他线程被阻塞,从而降低并发性能。因此一个线程已经持有了读锁,然后想要获取写锁,它必须先释放读锁,然后再获取写锁。

写锁是可以降级为读锁

首先,线程必须持有写锁。

然后,线程可以在持有写锁的情况下获取读锁。

最后,线程释放写锁,但保留读锁。

代码案例

public class WriteLockDowngradeExample {
    private static final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private static final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
    private static final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();

    public static void main(String[] args) {
        writeLock.lock();
        try {
            // 执行共享资源的写操作
            System.out.println("Performing write operation");
            // 降级为读锁
            readLock.lock();
        } finally {
            writeLock.unlock(); // 释放写锁
        }

        try {
            // 执行共享资源的读操作
            System.out.println("Performing read operation after downgrading");
        } finally {
            readLock.unlock(); // 释放读锁
        }
    }

}

输出结果

Performing write operation
Performing read operation after downgrading

Synchronized和ReentrantLock比较

相同点:可重入,

区别:synchronized依赖JVM,而ReetranLock依赖API;

ReentrantLock实现等待可中断,公平锁,选择通知

StampedLock

乐观读(Optimistic Reading)是 StampedLock 提供的一种特殊的读操作模式。在乐观读模式下,线程不会阻塞,它会尝试获取一个标记(stamp),然后继续执行后续操作。获取标记后,线程会使用标记去读取共享数据,但是在读取过程中,其他线程可能已经获取了写锁,因此共享数据可能已经发生了改变。

当一个线程使用乐观读取共享数据后,它需要立即验证这个数据是否有效。这个验证过程就是使用之前获取的标记去检查共享数据在读取过程中是否发生了改变。如果数据没有发生改变,那么乐观读取是成功的;如果数据发生了改变,那么乐观读取就失败了,这时候线程需要使用悲观读锁来重新读取数据。

乐观读的原理是基于乐观的假设:在大多数情况下,读操作并不会被写操作所阻塞,因此可以先尝试快速获取数据,然后在验证阶段再决定是否需要进一步的处理。这种乐观的假设可以在读多写少的场景下提供更好的性能。

需要注意的是,乐观读并不适用于所有的场景,因为在验证阶段可能需要额外的处理,而且在并发写入较多的情况下,乐观读的验证可能会失败较多次,降低了性能优势。因此,在使用乐观读时需要根据具体的业务场景和并发情况进行评估。

ThreadLocal

原理

ThreadLocal使用一个 ThreadLocalMap 数据结构来存储每个线程的独立变量副本。当我们调用 ThreadLocalset 方法设置变量的值时,实际上是在当前线程的 ThreadLocalMap 中存储了这个值;当我们调用get方法获取变量的值时,实际上是从当前线程的 ThreadLocalMap 中获取这个值。

内存泄漏原因

ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。

代码案例

public class ThreadLocalExample {
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public static void main(String[] args) {
        Runnable task = () -> {
            int value = threadLocal.get(); // 获取线程局部变量的值
            value++; // 修改线程局部变量的值
            threadLocal.set(value); // 设置线程局部变量的值
            System.out.println(Thread.currentThread().getName() + " value: " + threadLocal.get()); // 获取线程局部变量的值
        };

        Thread thread1 = new Thread(task, "Thread 1");
        Thread thread2 = new Thread(task, "Thread 2");

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

输出结果

Thread 2 value: 1
Thread 1 value: 1

Semaphore

Semaphore使用案例-CSDN博客

CountDownLatch

CountDownLatch使用案例-CSDN博客

CyclicBarrier 

CyclicBarrier使用案例-CSDN博客

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值