JAVA并发与锁相关的使用

来源:互联网 发布:安卓纪元 矩阵潜袭 编辑:程序博客网 时间:2024/06/06 17:39
        JAVA比C++优雅的地方就在于对于并发拥有非常完美的生态(虽然boost库也提供了很多成熟的功能,但是仍然无法和java相比),而且还在不断进化。当C++程序员还在研究互斥被虐过100遍的时候,JAVA不需要任何知识便可愉快的写出强壮的并发代码。

        但是,偶然的一次我在使用lock的时候出现了一个很大的问题:当我对锁持有的时间很短,但是却非常频繁的访问的时候,性能却消耗在了锁的竞争与线程的挂起,眼看这CPU跑满但效率却不忍直视。这个时候我才意识到并发是个非常复杂的概念。我赶快去查找相关资料,做了一些整理。


        同步

        首先   同步关键字仍然是首选,编译器会自动优化达到性能最佳化(尤其是1.7+版本中)。尽可能的减少同步块内的逻辑复杂度,同时也要尽量避免在同步块内调用其他含有同步语句的方法,规避死锁风险。


        LOCK

        JAVA的锁可以说是重量级的锁,在锁的竞争和线程的挂起中会消耗一些CPU资源,如果我们在加锁后的操作非常复杂,无可厚非,这种消耗在整体的占比中并不高。但是如果我们加锁的业务非常简单,那么浪费的CPU会非常可观。做一个测试,让CPU不停的竞争锁:

    private static int i = 0;        private static ReentrantLock lock = new ReentrantLock();        private static CountDownLatch latch = new CountDownLatch(8);        public static class TESTLOCK implements Runnable{public void run() {while(true){try{lock.lock();if(i > 1000000000){break;}i += 1;}finally{lock.unlock();}}latch.countDown();}    }    public static void main(String[] parms) throws UnsupportedEncodingException{        long t = System.currentTimeMillis();        for(int i = 0 ; i < 8 ; i++){            new Thread(new TESTLOCK()).start();        }        try {            latch.await();        } catch (InterruptedException e) {            e.printStackTrace();        }                System.out.println("USE:" + (System.currentTimeMillis() - t));    }

        这段在我的系统上运行消耗了55%的CPU,并消耗了约30秒的时间 (此处数据仅用作与其他方法对比,不做数据报告使用)。但是,由于而同样的单线程计算1000000000次加法操作,仅仅消耗了20%的CPU与约19秒的时间。而中间相差的资源则浪费在锁的竞争与线程的挂起状态切换上。所以, 假如我们的操作对锁的持有非常短,则看起来,这种方法是非常不划算的。为了避免这种情况,可以使用自旋锁来避免频繁挂起线程。


将上文中代码

lock.lock();

换成:


while(!lock.tryLock()){   try {     Thread.sleep(1);   } catch (InterruptedException e) {     e.printStackTrace();   }}
      这次,我的CPU消耗下降到了20%,消耗时间20秒。可以说已经接近了无锁的水平,但是这里会有一个很大的缺陷:由于SLEEP后,其他线程会持续的操作对象,从而使SLEEP的线程变相的失去了优先级,所以对一些场景非常不利。比如说整个业务时间非常短,则竞争一次CPU后往往可以执行很多次业务才会再次让其他线程参与竞争。所以这种实现其实只是大幅度的降低了锁竞争的次数,在一些场景中,线程往往白白放弃了CPU,不能发挥出全部性能。


将上文中代码

lock.lock();
换成

while(!lock.tryLock()){   Thread.yield();}
      由于yield只是让出了CPU使用权,重新进入CPU请求队列,所以对优先级影响并不大,但是有一个很大的缺陷:如果操作对锁的持有非常频繁,而且加锁的逻辑在整个业务流程中占据了较大一个比重(如上文中几乎是100%的比重),则CPU可能在连续的空跑中,反而会浪费更多的CPU资源,如上文代码在此时消耗了75%的CPU与约24秒的时间。但是如果在另外一种场景下,加锁的业务逻辑占比非越小,性能越好。



       LockSupport

       同步原语,特点是直接对线程本身操作,不会产生死锁,而且unpark可以在park之前,非常灵活,但是很多场景不适用。

 

      CountDownLatch


      wait/notify

      

       。。。未完,整理中。。。


0 0