线程高级---线程的一些编程技巧

对于线程的编程,同步已经不在算什么技巧了,而是基础。所以在阅读本篇之前,希望对同步有很深刻的认识。而本篇是朝着技巧来的,将会在今后的阐述中以此为主线将一些简单但常见的线程编程问题解释一下。
 

       
        1.提高性能的技巧。
          这里主要介绍几个提高性能的多线程编程技巧,适当的使用它们将会让多线程程序快很多。
        1)在阐述之前,先看看synchronized的工作方式,对于同一段临界区,这个代码进入之前要上锁,而知道出去后才会解锁给下一个线程去执行,也就 是说,使用synchronized之后,线程在临界区变成了串行执行而不是并行,这样看来似乎跟没使用多线程一样,尽管不得不这样做。有没有什么方法, 即使使用synchronized也能够照样并行执行呢?
          听起来这似乎不太可能,但确实有种方法能做到,虽然有些限制。记得之前曾今 提过String是一个不变类(immutable),试想多个线程对String进行操作会如何?因为根本不能改变它的字段,所以多个线程对它操作是安 全的,不需要加synchronized---根据我的测试,synchronized和非synchronized之间执行时间是(8:1),这是相当 大的差异。当然,之前也说过有些限制,是什么限制呢?一个类一经产生设置后就不能再改变,且里面的对象也不能是可变的。虽然看起来很苛刻,但在某些地方能 够提升性能。
        关于如何让一个类变成不变的,这里有几点需要注意:1.消除所有能改变字段的途径,包括字段本身的final private,设置字段方法的消除;2.消除所有能够被改变的对象;3.如果不能消除能被改变的对象,那么需要考虑这个不能消除对象操作不变类的同步等问题。
        2).另外一个提高性能的技巧是使用读写锁,这样对多个读线程来说不用互斥从而减少了开销,关于读写锁问题,将在后面的文章进行解释。
        可以看出来,要想提升性能,都必须有些特定条件,或者说必须牺牲一些东西。比如方法1)要想使用不变类有一些限制的条件,你不能改变这个不变类,并且需要 对这个不变类做许多苛刻的限制,而方法2)同样是只有在特定问题时才能使用,比如需要有读线程和写线程同时存在,并且读线程量大大超过写线程等等。
 

 
        2.循环锁。
        循环锁是多线程编程中比较常见的一种编程技巧。其主要的逻辑框架是对需要执行的一段代码进行保护,当保护条件成立时才执行该段代码,而当条件不成立时就让线程等待。

while(!doCondition){

    wait();

}

doSomethingImportant();

 
      这段代码虽然短小精悍,但是在多线程编程中极为有用。这里举个简单的例子以助于理解。比如公司桌上有个盒子,有些人将自己带的吃的放进去,有些人则拿出自 己想吃的东西。这时候其实我们是有这样的约束条件的:盒子不空则能取出东西吃,盒子不满则能向盒子中放东西。这个约束条件就是这里的 doCondition,套用过来就是说盒子空了就要等待直到盒子里有东西才能取出来吃,同样,盒子满了就要等待空出一个位置才能放东西进去。于是把盒子 作为一个资源临界区,我们有如下的代码:

public class Box{

   

    ......

    public synchronized void put(Object something){

        while(isFull){//can't put

            try{

                wait();

            }

            catch(InterruptedException){

            }

        }

        doPut(something);

        notifyAll();

    }

 

    public synchronized Object get(){

        while(isEmpty){

            try{

                wait();

            }

            catch(InterruptedException){

            }

        }

        Object something = doGet();

        notifyAll();

        return something;

    }

    ......

}

   我们来分析一下get,如果盒子是空的话线程就一直等待,而要线程继续执行循环之外的语句其实有两个条件,条 件一:有其它线程将其唤醒,条件二:唤醒后盒子至少有一件东西在里面。这里用这个while循环保证了这两个条件。之后才是事实上的从盒子去东西出来,然 后有一个notifyAll(),在这里的意思是对那些准备put却因为盒子满的线程而做的,将所有线程唤醒,一旦那些put线程得到控制权那么一定满足 两个条件第一是被唤醒了第二至少有一个空间能放东西。循环锁保证了这一整个过程的安全性,因此这是个非常重要的技巧。

    试想,如果while改成if还安全吗?当然不安全了,假设当前存在几个线程同时在get上 等待(这是可能的,假设盒子已为空),这时一个线程put并唤醒了全部等待的线程,第一个线程没有检查就开始执行doGet()(因为用了if),庆幸的 是它是正确的,但第二个线程同样没检查就doGet(),由于被第一个线程把盒子最后一件物品也取走了,所以第二个线程是不可能取到东西的,这时就会报错 了,因此换成if是不安全的,这也正式循环锁的意义锁在。

    那如果将try{}catch提到循环外呢?这个程序还是安全的吗?答案仍然是否定的。解释 其实同上面一样,还记得我强调过InterruptedException这个异常,一旦被唤醒,是从catch后的下一个语句开始执行,如果try在 while里,catch后的下一个语句其实是while的条件判断;如果try在while外面,catch的下一个语句就是直接执行doGet,其实 就成了刚刚那种情况。

    还有一点,循环中的wait也不能变成sleep,之前特别强调过wait在进入之前会获得对象锁,而进入后就释放了,因此能够给其他线程进入这段代码的 机会,而sleep则是在睡觉过程中还死握着锁不放,不妨将上例两个wait换成sleep,会发现开始线程就一直握着锁,醒了判断条件一定不满足,然后 又睡,又醒……它们被放在了死循环中,因为循环条件需要另一个线程持锁完成,可锁却被自己紧紧握着。

 


 

    3.条件锁。

    还用上例做例子,比如我可以将get方法改成如下:

    public synchronized Object get(){

        if(isEmpty)

            return;

        Object something = doGet();

        return something;

    }

    从形式上看跟上例也差不多,只是如果判断出不符合条件就返回回去,而不是让线程在这里空等。而返回去的线程也可以根据需要先做点别的事情,从而提高了吞吐和相应。

    当然,这里只是使用这个相同的例子打个比方,使得思路有连贯性,如果真要用if来做这个问题,还需要改动别处的代码,并且对此例来说不如循环锁方便。这里也总结下代码的框架如下:

    if(!doCondition)

        return;

    doSomething();

       
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值