多线程与高并发(三)--Atomic类和线程同步新机制

一、什么是无锁操作?

  • 举个例子

模拟我们计一个数,所有的线程都要共同访问这个数count值,大家知道如果所有线程都要访问这个数的时候,如果每个线程给它往上加了10000,你这个时候是需要加锁的,不加锁会出问题。但是,你把它改成AtomicInteger之后就不用在做加锁的操作了,因为incrementAndGet内部用了cas操作,直接无锁的操作往上递增,无锁的操作效率会更高。

/**
 * 解决同样的问题的高效方法,使用AtomXXX类
 * AtomXXX类的本身方法都是原子性的,但不能保证多个方法连续调用都是原子性的
 */
public class Test {
   

    /*volatile*/ //int count1 = 0;
    AtomicInteger count = new AtomicInteger(0);

    /*synchronized*/void m() {
   
        for (int i = 0; i < 10000; i++)
            //if count1.get() < 1000
            count.incrementAndGet(); //count1++
    }

    public static void main(String[] args) {
   
        Test t = new Test();
        List<Thread> threads = new ArrayList<Thread>();
        for (int i = 0; i < 10; i++) {
   
            threads.add(new Thread(t::m, "thread-" + i));
        }
        threads.forEach((o) -> o.start());
        threads.forEach((o) -> {
   
            try {
   
                o.join();
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        });
        System.out.println(t.count);
    }
}
输出:100000

二、效率的比较?

  • 举个例子

模拟了很多个线程对一个数进行递增。
第一种:是一个long类型的数,递增的时候加锁;用synchronized,用一个lock,Object lock = new Object();,然后newRunnable(), 依然是一样的,在递增的时候我写的是synchronized (lock),然后计算时间;
第二种:使用AtomicLong可以让它不断的往上递增,这是第二种;每一个线程都new出来,之后每一个线程都做了十万次递增,第一种方式,打印起始时间->线程开始->所有线程结束->打印结束时间->计算最后花了多少时间;
第三种:使用LongAdder,1000个线程,每个线程十万次递增,第三种呢用的是LongAdder,这个LongAdder里面直接就是count3.increment();

public class Test {
   

    static long count2 = 0L;
    static AtomicLong count1 = new AtomicLong(0L);
    static LongAdder count3 = new LongAdder();

    public static void main(String[] args) throws Exception {
   
        Thread[] threads = new Thread[1000];
        for (int i = 0; i < threads.length; i++) {
   
            threads[i] = new Thread(() -> {
   
                for (int k = 0; k < 100000; k++) count1.incrementAndGet();
            });
        }
        long start = System.currentTimeMillis();
        for (Thread t : threads) t.start();
        for (Thread t : threads) t.join();
        long end = System.currentTimeMillis();
        //TimeUnit.SECONDS.sleep(10);
        System.out.println("Atomic: " + count1.get() + " time " + (end - start));

        //-----------------------------------------------------------

        Object lock = new Object();
        for (int i = 0; i < threads.length; i++) {
   
            threads[i] = new Thread(new Runnable() {
   
                @Override
                public void run() {
   
                    for (int k = 0; k < 100000; k++)
                        synchronized (lock) {
   
                            count2++;
                        }
                }
            });
        }

        start = System.currentTimeMillis();
        for (Thread t : threads) t.start();
        for (Thread t : threads) t.join();
        end = System.currentTimeMillis();

        System.out.println("Sync: " + count2 + " time " + (end - start));

        //---------------------------------------------------------

        for (int i = 0; i < threads.length; i++) {
   
            threads[i] = new Thread(() -> {
   
                for (int k = 0; k < 100000; k++) count3.increment();
            });
        }
        start = System.currentTimeMillis();
        for (Thread t : threads) t.start();
        for (Thread t : threads) t.join();
        end = System.currentTimeMillis();
        //TimeUnit.SECONDS.sleep(10);
        System.out.println("LongAdder: " + count1.longValue() + " time " + (end - start));
    }
    
}

输出:
Atomic: 100000000 time 1137
Sync: 100000000 time 6154
LongAdder: 100000000 time 186

结论:跑起来对比LongAdder是效率最高的,但是,把线程数变小了LongAdder未必有优势,循环数量少了LongAdder也未必有优势,所以,实际当中用哪种你要考虑一下你的并发有多高。

问题一:为什么Atomic要比Sync快?

因为不加锁,synchronized是要加锁的,有可能它要去操作系统申请重量级锁,所以synchronized效率偏低,在这种情形下效率偏低。

问题二:LongAdder为什么要比Atomicx效率要高呢?

是因为LongAdder的内部做了一个分段锁,类似于分段锁的概念。在它内部的时候,会把一个值放到一个数组里,比如说数组长度是4,最开始是0,1000个线程,也就是每个数组单元就有250个线程,以此类推,每一个都往上递增算出来结果在加到一起。
在这里插入图片描述

二、ReentrantLock

  • 什么是可重入?

意思就是我锁了一下还可以对同样这把锁再锁一下,synchronized必须是可重入的,不然的话子类调用父类是没法实现的,synchronized方法是可以调用synchronized方法的。锁是可重入的。子类和父类如果是synchronized(this)就是同一把锁,同一个this当然是同一把锁。

  • 举个例子
/**
 * new Thread(rl::m2).start();时候
 * 本例中由于m1锁定this,只有m1ִ执行完毕的时候,m2才能执行
 */
public class Test {
   

    synchronized void m1() {
   
        for (int i = 0; i < 5; i++) {
   
            try {
   
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
            System.out.println(i);
            if (i == 2) m2();
        }
    }

    synchronized void m2() {
   
        System.out.println("m2 ...");
    }

    public static void main(String[] args) {
   
        Test rl = new Test();
        new Thread(rl::m1).start();
        try {
   
            TimeUnit.S
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值