Java线程安全的计数器简单实现代码示例

前几天工作中一段业务代码需要一个变量每天从1开始递增。为此自己简单的封装了一个线程安全的计数器,可以让一个变量每天从1开始递增。当然了,如果项目在运行中发生重启,即便日期还是当天,还是会从1开始重新计数。所以把计数器的值存储在数据库中会更靠谱,不过这不影响这段代码的价值,现在贴出来,供有需要的人参考。

package com.hikvision.cms.rvs.common.util;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/**
 * Created by lihong10 on 2017/8/9.
 * 一个循环计数器,每天从1开始计数,隔天重置为1。
 * 可以创建一个该类的全局对象,然后每次使用时候调用其get方法即可,可以保证线程安全性
 */
public class CircularCounter {
  private static final AtomicReferenceFieldUpdater<CircularCounter, AtomicInteger> valueUpdater =
      AtomicReferenceFieldUpdater.newUpdater(CircularCounter.class, AtomicInteger.class, "value");
  //保证内存可见性
  private volatile String key;
  //保证内存可见性
  private volatile AtomicInteger value;
  private static final String DATE_PATTERN = "yyyy-MM-dd";
  public CircularCounter() {
    /**
     * 这里将key设置为getCurrentDateString() + "sssssssssss" 是为了测试addAndGet()方法中日期发生变化的情况
     * 正常使用应该将key初始化为getCurrentDateString()
     */
    this.key = getCurrentDateString() + "sssssssssss";
    this.value = new AtomicInteger(0);
  }
  /**
   * 获取计数器加1以后的值
   *
   * @return
   */
  public Integer addAndGet() {
    AtomicInteger oldValue = value;
    AtomicInteger newInteger = new AtomicInteger(0);
    int newVal = -1;
    String newDateStr = getCurrentDateString();
    //日期一致,计数器加1后返回
    if (isDateEquals(newDateStr)) {
      newVal = add(1);
      return newVal;
    }
    //日期不一致,保证有一个线程重置技术器
    reSet(oldValue, newInteger, newDateStr);
    this.key = newDateStr;
    //重置后加1返回
    newVal = add(1);
    return newVal;
  }
  /**
   * 获取计数器的当前值
   * @return
   */
  public Integer get() {
    return value.get();
  }
  /**
   * 判断当前日期与老的日期(也即key成员变量记录的值)是否一致
   *
   * @return
   */
  private boolean isDateEquals(String newDateStr) {
    String oldDateStr = key;
    if (!isBlank(oldDateStr) && oldDateStr.equals(newDateStr)) {
      return true;
    }
    return false;
  }
  /**
   * 如果日期发生变化,重置计数器,也即将key设置为当前日期,并将value重置为0,重置后才能接着累加,
   */
  private void reSet(AtomicInteger oldValue, AtomicInteger newValue, String newDateStr) {
    if(valueUpdater.compareAndSet(this, oldValue, newValue)) {
      System.out.println("线程" + Thread.currentThread().getName() + "发现日期发生变化");
    }
  }
  /**
   * 获取当前日期字符串
   *
   * @return
   */
  private String getCurrentDateString() {
    Date date = new Date();
    String newDateStr = new SimpleDateFormat(DATE_PATTERN).format(date);
    return newDateStr;
  }
  /**
   * 计数器的值加1。采用CAS保证线程安全性
   *
   * @param increment
   */
  private int add(int increment) {
    return value.addAndGet(increment);
  }
  public static boolean isBlank(CharSequence cs) {
    int strLen;
    if(cs != null && (strLen = cs.length()) != 0) {
      for(int i = 0; i < strLen; ++i) {
        if(!Character.isWhitespace(cs.charAt(i))) {
          return false;
        }
      }
      return true;
    } else {
      return true;
    }
  }
  public static void test() {
    CircularCounter c = new CircularCounter();
    AtomicInteger count = new AtomicInteger(0);
    List<Thread> li = new ArrayList<Thread>();
    int size = 10;
    CountDownLatch latch1 = new CountDownLatch(1);
    CountDownLatch latch2 = new CountDownLatch(size);
    for (int i = 0; i < size; i++) {
      Thread t = new Thread(new CounterRunner(c, latch1, latch2, count), "thread-" + i);
      li.add(t);
      t.start();
    }
    System.out.println("start");
    latch1.countDown();
    try {
      latch2.await();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println(count.get());
    System.out.println(c.get());
    if(count.get() == c.get()) {
      System.out.println("该计数器是线程安全的!!!");
    }
  }
  public static void main(String... args) {
    for(int i = 0; i < 15; i++) {
      test();
    }
  }
}
/**
 * 测试使用的Runnable对象
 */
class CounterRunner implements Runnable {
  private CircularCounter counter;
  private CountDownLatch latch1;
  private CountDownLatch latch2;
  private AtomicInteger count;
  public CounterRunner(CircularCounter counter, CountDownLatch latch1, CountDownLatch latch2, AtomicInteger count) {
    this.latch1 = latch1;
    this.latch2 = latch2;
    this.counter = counter;
    this.count = count;
  }
  @Override
  public void run() {
    try {
      latch1.await();
      System.out.println("****************");
      for (int i = 0; i < 20; i++) {
        counter.addAndGet();
        count.addAndGet(1);
      }
      latch2.countDown();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

总结

以上就是本文关于Java线程安全的计数器简单实现代码示例的内容,希望对大家有所帮助,感兴趣的朋友可以参阅:Java线程安全基础概念解析、详解java各种集合的线程安全 、Java线程安全与非线程安全解析等。有什么问题可以随时留言,欢迎大家一起交流讨论。感谢朋友们对我们网站的支持!

(0)

相关推荐

  • Java 高并发三:Java内存模型和线程安全详解

    网上很多资料在描述Java内存模型的时候,都会介绍有一个主存,然后每个工作线程有自己的工作内存.数据在主存中会有一份,在工作内存中也有一份.工作内存和主存之间会有各种原子操作去进行同步. 下图来源于这篇Blog 但是由于Java版本的不断演变,内存模型也进行了改变.本文只讲述Java内存模型的一些特性,无论是新的内存模型还是旧的内存模型,在明白了这些特性以后,看起来也会更加清晰. 1. 原子性 原子性是指一个操作是不可中断的.即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰

  • 实例解析Java中的synchronized关键字与线程安全问题

    首先来回顾一下synchronized的基本使用: synchronized代码块,被修饰的代码成为同步语句块,其作用的范围是调用这个代码块的对象,我们在用synchronized关键字的时候,能缩小代码段的范围就尽量缩小,能在代码段上加同步就不要再整个方法上加同步.这叫减小锁的粒度,使代码更大程度的并发. synchronized方法,被修饰的方法成为同步方法,其作用范围是整个方法,作用对象是调用这个方法的对象. synchronized静态方法,修饰一个static静态方法,其作用范围是整个

  • java中volatile不能保证线程安全(实例讲解)

    今天打了打代码研究了一下java的volatile关键字到底能不能保证线程安全,经过实践,volatile是不能保证线程安全的,它只是保证了数据的可见性,不会再缓存,每个线程都是从主存中读到的数据,而不是从缓存中读取的数据,附上代码如下,当synchronized去掉的时候,每个线程的结果是乱的,加上的时候结果才是正确的. /** * * 类简要描述 * * <p> * 类详细描述 * </p> * * @author think * */ public class Volatil

  • Java线程安全与非线程安全解析

    ArrayList和Vector有什么区别?HashMap和HashTable有什么区别?StringBuilder和StringBuffer有什么区别?这些都是Java面试中常见的基础问题.面对这样的问题,回答是:ArrayList是非线程安全的,Vector是线程安全的:HashMap是非线程安全的,HashTable是线程安全的:StringBuilder是非线程安全的,StringBuffer是线程安全的.因为这是昨晚刚背的<Java面试题大全>上面写的.此时如果继续问:什么是线程安全

  • Java 集合中的类关于线程安全

    Java集合中那些类是线程安全的 线程安全类 在集合框架中,有些类是线程安全的,这些都是jdk1.1中的出现的.在jdk1.2之后,就出现许许多多非线程安全的类. 下面是这些线程安全的同步的类: vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用.在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的. statck:堆栈类,先进后出 hashtable:就比hashmap多了个线程安全 enumeration:枚举,相当于迭代器

  • Java线程安全的计数器简单实现代码示例

    前几天工作中一段业务代码需要一个变量每天从1开始递增.为此自己简单的封装了一个线程安全的计数器,可以让一个变量每天从1开始递增.当然了,如果项目在运行中发生重启,即便日期还是当天,还是会从1开始重新计数.所以把计数器的值存储在数据库中会更靠谱,不过这不影响这段代码的价值,现在贴出来,供有需要的人参考. package com.hikvision.cms.rvs.common.util; import java.text.SimpleDateFormat; import java.util.Arr

  • java 线程同步详细介绍及实例代码

    java 线程同步 概要: 为了加快代码的运行速度,我们采用了多线程的方法.并行的执行确实让代码变得更加高效,但随之而来的问题是,有很多个线程在程序中同时运行,如果它们同时的去修改一个对象,很可能会造成讹误的情况,这个时候我们需要用一种同步的机制来管理这些线程. (一)竞争条件 记得操作系统中,让我印象很深的有一张图.上面画的是一块块进程,在这些进程里面分了几个线程,所有这些线程齐刷刷统一的指向进程的资源.Java中也是如此,资源会在线程间共享而不是每个线程都有一份独立的资源.在这种共享的情况下

  • Java web的读取Excel简单实例代码

    目录结构: Data.xls数据: 后台页面: public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //System.out.println(this.getServletContext().getRealPath ("/")); try{ Workbook wb = Workbook.getWorkbook(

  • Java HtmlEmail 邮件发送的简单实现代码

    Java 项目中常常回遇到发送邮件 Java 发送邮件有几种,今天先给大家介绍用 HtmlEmail 来发送邮件,我这里是用 Maven 来搭建的 HtmlEmail 可以抄带HTML 首先 需要导入jar 包 <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-email</artifactId> <version>1.4</versio

  • Java编程GUI中的事件绑定代码示例

    程序绑定的概念: 绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来.对java来说,绑定分为静态绑定和动态绑定:或者叫做前期绑定和后期绑定 静态绑定: 在程序执行前方法已经被绑定,此时由编译器或其它连接程序实现.例如:C. 针对java简单的可以理解为程序编译期的绑定:这里特别说明一点,java当中的方法只有final,static,private和构造方法是前期绑定 动态绑定 后期绑定:在运行时根据具体对象的类型进行绑定. 若一种语言实现了后期绑定,同时必须提供一些机制,可在运行期间

  • Java多线程饥饿与公平介绍及代码示例

    如果一个线程因为CPU时间全部被其他线程抢走而得不到CPU运行时间,这种状态被称之为"饥饿".而该线程被"饥饿致死"正是因为它得不到CPU运行时间的机会.解决饥饿的方案被称之为"公平性" – 即所有线程均能公平地获得运行机会. 下面是本文讨论的主题: Java中导致饥饿的原因 在Java中,下面三个常见的原因会导致线程饥饿: 高优先级线程吞噬所有的低优先级线程的CPU时间. 线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续

  • Java之dao模式详解及代码示例

    什么是dao模式? DAO(Data Access Object)顾名思义是一个为数据库或其他持久化机制提供了抽象接口的对象,在不暴露底层持久化方案实现细节的前提下提供了各种数据访问操作.在实际的开发中,应该将所有对数据源的访问操作进行抽象化后封装在一个公共API中.用程序设计语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法.在这个应用程序中,当需要和数据源进行交互的时候则使用这个接口,并且编写一个单独的类来实现这个接口,在逻辑上该类对应一个特定的数据存储.DAO模式实

  • Java网络编程之TCP通信完整代码示例

    一.概述 Socket类是Java执行客户端TCP操作的基础类,这个类本身使用代码通过主机操作系统的本地TCP栈进行通信.Socket类的方法会建立和销毁连接,设置各种Socket选项. ServerSocket类是Java执行服务器端操作的基础类,该类运行于服务器,监听入站TCP连接,每个socket服务器监听服务器的某个端口,当远程主机的客户端尝试连接此端口时,服务器就被唤醒,并返回一个表示两台主机之间socket的正常Socket对象. 二.什么是TCP? TCP是一种面向连接的.可靠的.

  • Java编程将汉字转Unicode码代码示例

    上一次接触到编码的知识,还是上大学的时候,那时候学的是通信工程专业,有关编码的内容,不记得是在通信原理还是信息论与编码里面学到的了.却依然记得那个信息论与编码的老师,最喜欢吃的是尖椒肥肠盖饭,不知道是尖椒肥肠吃多了还是太聪明的缘故,三十多岁就开始拜顶了.那四年真是一段难忘的回忆... 话不多说,咱们进入正题.这里是一个简单的Java编程将汉字转Unicode码代码示例,下面是代码: package me.socketthread; public class ToUnicode { /** * @

  • Java编程利用socket多线程访问服务器文件代码示例

    这篇文章将向大家展示Java编程利用socket多线程访问服务器文件代码示例,如果您想先了解Java多线程socket编程的基础知识,可以看下这篇文章:Java多线程编程实现socket通信示例代码. 接下来进入正文,我们看看利用socket多线程访问服务器代码: ServerMain.java package com.ysk.webServer; import java.io.File; import java.io.IOException; import java.net.ServerSoc

随机推荐