高并发获取唯一性自增长字段

Posted 前度刘郎

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高并发获取唯一性自增长字段相关的知识,希望对你有一定的参考价值。

一、考虑使用synchronized同步锁

对synchronized(this)的一些理解

  1. 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。 
  2. 当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
  3. 然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的除synchronized(this)同步代码块以外的部分。
  4. 第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
  5. 以上规则对其它对象锁同样适用。

一:synchronized同步代码块

技术分享
package com.clzhang.sample.thread;

public class SyncThread1 implements Runnable {
    private Integer key = 0;

    @Override
    public void run() {
        // key是Integer对象(注意不是int,因为int不是对象)
        // 线程进入下面同步代码之前,需要先获取key的锁。
        // 需要结果是key实现自增长,如果没有同步块,则可能会出现重复key值的现象
        synchronized (key) {
            key++;
            
            System.out.println(Thread.currentThread().getName() + ":" + key);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
        }
    }

    public static void main(String[] args) {
        SyncThread1 st = new SyncThread1();
        
        for(int i=0; i<10; i++) {
            new Thread(st, "Thread" + i).start();
        }
    }
}
技术分享

输出:

Thread1:2
Thread3:3
Thread5:4
Thread7:5
Thread0:2
Thread2:7
Thread9:6
Thread4:8
Thread6:9
Thread8:10

二:synchronized同步方法

同步方法分静态和非静态两种。静态方法则一定会同步,非静态方法需在单例模式才生效,推荐用静态方法(不用担心是否单例)。

2.1 非静态方法同步示范

技术分享
package com.clzhang.sample.thread;

// 如果是同步方法,则分静态和非静态两种。
// 静态方法则一定会同步,非静态方法需在单例模式才生效,推荐用静态方法(不用担心是否单例)。
public class SyncThread2 implements Runnable {
    private Integer key = 0;

    // 此示范为非静态方法同步
    public synchronized Integer getKey() {
        key++;

        return key;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":" + getKey());
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
        }
    }

    public static void main(String[] args) {
        // 非静态方法同步,需要启动单例模式
        SyncThread2 st = new SyncThread2();
        for (int i = 0; i < 10; i++) {
            new Thread(st, "Thread" + i).start();
        }
    }
}
技术分享

输出:

Thread0:1
Thread1:3
Thread2:2
Thread3:5
Thread5:6
Thread7:7
Thread9:8
Thread6:9
Thread8:10
Thread4:4

2.2 静态方法同步示范

技术分享
package com.clzhang.sample.thread;

// 如果是同步方法,则分静态和非静态两种。
// 静态方法则一定会同步,非静态方法需在单例模式才生效,推荐用静态方法(不用担心是否单例)。
public class SyncThread3 implements Runnable {
    private static Integer key = 0;

    // 此示范为静态方法同步
    public synchronized static Integer getKey() {
        key++;

        return key;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":" + getKey());
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
        }
    }

    public static void main(String[] args) {
        
        // 如果用静态方法实现同步,则可以生成对象的多个实例
        for (int i = 0; i < 10; i++) {
            SyncThread3 st = new SyncThread3();
            new Thread(st, "Thread" + i).start();
        }
    }
}
技术分享

输出:

Thread3:3
Thread1:1
Thread0:2
Thread5:4
Thread7:5
Thread9:6
Thread2:7
Thread8:10
Thread6:9
Thread4:8

总结

1、无论是同步代码块还是同步方法,必须获得对象锁才能够进入同步代码块或者同步方法进行操作。

2、如果采用方法级别的同步,对象锁为方法所在的对象;如果是静态同步方法,对象锁为方法所在的类(唯一)。

3、对于代码块,对象锁即指synchronized(object)中的object。

 

二、使用java并发包

使用java.util.concurrent包来的较为简单,java.util.concurrent.atomic原子操作包,这个包里面提供了一组原子变量类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。可以对基本数据、数组中的基本数据、对类中的基本数据进行操作。原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的读-改-写操作。

    java.util.concurrent.atomic中的类可以分成4组:

  • 标量类(Scalar):AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • 数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
  • 更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  • 复合变量类:AtomicMarkableReference,AtomicStampedReference

第一组AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference这四种基本类型用来处理布尔,整数,长整数,对象四种数据,其内部实现不是简单的使用synchronized,而是一个更为高效的方式CAS (compare and swap) + volatile和native方法,从而避免了synchronized的高开销,执行效率大为提升。

下面以AtomicInteger示例:

/**
 * 来看AtomicInteger提供的接口。

 //获取当前的值
 
 public final int get()
 
 //取当前的值,并设置新的值
 
  public final int getAndSet(int newValue)
 
 //获取当前的值,并自增
 
  public final int getAndIncrement() 
 
 //获取当前的值,并自减
 
 public final int getAndDecrement()
 
 //获取当前的值,并加上预期的值
 
 public final int getAndAdd(int delta)

 

测试代码:

public class AtomicIntegerDemo {
 public static void main(String[] args) {
  AtomicInteger ai=new AtomicInteger(0);
  int i1=ai.get();
  v(i1);
  int i2=ai.getAndSet(5);
  v(i2);
  int i3=ai.get();
  v(i3);
  int i4=ai.getAndIncrement();
  v(i4);
  v(ai.get());
  
 }
 static void v(int i)
 {
  System.out.println("i : "+i);
 }
}

以上是关于高并发获取唯一性自增长字段的主要内容,如果未能解决你的问题,请参考以下文章

分布式系统ID生成方案汇总

MySQL数据库8列属性之主键自增长

MyBatis获取插入记录的自增长字段值

数据库表的设计用 自增长int类型字段做主键,插入数据时怎么保证这条记录在表中是唯一的呢

9.创建表-自增长(AUTO_INCREAMENT)

mysql关于自增长增长问题