Java并发编制程序(五)Lock

一.synchronized的缺陷

图片 1图片 2

synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为什么会出现Lock呢?

在上面一篇文章中,我们了解到如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
2)线程执行发生异常,此时JVM会让线程自动释放锁。

那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。
因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。

再举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
但是采用synchronized关键字来实现同步的话,就会导致一个问题:
如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。

另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。

总结一下,也就是说Lock提供了比synchronized更多的功能。但是要注意以下几点:
1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

View Code

二.java.util.concurrent.locks包下常用的类

1.Lock

图片 3图片 4

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

View Code

下边来每个讲述Lock接口中各样方法的利用,lock()、tryLock()、tryLock(long
time, TimeUnit
unit)和lockInterruptibly()是用来收获锁的。unLock()方法是用来释放锁的。newCondition()这一个艺术一时半刻不在此描述,会在后面包车型大巴线程同盟一文中讲述。

①首先lock()方法是平凡使用得最多的一个方法,正是用来得到锁。假如锁已被其余线程获取,则展开等待。

出于在前方讲到如若采用Lock,必须积极去释放锁,并且在发生非常时,不会自行释放锁。因而一般的话,使用Lock必须在try{}catch{}块中开始展览,并且将释放锁的操作放在finally块中展开,以保障锁一定被被放出,制止死锁的产生。平日选拔Lock来实行同步的话,是以上边那种样式去选拔的:

图片 5图片 6

Lock lock = ...;
lock.lock();
try{
    //处理任务
}catch(Exception ex){

}finally{
    lock.unlock();   //释放锁
}

View Code

②tryLock()方法是有再次回到值的,它意味着用来品尝获得锁,假如获得成功,则赶回true,假使获得失利(即锁已被别的线程获取),则赶回false,也就说那几个主意无论怎么样都会及时回去。在拿不到锁时不会间接在那等待。

tryLock(long
time, TimeUnit
unit)方法和tryLock()方法是类似的,只可是差距在于这几个格局在拿不到锁时会等待一定的年华,在时刻期限之内如若还拿不到锁,就赶回false。假若只要一开端获得锁可能在等待时期内获得了锁,则赶回true。

故而,一般情状下通过tryLock来博取锁时是那般使用的:

图片 7图片 8

Lock lock = ...;
if(lock.tryLock()) {
     try{
         //处理任务
     }catch(Exception ex){

     }finally{
         lock.unlock();   //释放锁
     } 
}else {
    //如果不能获取锁,则直接做其他事情
}

View Code

③lockInterruptibly()方法相比奇特,当通过这些点子去获取锁时,假诺线程正在等候获取锁,则这几个线程能够响应中断,即中断线程的等候情状。也就使说,当八个线程同时经过lock.lockInterruptibly()想获取有些锁时,假诺此时线程A获取到了锁,而线程B唯有在守候,那么对线程B调用threadB.interrupt()方法可以中断线程B的等候历程。

出于lockInterruptibly()的扬言中抛出了拾分,所以lock.lockInterruptibly()必须放在try块中或然在调用lockInterruptibly()的不二法门外注解抛出InterruptedException。

由此lockInterruptibly()一般的利用形式如下:

图片 9图片 10

public void method() throws InterruptedException {
    lock.lockInterruptibly();
    try {  
     //.....
    }
    finally {
        lock.unlock();
    }  
}

View Code

专注,当二个线程获取了锁之后,是不会被interrupt()方法中断的。因为自个儿在前边的篇章中讲过单独调用interrupt()方法不能暂停正在运作进程中的线程,只可以中断阻塞进程中的线程。

据此当通过lockInterruptibly()方法获得有个别锁时,假若不可能得到到,唯有进行等待的气象下,是足以响应中断的。

而用synchronized修饰的话,当叁个线程处于等候某些锁的情事,是力不从心被搁浅的,唯有一贯等待下去。

 

2.ReentrantLock

ReentrantLock,意思是“可重入锁”,关于可重入锁的定义在下一节讲述。ReentrantLock是绝无仅有兑现了Lock接口的类,并且ReentrantLock提供了越多的法门。上边通过有些实例看现实看一下什么接纳ReentrantLock。

图片 11图片 12

例子1,lock()的正确使用方法
public class Test {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    public static void main(String[] args)  {
        final Test test = new Test();

        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();

        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
    }  

    public void insert(Thread thread) {
        Lock lock = new ReentrantLock();    //注意这个地方
        lock.lock();
        try {
            System.out.println(thread.getName()+"得到了锁");
            for(int i=0;i<5;i++) {
                arrayList.add(i);
            }
        } catch (Exception e) {
            // TODO: handle exception
        }finally {
            System.out.println(thread.getName()+"释放了锁");
            lock.unlock();
        }
    }
}
输出结果是什么?
Thread-0得到了锁
Thread-1得到了锁
Thread-0释放了锁
Thread-1释放了锁

也许有朋友会问,怎么会输出这个结果?第二个线程怎么会在第一个线程释放锁之前得到了锁?原因在于,在insert方法中的lock变量是局部变量,每个线程执行该方法时都会保存一个副本,那么理所当然每个线程执行到lock.lock()处获取的是不同的锁,所以就不会发生冲突。

==============================================
知道了原因改起来就比较容易了,只需要将lock声明为类的属性即可。
public class Test {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private Lock lock = new ReentrantLock();    //注意这个地方
    public static void main(String[] args)  {
        final Test test = new Test();

        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();

        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
    }  

    public void insert(Thread thread) {
        lock.lock();
        try {
            System.out.println(thread.getName()+"得到了锁");
            for(int i=0;i<5;i++) {
                arrayList.add(i);
            }
        } catch (Exception e) {
            // TODO: handle exception
        }finally {
            System.out.println(thread.getName()+"释放了锁");
            lock.unlock();
        }
    }
}
这样就是正确地使用Lock的方法了。


例子2,tryLock()的使用方法
public class Test {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private Lock lock = new ReentrantLock();    //注意这个地方
    public static void main(String[] args)  {
        final Test test = new Test();

        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();

        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
    }  

    public void insert(Thread thread) {
        if(lock.tryLock()) {
            try {
                System.out.println(thread.getName()+"得到了锁");
                for(int i=0;i<5;i++) {
                    arrayList.add(i);
                }
            } catch (Exception e) {
                // TODO: handle exception
            }finally {
                System.out.println(thread.getName()+"释放了锁");
                lock.unlock();
            }
        } else {
            System.out.println(thread.getName()+"获取锁失败");
        }
    }
}
输出结果:
Thread-0得到了锁
Thread-1获取锁失败
Thread-0释放了锁

例子3,lockInterruptibly()响应中断的使用方法:
public class Test {
    private Lock lock = new ReentrantLock();   
    public static void main(String[] args)  {
        Test test = new Test();
        MyThread thread1 = new MyThread(test);
        MyThread thread2 = new MyThread(test);
        thread1.start();
        thread2.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.interrupt();
    }  

    public void insert(Thread thread) throws InterruptedException{
        lock.lockInterruptibly();   //注意,如果需要正确中断等待锁的线程,必须将获取锁放在外面,然后将InterruptedException抛出
        try {  
            System.out.println(thread.getName()+"得到了锁");
            long startTime = System.currentTimeMillis();
            for(    ;     ;) {
                if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
                    break;
                //插入数据
            }
        }
        finally {
            System.out.println(Thread.currentThread().getName()+"执行finally");
            lock.unlock();
            System.out.println(thread.getName()+"释放了锁");
        }  
    }
}

class MyThread extends Thread {
    private Test test = null;
    public MyThread(Test test) {
        this.test = test;
    }
    @Override
    public void run() {

        try {
            test.insert(Thread.currentThread());
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+"被中断");
        }
    }
}
运行之后,发现thread2能够被正确中断。

View Code

3.ReadWriteLock

ReadWriteLock也是三个接口,在它其中只定义了多个措施:

图片 13图片 14

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading.
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing.
     */
    Lock writeLock();
}

View Code

一个用来博取读锁,1个用来收获写锁。也便是说将文件的读写操作分开,分成1个锁来分配给线程,从而使得多少个线程能够而且举办读操作。上面包车型大巴ReentrantReadWriteLock实现了ReadWriteLock接口。

4.ReentrantReadWriteLock

ReentrantReadWriteLock里面提供了许多抬高的法门,可是最重庆大学的有七个主意:readLock()和writeLock()用来获取读锁和写锁。

上边通过多少个例证来看一下ReentrantReadWriteLock具体用法。

①倘诺有多少个线程要同时展开读操作的话,先看一下synchronized达到的功力:

图片 15图片 16

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 synchronized void get(Thread thread) {
        long start = System.currentTimeMillis();
        while(System.currentTimeMillis() - start <= 1) {
            System.out.println(thread.getName()+"正在进行读操作");
        }
        System.out.println(thread.getName()+"读操作完毕");
    }
}

这段程序的输出结果会是,直到thread1执行完读操作之后,才会打印thread2执行读操作的信息。
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0读操作完毕
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1读操作完毕

View Code

②而改成用读写锁的话:

图片 17图片 18

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();
        }
    }
}

此时打印的结果为:
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0读操作完毕
Thread-1读操作完毕

View Code

表达thread1和thread2在同时举行读操作。

那般就大大升级了读操作的频率。

然则要留心的是,固然有四个线程已经占据了读锁,则此时其余线程借使要提请写锁,则申请写锁的线程会一向等待释放读锁。

设若有3个线程已经占据了写锁,则此时其余线程要是申请写锁或然读锁,则申请的线程会一贯守候释放写锁。

5.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。所以说,在切实运用时要依照适当意况选取。

三.锁的连带概念介绍

1.可重入锁

图片 19图片 20

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

看下面这段代码就明白了:
class MyClass {
    public synchronized void method1() {
        method2();
    }

    public synchronized void method2() {

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

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

View Code

2.可暂停锁

图片 21图片 22

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

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

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

在前面演示lockInterruptibly()的用法时已经体现了Lock的可中断性。

View Code

3.公平锁

公允锁即尽量以请求锁的逐一来得到锁。比就像是有四个线程在等候贰个锁,当那么些锁被释放时,等待时间最久的线程(初阶请求的线程)会取得该所,那种正是公平锁。

非公平锁即不可能确认保证锁的获得是比照请求锁的各样举办的。那样就只怕导致某些或许有个别线程永远获取不到锁。

在Java中,synchronized便是非公平锁,它不能够保障等待的线程获取锁的各种。

而对此ReentrantLock和ReentrantReadWriteLock,它暗中同意情形下是非公平锁,可是足以设置为正义锁。

看一下那三个类的源代码就知晓了:

图片 23

在ReentrantLock中定义了一个静态内部类,3个是NotFairSync,3个是FairSync,分别用来促成非公平锁和正义锁。

大家得以在创设ReentrantLock对象时,通过以下措施来设置锁的公平性:

ReentrantLock lock = new ReentrantLock(true);

借使参数为true表示为公平锁,为fasle为非公平锁。私下认可情状下,假使使用无参构造器,则是非公平锁。

图片 24

其它在ReentrantLock类中定义了累累方法,比如:

isFair()       
//判断锁是或不是是公平锁

isLocked()   
//判断锁是不是被别的线程获取了

isHeldByCurrentThread()  
//判断锁是还是不是被当下线程获取了

hasQueuedThreads()  
//判断是或不是有线程在等候该锁

在ReentrantReadWriteLock中也有接近的措施,同样也足以设置为正义锁和非公平锁。可是要切记,ReentrantReadWriteLock并未达成Lock接口,它完成的是ReadWriteLock接口。

4.读写锁

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

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

ReadWriteLock便是读写锁,它是叁个接口,ReentrantReadWriteLock落成了这一个接口。

能够由此readLock()获取读锁,通过writeLock()获取写锁。

相关文章