Java并发编程LockSupport使用实例

本文涉及的产品
文档翻译,文档翻译 1千页
文本翻译,文本翻译 100万字符
图片翻译,图片翻译 100张
简介: 最近负责的项目需要实现一个Web页面监控功能,待监控的数据需要从数据库中统计出来。本身来讲这是一个很简单的功能点,但是考虑到监控端页面会被多人同时访问的业务场景,监控数据又要求每间隔一秒刷新一次,如果每个监控界面都实时去访问数据库,那么数据库的资源开销就太大了,若在白天的业务繁忙期遇到监控端用户数较多时有可能会影响正常的交易办理。

最近负责的项目需要实现一个Web页面监控功能,待监控的数据需要从数据库中统计出来。本身来讲这是一个很简单的功能点,但是考虑到监控端页面会被多人同时访问的业务场景,监控数据又要求每间隔一秒刷新一次,如果每个监控界面都实时去访问数据库,那么数据库的资源开销就太大了,若在白天的业务繁忙期遇到监控端用户数较多时有可能会影响正常的交易办理。为了避免数据库资源过度使用的问题我的设计是在web容器后台构建一块监控数据缓存,无论前台有多少个人访问监控页面,都只是从web容器缓存中获取监控数据,web容器后台有一个值守线程X每间隔一秒访问数据库轮询监控数据至内存中,示意图如下:


img_f97cf23fe529a190361d69b923ccf5ef.png
屏幕快照 2018-07-27 下午4.50.43.png

仅仅实现以上业务流程其实也非常简单,还用不上LockSupport支持,但是本着对系统资源的最低能耗及高性能需求,我有了更进一步的优雅实现愿景,当没有User监控请求访问容器时后台值守线程可以不干活让其处于阻塞状态,当容器收到User端监控请求时后台值守线程X立即从阻塞状态转变成Running状态,为此我们需要学习运用concurrent包中的LockSupport类来控制多线程间的运行状态切换以实现需求

LockSupport学习

LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语。LockSupport很类似于二元信号量(只有1个许可证可供使用),如果这个许可还没有被占用,当前线程获取许可并继续执行;如果许可已经被占用,当前线程阻塞,等待获取许可。通过网上一些对LockSupport的源码分析可知,其实现是通过调用本地代码(C++)来做的,具有很强的OS平台相关性,因此性能应该是非常高的。对于JVM应用来说主要是通过调用LockSupport.park()和LockSupport.unpark()实现线程的阻塞和唤醒操作的,当然实现线程间的阻塞和唤醒我们还可以用到对象锁,通过Synchronizer关键字来实现对象同步锁,使用对象的wait()和notify()方法来实现,但是此方式的实现在性能上会大打折扣而且有些并发控制不当非常容易引发线程间死锁,可以说非常不优雅。

LockSupport类核心方法

基于Unsafe类中的park和unpark方法

public static void park() {
        UNSAFE.park(false, 0L);
    }
 public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }
  • park()方法,调用native方法阻塞当前线程
  • unpark()方法,唤醒处于阻塞状态的线程Thrread

LockSupport类测试Demo

如下编写一个ThreadPark类来验证park与unpark方法的成对使用

public class ThreadParkTest {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.setName("mt");
        mt.start();
        try {
            Thread.currentThread().sleep(10);
            mt.park();
            Thread.currentThread().sleep(10000);
            mt.unPark();
            Thread.currentThread().sleep(10000);
            mt.park();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    static class MyThread extends Thread {

        private boolean isPark = false;
        public void run() {
            System.out.println(" Enter Thread running.....");
            while (true) {
                if (isPark) {
                    System.out.println(Thread.currentThread().getName()+"Thread is Park.....");
                    LockSupport.park();
                }
                //do something
                System.out.println(Thread.currentThread().getName()+">> is running");
                try {
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        public void park() {
            isPark = true;
        }
        public void unPark() {
            isPark = false;
            LockSupport.unpark(this);
            System.out.println("Thread is unpark.....");
        }
    }
}

程序运行输出:

Enter Thread running.....
mt>> is running
mt>> is running
mt>> is running
mt>> is running
mt>> is running
mtThread is Park.....
Thread is unpark.....
mt>> is running
mt>> is running
mt>> is running
mt>> is running
mt>> is running
mt>> is running
mt>> is running
mt>> is running
mt>> is running
mt>> is running
mtThread is Park

park翻译过来即是停车的意思,我们可以这样理解,每个被应用程序启动的线程就是一辆在计算机总线赛道上奔驰着的跑车,当你想让某台车停下来休息会时那么就给它一个park信号,它就会立即停到赛道旁边的停车位中,当你想让它从停车位中驶出并继续在赛道上奔跑时再给它一个unpark信号即可

LockSupport的业务实际应用

我们对技术基础知识的掌握是为了更好,更优雅,更从容的实现业务需求,以最小的程序代价来实现业务最大收益化是计算机软件工程的永恒追求主题之一。 差不多给自己埋好坑了(围笑),不扯淡了,还是show me the code吧。
回到第一章的监控业务需求,首先我们需要编写后台值守线程X类,Daemon线程类Run()方法中除实现从数据库中加载监控数据到内存之外还必须实现具备满足一定条件时调用park()方法线程自动停车,同时对外要提供unpack()方法用于外部唤醒线程


后台值守线程MonitorWorkThread类代码编写:

class MonitorWorkThread extends Thread
    {
       //当前线程停车标志
        private volatile boolean isPark = false;
        
        //工作线程默认一秒钟加载一次,count即为工作监控线程每一次unpack之后会继续工作的时间,此值可根据实际需求配置化
    private int maxWorkCount = 300;
        
        @Override
        public void run() 
        {
            int indexCount=0;
            logger.info("成功启动审核任务监控工作线程,当前工作线程每次unpack连续工作的时间设定为"+maxWorkCount+"秒");
            while(true)
            {
                
                if(indexCount >= maxWorkCount)
                {
                    logger.info("当前监控工作线程已到达连续工作时间设定上限,现在进入pack休眠状态");
                    isPark =true;
                    indexCount=0;
                    LockSupport.park();
                }
                //从数据库中加载数据至内存
                try 
                {
                    loadDataFromDB();
                } catch (Exception e1) 
                {
                    logger.warn("从数据库中加载监控数据至内存发生异常", e1);
                }
                try 
                {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) 
                {
                    logger.warn("工作线程被异常中断唤醒", e);
                }
                indexCount++;
            }
        }
        /**
         * 假如当前线程正在运行状态,donothing
         */
        public void unPack()
        {
            if(isPark)
            {
                //唤醒当前监控工作线程,此处有并发唤醒动作需加锁
                synchronized (this)
                {
                     isPark = false;
                     LockSupport.unpark(this);
                     logger.info("当前监控工作线程已被唤醒");
                }
            }
        }
        
    }

接下来我们考虑编写监控业务实现类,大体思路是我们先需要定义内存缓存map用于装载数据库监控数据,业务实现类在容器实例化时自动启动上面的值守线程MonitorWorkThread,对外提供一个获取内存数据的公共方法,公共方法体中需要调用值守线程的unPack()方法以实现当容器收到客户端监控访问请求时若后台异步值守线程处于停车(阻塞)状态时,会被唤醒继续奔跑上路。
任务监控业务实现类TaskMonitorServiceImpl编写:

/**
 * 
 * @author lyp
 *  审核任务监控实现类
 */
@Service("TaskMonitorServiceImpl")
public class TaskMonitorServiceImpl 
{
    private static Logger logger= Logger.getLogger(TaskMonitorServiceImpl.class);
    
    //总任务状态缓存表
    private List<TaskStatusBean> totalStatus = new ArrayList<TaskStatusBean>();
    //审核柜员任务处理缓存表
    private Map<String,UserTaskCountDto> userTaskMap = new ConcurrentHashMap<String, UserTaskCountDto>();
    //监控工作线程引用
    private static MonitorWorkThread workThread=null;
    
    @PostConstruct
    private void initalizal()
    {
        //实例化之后执行的初始化动作,用于启动值守监控线程来刷新加载数据
        workThread = new MonitorWorkThread();
        workThread.setDaemon(true);
        workThread.setName("AuthTaskMonitor");
        workThread.start();
    }

    /**
     * 此为对外提供方法用于外部根据监控用户号获取内存中缓存的监控数据
     * @param userno           监控用户号
     * @return map key1:totalStatus key2:userno
     * @throws Exception
     */
    public Map<String,Object> monitorDataByUser(String userno) throws Exception
    {
        if(null == userno || "".equals(userno))
        {
            return null;
        }
        Map<String,Object> retMap = new HashMap<String, Object>();
        if(null !=workThread)
        {
           //每次请求都去看看异步值守线程是否需求唤醒
            workThread.unPack();
        }
        retMap.put("totalStatus", totalStatus);
        if(userTaskMap.containsKey(userno))
        {
            retMap.put(userno, userTaskMap.get(userno));
        }else
        {
            UserTaskCountDto dto =  new UserTaskCountDto(userno);
            retMap.put(userno, dto);
        }
        return retMap;
    }
    
    /**
     * 从数据库中加载内存数据至内存
     */
    private void loadDataFromDB () throws Exception
    {
        
        logger.info("开始从数据库中加载任务监控数据...");
        //do something about business....
        
        logger.info("从数据库中加载任务监控数据完毕...");
    }
    
    
    /**
     * 清理监控缓存数据map 
     */
    public void clearMonitorCache()
    {
        this.totalStatus.clear();
        this.userTaskMap.clear();
    }
}

写在最后

技术知识的学习本身就是枯燥无味的,靠解决问题的动力来驱动技术知识的掌握未尝不是一个值得尝试的高效学习方法。以上是我第一次在简书书写文章,选择加入简书的原因其实很简单,一是看美剧的时候被大量广告植入,二是简书的编辑器完美支持MarkDown语言写作。其实这也是我第一次使用MarkDown标记语言写作排版,MarkDown的写作方式对于程序员来说真的是太爽了,啊啊啊。
MarkDown语言

Markdown is intended to be as easy-to-read and easy-to-write as is feasible.
Readability, however, is emphasized above all else. A Markdown-formatted document should be publishable as-is, as plain text, without looking like it's been marked up with tags or formatting instructions.
Markdown's syntax is intended for one purpose: to be used as a format for writing for the web

写作不易,看完本文如果你觉得对你的工作生活有帮助请给个赞赏,不在乎多少,这会给予我写作无限的动力。
最后如果你需要转载此文,请标明原创出处,谢谢。

目录
相关文章
|
22天前
|
设计模式 安全 Java
Java编程中的单例模式:理解与实践
【10月更文挑战第31天】在Java的世界里,单例模式是一种优雅的解决方案,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的实现方式、使用场景及其优缺点,同时提供代码示例以加深理解。无论你是Java新手还是有经验的开发者,掌握单例模式都将是你技能库中的宝贵财富。
30 2
|
11天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
17天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
24天前
|
Java API Apache
Java编程如何读取Word文档里的Excel表格,并在保存文本内容时保留表格的样式?
【10月更文挑战第29天】Java编程如何读取Word文档里的Excel表格,并在保存文本内容时保留表格的样式?
101 5
|
19天前
|
安全 Java 编译器
JDK 10中的局部变量类型推断:Java编程的简化与革新
JDK 10引入的局部变量类型推断通过`var`关键字简化了代码编写,提高了可读性。编译器根据初始化表达式自动推断变量类型,减少了冗长的类型声明。虽然带来了诸多优点,但也有一些限制,如只能用于局部变量声明,并需立即初始化。这一特性使Java更接近动态类型语言,增强了灵活性和易用性。
97 53
|
10天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
9天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
12天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
18天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
15天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin