Java多线程之内置锁与显示锁

简介:

Java中具有通过Synchronized实现的内置锁,和ReentrantLock实现的显示锁,这两种锁各有各的好处,算是互有补充,今天就来做一个总结。

Synchronized

内置锁获得锁和释放锁是隐式的,进入synchronized修饰的代码就获得锁,走出相应的代码就释放锁。

synchronized(list){ //获得锁
    list.append();
    list.count();
}//释放锁

通信

与Synchronized配套使用的通信方法通常有wait(),notify()。

wait()方法会立即释放当前锁,并进入等待状态,等待到相应的notify并重新获得锁过后才能继续执行;notify()不会立刻立刻释放锁,必须要等notify()所在线程执行完synchronized块中的所有代码才会释放。用如下代码来进行验证:

public static void main(String[] args){
    List list = new LinkedList();
    Thread r = new Thread(new ReadList(list));
    Thread w = new Thread(new WriteList(list));
    r.start();
    w.start();
}
class ReadList implements Runnable{

    private List list;

    public ReadList(List list){ this.list = list; }

    @Override
    public void run(){
        System.out.println("ReadList begin at "+System.currentTimeMillis());
        synchronized (list){
            try {
                Thread.sleep(1000);
                System.out.println("list.wait() begin at "+System.currentTimeMillis());
                list.wait();
                System.out.println("list.wait() end at "+System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("ReadList end at "+System.currentTimeMillis());

    }
}

class WriteList implements Runnable{

    private List list;

    public WriteList(List list){ this.list = list; }

    @Override
    public void run(){
        System.out.println("WriteList begin at "+System.currentTimeMillis());
        synchronized (list){
            System.out.println("get lock at "+System.currentTimeMillis());
            list.notify();
            System.out.println("list.notify() at "+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("get out of block at "+System.currentTimeMillis());
        }
        System.out.println("WriteList end at "+System.currentTimeMillis());

    }
}

运行结果

ReadList begin at 1493650526582
WriteList begin at 1493650526582
list.wait() begin at 1493650527584
get lock at 1493650527584
list.notify() at 1493650527584
get out of block at 1493650529584
WriteList end at 1493650529584
list.wait() end at 1493650529584
ReadList end at 1493650529584

可见读线程开始运行,开始wait过后,写线程才获得锁;写线程走出同步块而不是notify过后,读线程才wait结束,亦即获得锁。所以notify不会释放锁,wait会释放锁。值得一提的是,notifyall()会通知等待队列中的所有线程。

编码

编码模式比较简单,单一,不必显示的获得锁,释放锁,能降低因粗心忘记释放锁的错误。使用模式如下:

synchronized(object){ 

}

灵活性

  • 内置锁在进入同步块时,采取的是无限等待的策略,一旦开始等待,就既不能中断也不能取消,容易产生饥饿与死锁的问题
  • 在线程调用notify方法时,会随机选择相应对象的等待队列的一个线程将其唤醒,而不是按照FIFO的方式,如果有强烈的公平性要求,比如FIFO就无法满足

性能

Synchronized在JDK1.5及之前性能(主要指吞吐率)比较差,扩展性也不如ReentrantLock。但是JDK1.6以后,修改了管理内置锁的算法,使得Synchronized和标准的ReentrantLock性能差别不大。

ReentrantLock

ReentrantLock是显示锁,需要显示进行 lock 以及 unlock 操作。

通信

与ReentrantLock搭配的通行方式是Condition,如下:

private Lock lock = new ReentrantLock();  
private Condition condition = lock.newCondition(); 
condition.await();//this.wait();  
condition.signal();//this.notify();  
condition.signalAll();//this.notifyAll();

Condition是被绑定到Lock上的,必须使用lock.newCondition()才能创建一个Condition。从上面的代码可以看出,Synchronized能实现的通信方式,Condition都可以实现,功能类似的代码写在同一行中。而Condition的优秀之处在于它可以为多个线程间建立不同的Condition,比如对象的读/写Condition,队列的空/满Condition,在JDK源码中的ArrayBlockingQueue中就使用了这个特性:

 public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}
public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length)
            notFull.await();
        enqueue(e);
    } finally {
        lock.unlock();
    }
}
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}
private void enqueue(E x) {
    // assert lock.getHoldCount() == 1;
    // assert items[putIndex] == null;
    final Object[] items = this.items;
    items[putIndex] = x;
    if (++putIndex == items.length)
        putIndex = 0;
    count++;
    notEmpty.signal();
}
private E dequeue() {
    // assert lock.getHoldCount() == 1;
    // assert items[takeIndex] != null;
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    E x = (E) items[takeIndex];
    items[takeIndex] = null;
    if (++takeIndex == items.length)
        takeIndex = 0;
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    notFull.signal();
    return x;
}

编码

Lock lock = new ReentrantLock();
lock.lock();
try{

}finally{
    lock.unlock();
}

相比于Synchronized要复杂一些,而且一定要记得在finally中释放锁而不是其他地方,这样才能保证即使出了异常也能释放锁。

灵活性

  • lock.lockInterruptibly() 可以使得线程在等待锁是支持响应中断;lock.tryLock() 可以使得线程在等待一段时间过后如果还未获得锁就停止等待而非一直等待。有了这两种机制就可以更好的制定获得锁的重试机制,而非盲目一直等待,可以更好的避免饥饿和死锁问题
  • ReentrantLock可以成为公平锁(非默认的),所谓公平锁就是锁的等待队列的FIFO,不过公平锁会带来性能消耗,如果不是必须的不建议使用。这和CPU对指令进行重排序的理由是相似的,如果强行的按照代码的书写顺序来执行指令,就会浪费许多时钟周期,达不到最大利用率

性能

虽然Synchronized和标准的ReentrantLock性能差别不大,但是ReentrantLock还提供了一种非互斥的读写锁,
也就是不强制每次最多只有一个线程能持有锁,它会避免“读/写”冲突,“写/写”冲突,但是不会排除“读/读”冲突,
因为“读/读”并不影响数据的完整性,所以可以多个读线程同时持有锁,这样在读写比较高的情况下,性能会有很大的提升。

下面用两种锁分别实现的线程安全的linkedlist:

class RWLockList {//读写锁

    private List list;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock readLock = lock.readLock();
    private final Lock writeLock = lock.writeLock();

    public RWLockList(List list){this.list = list;}

    public int get(int k) {
        readLock.lock();
        try {
            return (int)list.get(k);
        } finally {
            readLock.unlock();
        }
    }

    public void put(int value) {
        writeLock.lock();
        try {
            list.add(value);
        } finally {
            writeLock.unlock();
        }
    }
}

class SyncList  {

    private List list;

    public SyncList(List list){this.list = list;}

    public synchronized int  get(int k){
        return (int)list.get(k);
    }

    public synchronized void put(int value){
        list.add(value);
    }

}

读写锁测试代码:

List list = new LinkedList();
for (int i=0;i<10000;i++){
    list.add(i);
}
RWLockList rwLockList = new RWLockList(list);//初始化数据

Thread writer = new Thread(new Runnable() {
    @Override     public void run() {
        for (int i=0;i<10000;i++){
            rwLockList.put(i);
        }
    }
});
Thread reader1 = new Thread(new Runnable() {
    @Override     public void run() {
        for (int i=0;i<10000;i++){
            rwLockList.get(i);
        }
    }
});
Thread reader2 = new Thread(new Runnable() {
    @Override     public void run() {
        for (int i=0;i<10000;i++){
            rwLockList.get(i);
        }
    }
});
long begin = System.currentTimeMillis();
writer.start();reader1.start();reader2.start();
try {
    writer.join();
    reader1.join();
    reader2.join();
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("RWLockList take "+(System.currentTimeMillis()-begin) + "ms");

同步锁测试代码:

List list = new LinkedList();
for (int i=0;i<10000;i++){
    list.add(i);
}
SyncList syncList = new SyncList(list);//初始化数据
Thread writerS = new Thread(new Runnable() {
    @Override     public void run() {
        for (int i=0;i<10000;i++){
            syncList.put(i);
        }
    }
});
Thread reader1S = new Thread(new Runnable() {
    @Override     public void run() {
        for (int i=0;i<10000;i++){
            syncList.get(i);
        }
    }
});
Thread reader2S = new Thread(new Runnable() {
    @Override     public void run() {
        for (int i=0;i<10000;i++){
            syncList.get(i);
        }
    }
});
long begin1 = System.currentTimeMillis();
writerS.start();reader1S.start();reader2S.start();
try {
    writerS.join();
    reader1S.join();
    reader2S.join();
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("SyncList take "+(System.currentTimeMillis()-begin1) + "ms");

结果:

RWLockList take 248ms
RWLockList take 255ms
RWLockList take 249ms
RWLockList take 224ms

SyncList take 351ms
SyncList take 367ms
SyncList take 315ms
SyncList take 323ms

可见读写锁的确是优于纯碎的互斥锁

总结

内置锁最大优点是简洁易用,显示锁最大优点是功能丰富,所以能用内置锁就用内置锁,在内置锁功能不能满足之时在考虑显示锁。

关于两种锁,目前接触到的就是这么多,总结不到位之处,欢迎拍砖。


作者:邱康singasong

来源:51CTO

相关文章
|
19小时前
|
缓存 Java
【Java基础】简说多线程(上)
【Java基础】简说多线程(上)
5 0
|
1天前
|
并行计算 算法 安全
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
|
1天前
|
安全 Java 编译器
是时候来唠一唠synchronized关键字了,Java多线程的必问考点!
本文简要介绍了Java中的`synchronized`关键字,它是用于保证多线程环境下的同步,解决原子性、可见性和顺序性问题。从JDK1.6开始,synchronized进行了优化,性能得到提升,现在仍可在项目中使用。synchronized有三种用法:修饰实例方法、静态方法和代码块。文章还讨论了synchronized修饰代码块的锁对象、静态与非静态方法调用的互斥性,以及构造方法不能被同步修饰。此外,通过反汇编展示了`synchronized`在方法和代码块上的底层实现,涉及ObjectMonitor和monitorenter/monitorexit指令。
6 0
|
1天前
|
监控 安全 Java
在Java中如何优雅的停止一个线程?可别再用Thread.stop()了!
在Java中如何优雅的停止一个线程?可别再用Thread.stop()了!
7 2
|
1天前
|
Java 调度
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
8 1
|
1天前
|
安全 Java
Java基础教程(15)-多线程基础
【4月更文挑战第15天】Java内置多线程支持,通过Thread类或Runnable接口实现。线程状态包括New、Runnable、Blocked、Waiting、Timed Waiting和Terminated。启动线程调用start(),中断线程用interrupt(),同步用synchronized关键字。线程安全包如java.util.concurrent提供并发集合和原子操作。线程池如ExecutorService简化任务管理,Callable接口允许返回值,Future配合获取异步结果。Java 8引入CompletableFuture支持回调。
|
2天前
|
安全 Java 调度
Java线程:深入理解与实战应用
Java线程:深入理解与实战应用
15 0
|
2天前
|
Java
Java中的并发编程:理解和应用线程池
【4月更文挑战第23天】在现代的Java应用程序中,性能和资源的有效利用已经成为了一个重要的考量因素。并发编程是提高应用程序性能的关键手段之一,而线程池则是实现高效并发的重要工具。本文将深入探讨Java中的线程池,包括其基本原理、优势、以及如何在实际开发中有效地使用线程池。我们将通过实例和代码片段,帮助读者理解线程池的概念,并学习如何在Java应用中合理地使用线程池。
|
6天前
|
安全 Java
深入理解 Java 多线程和并发工具类
【4月更文挑战第19天】本文探讨了Java多线程和并发工具类在实现高性能应用程序中的关键作用。通过继承`Thread`或实现`Runnable`创建线程,利用`Executors`管理线程池,以及使用`Semaphore`、`CountDownLatch`和`CyclicBarrier`进行线程同步。保证线程安全、实现线程协作和性能调优(如设置线程池大小、避免不必要同步)是重要环节。理解并恰当运用这些工具能提升程序效率和可靠性。