转载

Java AtomicInteger类使用

一个计数器

对于普通的变量,在涉及多线程操作时,会遇到经典的线程安全问题。考虑如下代码:

private static final int TEST_THREAD_COUNT = 100;
private static int counter = 0;

public static void main(String[] args) {
    final CountDownLatch latch = new CountDownLatch(TEST_THREAD_COUNT);  
    Thread[] threads = new Thread[TEST_THREAD_COUNT];

    for (int i = 0; i < TEST_THREAD_COUNT; i++) {
        threads[i] = new Thread(new Runnable() {

            @Override
            public  void run() {
                ++counter;
                System.out.println("Thread " + Thread.currentThread().getId() + "  / Counter : " + counter);
                latch.countDown();
            }
        });
        threads[i].start();  

    }

    try {
        latch.await();
        System.out.println("Main Thread " + "  / Counter : " + counter);
    } catch (InterruptedException e) {  
        e.printStackTrace();  
    }
}

多次执行这段程序,我们会发现最后 counter 的值会出现 98 , 99 等值,而不是预想中的 100

...
...
Thread 100  / Counter : 90
Thread 101  / Counter : 91
Thread 102  / Counter : 92
Thread 103  / Counter : 93
Thread 104  / Counter : 95
Thread 105  / Counter : 95
Thread 106  / Counter : 96
Thread 107  / Counter : 97
Thread 108  / Counter : 98
Thread 109  / Counter : 99
Main Thread   / Counter : 99

同步

java有个 sychronized 关键字,它能后保证同一个时刻只有一条线程能够执行被关键字修饰的代码,其他线程就会在队列中进行等待,等待这条线程执行完毕后,下一条线程才能对执行这段代码。

它的修饰对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
  3. 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  4. 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

现在我们开始使用我们的新知识,调整以上代码,在 run() 上添加 sychronized 关键字。

private static final int TEST_THREAD_COUNT = 100;
private static int counter = 0;

public static void main(String[] args) {
    final CountDownLatch latch = new CountDownLatch(TEST_THREAD_COUNT);  
    Thread[] threads = new Thread[TEST_THREAD_COUNT];

    for (int i = 0; i < TEST_THREAD_COUNT; i++) {
        threads[i] = new Thread(new Runnable() {

            @Override
            public synchronized void run() {
                ++counter;
                System.out.println("Thread " + Thread.currentThread().getId() + "  / Counter : " + counter);
                latch.countDown();
            }
        });
        threads[i].start();  

    }

    try {
        latch.await();
        System.out.println("Main Thread " + "  / Counter : " + counter);
    } catch (InterruptedException e) {  
        e.printStackTrace();  
    }
}

多次执行新代码,我们依旧发现结果不正确:

...
...
Thread 98  / Counter : 87
Thread 97  / Counter : 86
Thread 99  / Counter : 89
Thread 100  / Counter : 89
Thread 101  / Counter : 90
Thread 102  / Counter : 91
Thread 104  / Counter : 95
Thread 108  / Counter : 97
Thread 106  / Counter : 96
Thread 105  / Counter : 95
Thread 103  / Counter : 95
Thread 109  / Counter : 98
Thread 107  / Counter : 97
Main Thread   / Counter : 98

这里的原因在于 synchronized 是锁定当前 实例对象 的代码块。也就是当多条线程操作同一个实例对象的同步方法是时,只有一条线程可以访问,其他线程都需要等待。这里 Runnable 实例有多个,所以锁就不起作用。

我们继续修改代码,使得 Runnable 实例只有一个:

private static final int TEST_THREAD_COUNT = 100;
private static int counter = 0;
private final static CountDownLatch latch = new CountDownLatch(TEST_THREAD_COUNT);

static class MyRunnable implements Runnable {

    @Override
    public synchronized void run() {
        ++counter;
        System.out.println("Thread " + Thread.currentThread().getId() + "  / Counter : " + counter);
        latch.countDown();
    }
    
}

public static void main(String[] args) {
    Thread[] threads = new Thread[TEST_THREAD_COUNT];

    MyRunnable myRun = new MyRunnable();
    for (int i = 0; i < TEST_THREAD_COUNT; i++) {
        threads[i] = new Thread(myRun);
        threads[i].start();
    }

    try {
        latch.await();
        System.out.println("Main Thread " + "  / Counter : " + counter);
    } catch (InterruptedException e) {  
        e.printStackTrace();  
    }
}

现在我们发现多次执行代码后,最后结果都是 100

原文  https://segmentfault.com/a/1190000020750149
正文到此结束
Loading...