十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java多线程初阶(下)—— synchronized、volatile、wait/notify与经典并发工具

Java多线程初阶(下)—— synchronized、volatile、wait/notify与经典并发工具 Java多线程初阶下—— synchronized、volatile、wait/notify与经典并发工具一、解决线程不安全的神器synchronized监视器锁synchronized能保证原子性和可见性。1. 核心特性互斥与可重入互斥当线程A进入synchronized代码块相当于加锁其他试图进入该锁保护的代码块的线程如线程B必须阻塞等待直到A执行完毕释放锁解锁。可重入同一个线程可以多次获取同一把锁不会把自己锁死。synchronized内部记录了“锁的持有者”和“计数器”每次重入计数器1完全退出时计数器归零才真正释放锁。synchronized(locker){synchronized(locker){// 同一个线程可以再次进入不会死锁count;}}2. 三种使用方式修饰代码块需要手动指定锁对象synchronized(locker) {}。修饰普通方法锁的是当前实例对象this。修饰静态方法锁的是当前类的Class对象全局锁。二、保证可见性的轻量级方案volatile作用被volatile修饰的变量读写时强制绕过工作内存直接操作主内存。保证一个线程修改后其他线程立刻可见。误区volatile不保证原子性。比如volatile int count两个线程执行count依然会数据错乱因为“读-改-写”不是原子操作。它适合作为标志位如boolean isQuit使用。三、线程间的协作wait() 与 notify()synchronized解决了竞争问题但有时我们需要线程间主动协调顺序如线程A传完球线程B才能扣篮。wait/notify定义在Object类中必须配合synchronized使用。1. wait() 做了什么释放当前对象锁。让当前线程进入等待队列WAITING状态。直到被notify唤醒或超时才重新竞争锁。2. notify() 与 notifyAll()notify()随机唤醒一个在该对象上等待的线程。notifyAll()唤醒所有等待的线程它们再去竞争锁。经典生产者消费者模型示例伪代码synchronized(lock){while(队列已满){lock.wait();}// 生产者等待生产数据;lock.notifyAll();// 唤醒消费者}synchronized(lock){while(队列为空){lock.wait();}// 消费者等待消费数据;lock.notifyAll();// 唤醒生产者}注意wait必须在while循环中调用因为被唤醒后不一定满足条件可能又被别的线程抢占了资源。wait 与 sleep 的区别高频面试题wait属于Object必须配合synchronized会释放锁sleep属于Thread不涉及锁释放。wait需要被显式唤醒sleep到时间自动醒来。四、必会的并发编程案例1. 单例模式双重检查锁 DCL懒汉模式单线程版线程不安全多线程版加上synchronized后虽然安全但效率低。最终优化版面试必考classSingleton{privatestaticvolatileSingletoninstancenull;privateSingleton(){}publicstaticSingletongetInstance(){if(instancenull){// 第一次检查避免每次都加锁synchronized(Singleton.class){if(instancenull){// 第二次检查防止多线程同时创建instancenewSingleton();}}}returninstance;}}为什么加volatile因为new Singleton()这一步可能会发生指令重排序分配内存、初始化、赋值加上volatile禁止重排序保证其他线程看到的是完整的对象。2. 阻塞队列BlockingQueueJava标准库提供了LinkedBlockingQueue、ArrayBlockingQueue。核心方法put(e)队列满则阻塞等待。take()队列空则阻塞等待。利用阻塞队列可以轻松实现“生产者-消费者模型”起到削峰填谷和解耦的作用。3. 定时器TimerTimertimernewTimer();timer.schedule(newTimerTask(){Overridepublicvoidrun(){System.out.println(3秒后执行);}},3000);原理内部维护一个优先级队列按执行时间排序后台线程不断扫描队首元素判断当前时间是否达到执行时间。4. 线程池ThreadPoolExecutor频繁创建/销毁线程开销大线程池复用线程。标准库创建Executors.newFixedThreadPool(10)固定数量等。核心参数线程池7大参数corePoolSize核心线程数正式工。maximumPoolSize最大线程数正式工 临时工。keepAliveTime临时工空闲多久会被辞退。workQueue任务阻塞队列。handler拒绝策略当队列满且线程数达到最大时执行。自定义线程池核心逻辑就是维护一个阻塞队列存放任务多个Worker线程不断从队列取任务执行。总结多线程编程的核心是访问共享资源时保证线程安全。优先考虑volatile轻量级标志位其次使用synchronized保证原子性线程间协调使用wait/notify最后不要重复造轮子多用标准库提供的线程池、阻塞队列等组件。
返回列表