Java-Lock和Synchronized区别

Scroll Down

Lock和Synchronized的区别

参考链接 海子博客链接地址
  • Lock不是java语言内置的,Lock是一个类,通过该类来实现同步访问,synchronzied是java内置关键字
  • synchronized不需要手动去释放,当sync的代码块执行完会自动释放对锁的占用,但是Lock需要手动去释放,否则会造成死锁,手动释放需要在finally关键字里面执行
  • Lock可以让等待锁的线程响应中断,但是sync不行,如果线程中断,sync会一直等待下去
  • 使用Lock可以判断是不是成功获取锁
  • Lock可以进行多个读操作可以提高效率

总结来说,如果竞争不激烈两者性能类似,但是如果线程较多竞争激烈,那么Lock的效率回高于sync

  1. Lock - ReentrantLock lock方法
package com.troy.concurrent.lock;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockRunner {
    List list = new ArrayList();

    //注意与方法里面锁的区别
    Lock lock = new ReentrantLock();
    public static void main(String[] args){
        LockRunner runner = new LockRunner();
        Thread ta = new Thread(runner.new WorkA(),"WorkA");
        Thread tb = new Thread(runner.new WorkB(),"WorkB");
        ta.start();
        tb.start();
        runner.list.forEach(f->System.out.println(f));
    }

    void conCurrOper(Thread thread){
        //如果new锁的操作放在方法内,那么则锁无效,因为每一个线程都会在内存中有自己单独的副本,所以会造成每个单独的线程使用各自副本里面的锁,因此无法看到锁效果
        //Lock lock = new ReentrantLock();
        lock.lock();
        try{
            System.out.println(thread.getName()+"线程获取到锁,线程正在处理...");
            for(int i=0;i<3;i++){
                list.add(thread.getName()+ "~"+i);
            }
        }catch (Exception e){
            System.out.println("程序发生异常");
            System.out.println(e.getMessage());
        }finally{
            lock.unlock();
            System.out.println(thread.getName() +"执行结束释放了锁...");
        }
    }

    class WorkA implements Runnable{

        public void run() {
            conCurrOper(Thread.currentThread());
        }
    }

    class WorkB implements Runnable{

        public void run() {
            conCurrOper(Thread.currentThread());
        }
    }
}

输出结果:

WorkA线程获取到锁,线程正在处理...<br>
WorkA执行结束释放了锁...<br>
WorkB线程获取到锁,线程正在处理...<br>
WorkB执行结束释放了锁...<br>
WorkA~0<br>
WorkA~1<br>
WorkA~2<br>
WorkB~0<br>
WorkB~1<br>
WorkB~2<br>

  1. Lock - ReentrantLock tryLock方法
package com.troy.concurrent.lock;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 通过tryLock()进行一次判断是否可以获取锁
 */
public class LockRunner_tryLock {
    List list = new ArrayList();
    Lock lock = new ReentrantLock();
    public static void main(String[] args) throws Exception{
        LockRunner_tryLock runner = new LockRunner_tryLock();
        Thread ta = new Thread(runner.new WorkA(),"WorkA");
        Thread tb = new Thread(runner.new WorkB(),"WorkB");
        ta.start();
        tb.start();
        Thread.sleep(3000);
        runner.list.forEach(f->System.out.println(f));
    }

    public boolean conCurrOper(Thread thread){
        if(lock.tryLock()){
            try {
                System.out.println(thread.getName() + "线程获取到锁,线程正在处理...");
                for(int i=0;i<3;i++){
                    list.add(thread.getName()+"~"+i);
                }
                Thread.sleep(500);
            }catch (Exception e){
                System.out.println("程序发生异常");
                System.out.println(e.getMessage());
                lock.unlock();
                return false;
            }finally{
                System.out.println(thread.getName() +"执行结束释放了锁...");
                lock.unlock();
                return true;
            }
        }else{
            System.out.println(thread.getName()+"线程获取锁失败,准备从新尝试本次任务终止...");
            return false;
        }
    }

    class WorkA implements Runnable{

        public void run() {
            int retry = 0;
            boolean isSuc = conCurrOper(Thread.currentThread());
            while(retry < 3 && !isSuc){
                System.out.println(Thread.currentThread().getName() + "正在进行第"+retry+"次重试...");
                isSuc = conCurrOper(Thread.currentThread());
                retry++;
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("isSuc = " + isSuc);
        }
    }

    class WorkB implements Runnable{

        public void run() {
            int retry = 0;
            boolean isSuc = conCurrOper(Thread.currentThread());
            while(retry < 3 && !isSuc){
                System.out.println(Thread.currentThread().getName() + "正在进行第"+retry+"次重试...");
                isSuc = conCurrOper(Thread.currentThread());
                retry++;
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("isSuc = " + isSuc);
        }
    }
}

输出结果:

WorkA线程获取到锁,线程正在处理...<br>
WorkB线程获取锁失败,准备从新尝试本次任务终止...<br>
WorkB正在进行第0次重试...<br>
WorkB线程获取锁失败,准备从新尝试本次任务终止...<br>
WorkA执行结束释放了锁...<br>
WorkB正在进行第1次重试...<br>
isSuc = true<br>
WorkB线程获取到锁,线程正在处理...<br>
WorkB执行结束释放了锁...<br>
isSuc = true<br>
WorkA~0<br>
WorkA~1<br>
WorkA~2<br>
WorkB~0<br>
WorkB~1<br>
WorkB~2<br>
  1. Lock - Condition
package com.troy.concurrent.lock;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockRunner_Condition {
    List list = new ArrayList();
    SmallQueue squ;
    public static void main(String[] args) throws Exception {
        LockRunner_Condition runner = new LockRunner_Condition();
        runner.squ = runner.new SmallQueue();

        System.out.println("take value from squ");
        //如果拿一个空squ,程序会一直block在这里直到有对象put进去squ,因为lock condition的控制
//        runner.squ.take();
//        runner.squ.put("Hello world");
//        runner.squ.put("Hello Java");
//        String value = runner.squ.take().toString();
//        System.out.println("take value from squ == " +value);

        Thread workA = new Thread(runner.new WorkA(),"读取线程_workA");
        Thread workB = new Thread(runner.new WorkB(),"写入线程_workB");
        workA.start();
        workB.start();
    }

    /**
     * 仿照ArrayBlockingQueue的put,take实现
     */
    class SmallQueue{
        Lock lock = new ReentrantLock();
        Condition empty = lock.newCondition();
        Condition full = lock.newCondition();
        Object[] objs = new Object[2];
        int putptr,takeptr,count;
        void put(Object obj) throws Exception{
            lock.lock();
            try{
                while(count == objs.length){
                    System.out.println(Thread.currentThread().getName() +" is waitting cosume item, now queue is full");
                    full.await();
                    System.out.println(Thread.currentThread().getName() +" wait over,now has space");
                }
                objs[putptr]=obj;
                if (++putptr == objs.length) putptr = 0;
                ++count;
                empty.signal();
            }finally{
                lock.unlock();
            }
        }
        Object take() throws Exception{
            lock.lock();
            try {
                while (count == 0)
                    // 注意这行代码,楼主在添加本行代码后,程序会一直输出这行print从而进入死循环,目前我也不知道什么原因,有明白的大神可以指点一下
                    //System.out.println(Thread.currentThread().getName() + " is waitting item, queue is empty now...");
                    empty.await();
                    System.out.println(Thread.currentThread().getName() + " waitting is end, queue has item now...");
                Object x = objs[takeptr];
                if (++takeptr == objs.length) takeptr = 0;
                --count;
                full.signal();
                return x;
            } finally {
                lock.unlock();
            }
         }
    }

    class WorkA implements Runnable{

        public void run() {
            int i = 0;
            while(i<4){
                try {
                    System.out.println(Thread.currentThread().getName() + " start to take value from squ...");
                    StringBuilder value = new StringBuilder(squ.take().toString());
                    System.out.println(Thread.currentThread().getName() + " take value from squ value is "+value.toString());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                i++;
            }
        }
    }

    class WorkB implements Runnable{

        public void run(){
            for(int i=0; i<4; i++){
                StringBuilder stringBuilder = new StringBuilder("Que-value-");
                stringBuilder.append(i);
                try {
                    System.out.println(Thread.currentThread().getName() + " will put down value to squ ");
                    squ.put(stringBuilder.toString());
                    System.out.println(Thread.currentThread().getName() + " put down to squ " + stringBuilder.toString());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

输出结果A:

take value from squ<br>
读取线程_workA start to take value from squ...<br>
写入线程_workB will put down value to squ <br>
写入线程_workB put down to squ Que-value-0<br>
写入线程_workB will put down value to squ <br>
写入线程_workB put down to squ Que-value-1<br>
写入线程_workB will put down value to squ <br>
写入线程_workB is waitting cosume item, now queue is full<br>
读取线程_workA waitting is end, queue has item now...<br>
读取线程_workA take value from squ value is Que-value-0<br>
读取线程_workA start to take value from squ...<br>
读取线程_workA waitting is end, queue has item now...<br>
读取线程_workA take value from squ value is Que-value-1<br>
读取线程_workA start to take value from squ...<br>
写入线程_workB wait over,now has space<br>
写入线程_workB put down to squ Que-value-2<br>
写入线程_workB will put down value to squ <br>
写入线程_workB put down to squ Que-value-3<br>
读取线程_workA waitting is end, queue has item now...<br>
读取线程_workA take value from squ value is Que-value-2<br>
读取线程_workA start to take value from squ...<br>
读取线程_workA waitting is end, queue has item now...<br>
读取线程_workA take value from squ value is Que-value-3<br>

输出结果B:

读取线程_workA start to take value from squ...<br>
写入线程_workB will put down value to squ <br>
写入线程_workB put down to squ Que-value-0<br>
写入线程_workB will put down value to squ <br>
读取线程_workA waitting is end, queue has item now...<br>
写入线程_workB put down to squ Que-value-1<br>
写入线程_workB will put down value to squ <br>
读取线程_workA take value from squ value is Que-value-0<br>
写入线程_workB put down to squ Que-value-2<br>
读取线程_workA start to take value from squ...<br>
写入线程_workB will put down value to squ <br>
读取线程_workA waitting is end, queue has item now...<br>
读取线程_workA take value from squ value is Que-value-1<br>
写入线程_workB put down to squ Que-value-3<br>
读取线程_workA start to take value from squ...<br>
读取线程_workA waitting is end, queue has item now...<br>
读取线程_workA take value from squ value is Que-value-2<br>
读取线程_workA start to take value from squ...<br>
读取线程_workA waitting is end, queue has item now...<br>
读取线程_workA take value from squ value is Que-value-3<br>

细心的朋友会发现会出现A和B两种情况,这是正常的,因为我们是一个多线程同时put和take数据从队列中:
情况A:表示写入的线程先执行到了队列容量,因此触发了队列满时的print提醒“写入线程_workB is waitting cosume item, now queue is full”;
情况B:表示程序在执行时资源分配均衡,写入线程和读取线程近乎平均,因此是有读有写的输出。

  1. Lock - ReentrantReadWriteLock
    在使用Read-Write锁之前我们看下sycn的话程序怎么处理
package com.troy.concurrent.lock;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LockRunner_ReadAndWriter_Sync {

    //注意与方法里面锁的区别
    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public static void main(String[] args){
        LockRunner_ReadAndWriter_Sync runner = new LockRunner_ReadAndWriter_Sync();
        Thread ta = new Thread(runner.new WorkA(),"WorkA");
        Thread tb = new Thread(runner.new WorkB(),"WorkB");
        ta.start();
        tb.start();
    }

    synchronized void conCurrOper(Thread thread){
        long start = System.currentTimeMillis();
        while(System.currentTimeMillis() - start <= 1) {
            System.out.println(thread.getName()+"正在进行读操作");
        }
        System.out.println(thread.getName()+"处理结束...");
        System.out.println(thread.getName()+"处理结束...");
    }

    class WorkA implements Runnable{

        public void run() {
            conCurrOper(Thread.currentThread());
        }
    }

    class WorkB implements Runnable{

        public void run() {
            conCurrOper(Thread.currentThread());
        }
    }
}

输出结果:

WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA处理结束...<br>
WorkA处理结束...<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB处理结束...<br>
WorkB处理结束...<br>

使用readLock的结果

package com.troy.concurrent.lock;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LockRunner_ReadAndWriter {

    //注意与方法里面锁的区别
    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public static void main(String[] args){
        LockRunner_ReadAndWriter runner = new LockRunner_ReadAndWriter();

        Thread ta_L = new Thread(runner.new WorkA_Lock(),"WorkA_Lock");
        Thread tb_L = new Thread(runner.new WorkB_Lock(),"WorkB_Lock");
        ta_L.start();
        tb_L.start();
    }

    void conCurrOper_Lock(Thread thread){
        lock.readLock().lock();
        try{
            long start = System.currentTimeMillis();
            while(System.currentTimeMillis() - start <= 1) {
                System.out.println(thread.getName()+"正在进行读操作");
            }
            System.out.println(thread.getName()+"处理结束...");
        }finally {
            lock.readLock().unlock();
        }

    }

    class WorkA_Lock implements Runnable{

        public void run() { conCurrOper_Lock(Thread.currentThread()); }
    }

    class WorkB_Lock implements Runnable{

        public void run() {
            conCurrOper_Lock(Thread.currentThread());
        }
    }

}

输出结果:

WorkB_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkB_Lock处理结束...<br>
WorkA_Lock正在进行读操作<br>
WorkA_Lock处理结束...<br>

通过例子可以看到读锁可以大大提高效率,但是有几点要注意:

  1. 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。
  2. 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。

锁的相关介绍

  1. 可重入锁

    如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。

        public synchronized void method1() {
            method2();
        }
    
        public synchronized void method2() {
    
        }
    

    上述代码中的两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。

    而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。

  2. 可中断锁

    可中断锁:顾名思义,就是可以相应中断的锁。

    在Java中,synchronized就不是可中断锁,而Lock是可中断锁(lockInterruptibly())。

    如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁.

  3. 公平锁

    公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。

    非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。

    在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。

    而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。

    ReentrantLock lock = new ReentrantLock(true);
    ReentrantReadWriteLock lk = new ReentrantReadWriteLock(true);
如果参数为true表示为公平锁,为fasle为非公平锁。默认情况下,如果使用无参构造器,则是非公平锁。<br>
另外在ReentrantLock类中定义了很多方法,比如:<br>
isFair()        //判断锁是否是公平锁<br>
isLocked()    //判断锁是否被任何线程获取了<br>
isHeldByCurrentThread()   //判断锁是否被当前线程获取了<br>
hasQueuedThreads()   //判断是否有线程在等待该锁<br>
  1. 读写锁

    读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。

    正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。

    ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。

    可以通过readLock()获取读锁,通过writeLock()获取写锁。

    上面已经演示过了读写锁的使用方法,在此不再赘述。