多线程之Lock与synchronized比较及使用

时间:2021-12-25 08:23:18
   第一:先比较两者的区别:
类别
synchronized
Lock
存在层次
Java的关键字,在jvm层面上
是一个类 java.util.concurrent.locks

锁的释放
1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁
一般用在try()catch{}中然后在finally中必须释放锁,不然容易造成线程死锁
锁的获取
假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待
分情况而定,Lock有多个锁获取的方式
一般如果用Lock.lock()锁那么如果一个线程占用另外一个线程也必须在一直等待当前线程释放锁之后才能执行
但是如果使用的是tyLock()锁的话一个线程占用锁那么其他线程则不用等待可以先去做其他事情或者返回

锁状态
无法判断
可以判断
tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
锁类型
可重入 不可中断 非公平
可重入 可判断 可公平(两者皆可)
性能
少量同步(并发量小的时候效率还可以)
大量同步(并发量大时效率也可以,在并发量小的时候与 synchronized区别不大
第二:使用synchronized时一定要注意:
            使用synchronized时锁标志一定要用static修饰,代表不管多少线程访问使用的都是同一个标志
            可以使用在方法体上也可以使用在代码块中
       例如:
                  private static Object object = new Object();   //用static修饰
                  private static int piaoshu = 50;//用static修饰
第三:使用类 java.util.concurrent.locks中的Lock锁
         一: 先来介绍ReentrantLock 锁
            private static ReentrantLock lock = new ReentrantLock();
            使用lock锁时一定要注意必须声明为全局的,如果声明为局部的那么每个线程在使用的时候都会new一个ReentrantLock对象出来那么每 个线程使用的都不是同一个ReentrantLock锁那么这样加锁就没有任何 
        ReentrantLock中的方法都有哪些呢,最常用的有以下几种:
        lock()、tryLock()、tryLock(long time, TimeUnit unit),lockInterruptibly()释放所的方法是unLock()
         1.首先lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果使用此方法进行对线程加锁那么当一个线程获取锁之后其他线程则需要等待这个线程释放所之后才能进行作用同synchronized一样
        2.如果当一个线程获取锁之后不想让其他线程等待那么可以使用tryLock()、tryLock(long time, TimeUnit unit)这两个方法中的任意一个加锁他们都有返回值true或false,tryLock(long time, TimeUnit unit)方法是让线程等待多久之后如果还没有获得锁那么就返回或者去做其他操作
        3.他们的使用一般都是在try()catch{}中使用,并且只会对在lock.lock()和lock.unlock()中间的代码进行加锁,之外的代码不会进行加锁,所以当A和B两个线程同时执行的时候,如果A先获得锁了,那么B就没获得锁,所以B是不能执行lock.unlock()之后的代码的,只能等A执行完之后B才能加锁然后释放锁之后才能继续执行下面的代码,这就是代码的执行顺序问题,前面的代码没执行完毕后面的代码是不能执行的(异步和从新开一个线程除外)
        4.lock.lock()使用方法,就是用来获取锁。如果锁已被其他线程获取,另外的线程则进行等待。
  
      lock.lock();
        try{
            //处理任务
            中间加锁执行的代码
        }catch(Exception ex){
            捕获异常
        }finally{
            lock.unlock();   //释放锁
        }

        5.tryLock()和tryLock(long time, TimeUnit unit)的使用,其中tryLock(代表时间, 代表时间单位可以是毫秒,纳秒,秒)
            tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
所以,一般情况下通过tryLock来获取锁时是这样使用的:
boolean tryLock = lock.tryLock();
或者boolean tryLock = lock.tryLock(long time, TimeUnit.MILLISECONDS);
第一个参数是时间,第二个参数是时间单位(秒或毫秒,纳秒等等)
if(tryLock ) {
     try{
         //处理任务
     }catch(Exception ex){
         捕获异常
     }finally{
         lock.unlock();   //释放锁
     } 
}else {
    //如果不能获取锁,则直接做其他事情
}

            6.其他方法
                isHeldByCurrentThread():查询当前线程是否保持此锁定 
                isFair():判断Lock是否为公平锁 
                isLocked():查询lock 是否被任意线程所持有。      
 二:ReentrantReadWriteLock读写锁实现了ReadWriteLock接口
                1. ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。
                2.如果读写文件时使用synchronized锁那么如果两个线程都是读文件那么一个线程占用另外一个线程则必须等待这样不利于读取效率,如果使用读锁则不一样了
            3.使用方法

            

public class Test {
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
     
    public static void main(String[] args)  {
        final Test test = new Test();
         
        new Thread(){
            public void run() {
                test.get(Thread.currentThread());
            };
        }.start();
         
        new Thread(){
            public void run() {
                test.get(Thread.currentThread());
            };
        }.start();
         
    }  
     
    public void get(Thread thread) {
        rwl.readLock().lock();
        try {
            long start = System.currentTimeMillis();
             
            while(System.currentTimeMillis() - start <= 1) {
                System.out.println(thread.getName()+"正在进行读操作");
            }
            System.out.println(thread.getName()+"读操作完毕");
        } finally {
            rwl.readLock().unlock();
        }
    }
}

说明thread1和thread2在同时进行读操作。
  这样就大大提升了读操作的效率。
  不过要注意的是,如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。
  如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。
 4.Lock和synchronized的选择
  总结来说,Lock和synchronized有以下几点不同:
  1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
  2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
  3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
  4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
  5)Lock可以提高多个线程进行读操作的效率。
  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。