【多线程】锁

多线程系列二(锁)

 

  使用多线程可以最大限度的利用CPU,提高程序的执行效率,但是同样也会产生线程安全的问题,如何保证线程安全,就涉及到了今天博客中要介绍的锁。

  加锁可以使用synchronized和lock两种方式,经过试验可以看出,当测试的数据量足够多的时候,使用lock方式在性能上要更快一些,在下面我们分别介绍这两种方式:

Synchronized加锁方式分为在同步方法和同步块级别上进行锁住对象。

同步方法:

public static synchronized void output2(String name){

        int len=name.length();

               

            for(int i=0;i<len;i++){

                System.out.print(name.charAt(i));

            }

            System.out.println();

               

    }

同步块:

static class Outputer{

        public void output(String name){

            int len=name.length();

            synchronized (Outputer.class) {

                for(int i=0;i<len;i++){

                    System.out.print(name.charAt(i));

                }

                System.out.println();

            }

           

        }

    }

 

  比较这两种方式,一般选择使用同步块的方式,哪个对象需要加上锁我们就在该对象所在的操作上加锁,如果在整个类或者整个方法上加锁,可能这个方法涉及到了一些别的变量就会被锁住,很容易发生死锁。

Lock加锁方式:

  在javaAPI中,有一个java.util.concurrent.locks.Lock接口的实现类,ReentrantLock与ReentrantReadWriteLock的内部类中的ReadLock与WriteLock;分别叫重入锁,读入锁,写入锁。

下面是使用ReentrantLock的例子:

//输出类

     static class Outputer{

         Lock lock=new ReentrantLock();

         

        public void output(String name){

            int len=name.length();

            lock.lock();

            try{

                for(int i=0;i<len;i++){

                    System.out.print(name.charAt(i));

                }

                System.out.println();

            }finally{

                lock.unlock();

            }

        }

    }

  每一个方法在加锁之后,在执行完锁对象的操作都要进行锁的释放。Lock()方法的源代码实现中有两个具体的实现类,NonfairSync与FairSync,刚好对应的一个是非公平锁,一个是公平锁。下面是别人总结的关于ReentrantLock采用非公平锁的时候程序执行的顺序:

ReentrantLock在采用非公平锁构造时,首先检查锁状态,如果锁可用,直接通过CAS设置成持有状态,且把当前线程设置为锁的拥有者。

如果当前锁已经被持有,那么接下来进行可重入检查,如果可重入,需要为锁状态加上请求数。如果不属于上面两种情况,那么说明锁是被其他线程持有,

当前线程应该放入等待队列。

在放入等待队列的过程中,首先要检查队列是否为空队列,如果为空队列,需要创建虚拟的头节点,然后把对当前线程封装的节点加入到队列尾部。由于设置尾部节点采用了CAS,为了保证尾节点能够设置成功,这里采用了无限循环的方式,直到设置成功为止。

在完成放入等待队列任务后,则需要维护节点的状态,以及及时清除处于Cancel状态的节点,以帮助垃圾收集器及时回收。如果当前节点之前的节点的等待状态小于1,说明当前节点之前的线程处于等待状态(挂起),那么当前节点的线程也应处于等待状态(挂起)。挂起的工作是由LockSupport类支持的,LockSupport通过JNI调用本地操作系统来完成挂起的任务(java中除了废弃的suspend等方法,没有其他的挂起操作)。

在当前等待的线程,被唤起后,检查中断状态,如果处于中断状态,那么需要中断当前线程。

 

下面是一个应用读写锁的示例:

package com.dmsd.thread;

 

import java.util.Random;

import java.util.concurrent.locks.ReadWriteLock;

import java.util.concurrent.locks.ReentrantReadWriteLock;

 

public class ReadWriteLockTest {

         public static void main(String[] args) {

                   final Queue3 q3 = new Queue3();

                   for(int i=0;i<3;i++)

                   {

                            new Thread(){

                                     public void run(){

                                               while(true){

                                                        q3.get();                                                

                                               }

                                     }

                                    

                            }.start();

 

                            new Thread(){

                                     public void run(){

                                               while(true){

                                                        q3.put(new Random().nextInt(10000));

                                               }

                                     }                          

                                    

                            }.start();

                   }

                  

         }

}

 

class Queue3{

         private Object data = null;//共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。

         ReadWriteLock rwl = new ReentrantReadWriteLock();

         public void get(){

                   rwl.readLock().lock();

                   try {

                            System.out.println(Thread.currentThread().getName() + " be ready to read data!");

                            Thread.sleep((long)(Math.random()*1000));

                            System.out.println(Thread.currentThread().getName() + "have read data :" + data);                         

                   } catch (InterruptedException e) {

                            e.printStackTrace();

                   }finally{

                            rwl.readLock().unlock();

                   }

         }

        

         public void put(Object data){

 

                   rwl.writeLock().lock();

                   try {

                            System.out.println(Thread.currentThread().getName() + " be ready to write data!");                                        

                            Thread.sleep((long)(Math.random()*1000));

                            this.data = data;              

                            System.out.println(Thread.currentThread().getName() + " have write data: " + data);                                            

                   } catch (InterruptedException e) {

                            e.printStackTrace();

                   }finally{

                            rwl.writeLock().unlock();

                   }

                  

        

         }

}

  读写锁应用的优势在于读的次数远远多于写的次数,任何需要修改的数据都需要在锁中进行。上面代码示例中对于一个共享变量,只能有一个线程写这个数据,但可以多个线程读这个数据,读的时候加读锁,写的时候加写锁,也就是说多个读锁不互斥,但是读锁与写锁之间是互斥的。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值