每日10题 2021-01-05

本文深入探讨了Java并发编程中的核心概念,包括ReentrantLock的公平锁机制、条件变量的应用及Java内存模型(JMM)的基本特性。通过实例解析了公平锁的实现方式,条件变量在多线程间的协调作用,以及volatile关键字如何确保变量的可见性和禁止指令重排。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

公平锁

ReentrantLock 默认是不公平的,公平锁按照顺序执行
公平锁一般没有必要,会降低并发度

import java.util.concurrent.locks.ReentrantLock;

public class FairLock {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock(true);
        lock.lock();
        for (int i = 0; i < 500; i++) {
            new Thread(() -> {
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " running...");
                } finally {
                    lock.unlock();
                }
            }, "t" + i).start();
        }
// 1s 之后去争抢锁
        Thread.sleep(1000);
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " start...");
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " running...");
            } finally {
                lock.unlock();
            }
        }, "强行插入").start();
        lock.unlock();
    }
}

条件变量

synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import static java.lang.Thread.sleep;

public class ConditionTest {
    static ReentrantLock lock = new ReentrantLock();
    static Condition waitCigaretteQueue = lock.newCondition();
    static Condition waitbreakfastQueue = lock.newCondition();
    static volatile boolean hasCigrette = false;
    static volatile boolean hasBreakfast = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            try {
                lock.lock();
                while (!hasCigrette) {
                    try {
                        waitCigaretteQueue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("等到了它的烟");
            } finally {
                lock.unlock();
            }
        }).start();
        new Thread(() -> {
            try {
                lock.lock();
                while (!hasBreakfast) {
                    try {
                        waitbreakfastQueue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("等到了它的早餐");
            } finally {
                lock.unlock();
            }
        }).start();
        sleep(2000);
        sendBreakfast();
        sleep(5000);
        sendCigarette();
    }

    private static void sendCigarette() {
        lock.lock();
        try {
            System.out.println("送烟来了");
            hasCigrette = true;
            waitCigaretteQueue.signal();
        } finally {
            lock.unlock();
        }
    }

    private static void sendBreakfast() {
        lock.lock();
        try {
            System.out.println("送早餐来了");
            hasBreakfast = true;
            waitbreakfastQueue.signal();
        } finally {
            lock.unlock();
        }
    }
}

在这里插入图片描述

Java 内存模型JMM

原子性 - 保证指令不会受到线程上下文切换的影响
可见性 - 保证指令不会受 cpu 缓存的影响
有序性 - 保证指令不会受 cpu 指令并行优化的影响

可见性

main 线程对 run 变量的修改对于 t 线程不可见,导致了 t 线程无法停止

import static java.lang.Thread.sleep;

public class VisibleTest {
    static volatile boolean run = true;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while(run){
            }
            System.out.println("停下来了");
        });
        t.start();
        sleep(1000);
        run = false; // 没有volatile,线程t不会如预想的停下来
        System.out.println("main线程将run改为false");
    }
}

因为 t 线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中,减少对主存中 run 的访问,提高效率
1 秒之后,main 线程修改了 run 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值
在这里插入图片描述

volatile(易变关键字)

它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存

可见性 vs 原子性

前面例子体现的实际就是可见性,它保证的是在多个线程之间,一个线程对 volatile 变量的修改对另一个线程可见, 不能保证原子性,仅用在一个写线程,多个读线程的情况
比较一下之前我们将线程安全时举的例子:两个线程一个 i++ 一个 i-- ,只能保证看到最新值,不能解决指令交错

import static java.lang.Thread.sleep;

public class VisibleTest {
    static final Object lock = new Object();
    static boolean run = true;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            //会一直运行,并且不释放锁,导致主线程无法运行,除非主线程先执行完,释放锁
            synchronized (lock) {
                while (run) {
                }
            }
            System.out.println("停下来了");
        });
        t.start();
        sleep(100);
        synchronized (lock) {
            run = false; // 没有volatile,线程t不会如预想的停下来
            System.out.println("main线程将run改为false");
        }
    }
}

synchronized 语句块既可以保证代码块的原子性,也同时保证代码块内变量的可见性。但缺点是
synchronized 是属于重量级操作,性能相对更低

指令重排

加粗样式
可能的返回值有三种:1, 4, 0,actor2方法中会发生指令重排
线程2 执行 ready = true,切换到线程1,进入 if 分支,相加为 0,再切回线程2 执行 num = 2

volatile 修饰的变量,可以禁用指令重排,使volatile之前的命令都按照顺序执行

描述 用户行为日志表tb_user_log id uid artical_id in_time out_time sign_in 1 101 9001 2021-11-01 10:00:00 2021-11-01 10:00:31 0 2 102 9001 2021-11-01 10:00:00 2021-11-01 10:00:24 0 3 102 9002 2021-11-01 11:00:00 2021-11-01 11:00:11 0 4 101 9001 2021-11-02 10:00:00 2021-11-02 10:00:50 0 5 102 9002 2021-11-02 11:00:01 2021-11-02 11:00:24 0 (uid-用户ID, artical_id-文章ID, in_time-进入时间, out_time-离开时间, sign_in-是否签到) 场景逻辑说明:artical_id-文章ID代表用户浏览的文章的ID,artical_id-文章ID为0表示用户在非文章内容页(比如App内的列表页、活动页等)。 问:统计2021年11月每天的人均浏览文章时长(秒数),结果保留1位小数,并按时长由短到长排序。 输出示例: 示例数据的输出结果如下 dt avg_viiew_len_sec 2021-11-01 33.0 2021-11-02 36.5 解释: 11月1日有2个人浏览文章,总共浏览时长为31+24+11=66秒,人均浏览33秒; 11月2日有2个人浏览文章,总共时长为50+23=73秒,人均时长为36.5秒。以下写法:select dt, round(avg(diff),1) avg_viiew_len_sec from ( select uid, date(in_time) dt sum(timestampdiff(SECOND,in_time,out_time)) diff from tb_user_log where dt='2021-11' group by uid,dt )t group by dt order by avg_viiew_len_sec 报错程序异常退出, 请检查代码"是否有数组越界等异常"或者"是否有语法错误" SQL_ERROR_INFO: "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'sum(timestampdiff(SECOND,in_time,out_time)) diff\nfrom\n tb_user_log\nwhere dt='' at line 9"
最新发布
03-11
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值