Java提供了synchronized为什么还要提供Lock

发布时间:2021-11-24 15:56:15 作者:iii
来源:亿速云 阅读:184

Java提供了synchronized为什么还要提供Lock

引言

在Java多线程编程中,线程安全是一个非常重要的话题。为了保证多个线程能够正确地访问共享资源,Java提供了多种同步机制,其中最常见的就是synchronized关键字和java.util.concurrent.locks.Lock接口。尽管synchronized关键字已经能够满足大部分的同步需求,但Java仍然提供了Lock接口及其实现类(如ReentrantLock)。那么,为什么Java在已经有了synchronized的情况下,还要提供Lock呢?本文将从多个角度探讨这个问题。

1. synchronized的局限性

1.1 无法中断等待的线程

synchronized关键字的一个主要局限性是,当一个线程因为获取不到锁而进入阻塞状态时,它无法被中断。也就是说,如果一个线程在等待获取锁的过程中,其他线程无法通过调用interrupt()方法来中断它。这可能会导致一些线程长时间处于阻塞状态,影响系统的响应性。

public class SynchronizedExample {
    private final Object lock = new Object();

    public void doSomething() {
        synchronized (lock) {
            // 长时间操作
        }
    }
}

在上面的例子中,如果一个线程在synchronized块中执行了一个长时间的操作,其他线程将无法中断它,只能一直等待。

1.2 无法尝试获取锁

synchronized关键字要求线程在获取锁时,必须一直等待,直到成功获取锁为止。这种机制在某些场景下可能不够灵活。例如,在某些情况下,我们可能希望线程在尝试获取锁失败后,立即返回或执行其他操作,而不是一直等待。

public class SynchronizedExample {
    private final Object lock = new Object();

    public void doSomething() {
        synchronized (lock) {
            // 操作
        }
    }
}

在上面的例子中,如果一个线程无法获取锁,它将一直阻塞,直到锁可用为止。

1.3 无法实现公平锁

synchronized关键字无法实现公平锁。公平锁是指多个线程在竞争锁时,按照请求锁的顺序来获取锁。而synchronized关键字采用的是非公平锁机制,即线程获取锁的顺序是不确定的,可能会导致某些线程长时间无法获取锁。

public class SynchronizedExample {
    private final Object lock = new Object();

    public void doSomething() {
        synchronized (lock) {
            // 操作
        }
    }
}

在上面的例子中,多个线程在竞争锁时,获取锁的顺序是不确定的,可能会导致某些线程长时间无法获取锁。

1.4 无法实现读写锁

synchronized关键字无法区分读操作和写操作。在某些场景下,读操作是可以并发执行的,而写操作则需要互斥执行。synchronized关键字无法实现这种读写分离的锁机制。

public class SynchronizedExample {
    private final Object lock = new Object();

    public void read() {
        synchronized (lock) {
            // 读操作
        }
    }

    public void write() {
        synchronized (lock) {
            // 写操作
        }
    }
}

在上面的例子中,读操作和写操作都需要互斥执行,无法实现读操作的并发执行。

2. Lock接口的优势

2.1 可中断的锁获取

Lock接口提供了lockInterruptibly()方法,允许线程在等待锁的过程中响应中断。这意味着,如果一个线程在等待锁的过程中被中断,它可以立即抛出InterruptedException并退出等待状态。

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

public class LockExample {
    private final Lock lock = new ReentrantLock();

    public void doSomething() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            // 长时间操作
        } finally {
            lock.unlock();
        }
    }
}

在上面的例子中,如果一个线程在等待锁的过程中被中断,它将立即抛出InterruptedException并退出等待状态。

2.2 尝试获取锁

Lock接口提供了tryLock()方法,允许线程尝试获取锁。如果锁可用,则立即获取锁并返回true;如果锁不可用,则立即返回false,而不是一直等待。

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

public class LockExample {
    private final Lock lock = new ReentrantLock();

    public void doSomething() {
        if (lock.tryLock()) {
            try {
                // 操作
            } finally {
                lock.unlock();
            }
        } else {
            // 执行其他操作
        }
    }
}

在上面的例子中,如果一个线程无法获取锁,它将立即返回并执行其他操作,而不是一直等待。

2.3 公平锁

Lock接口的实现类ReentrantLock提供了公平锁的选项。通过将ReentrantLock的构造函数参数设置为true,可以实现公平锁机制,即多个线程在竞争锁时,按照请求锁的顺序来获取锁。

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

public class LockExample {
    private final Lock lock = new ReentrantLock(true);

    public void doSomething() {
        lock.lock();
        try {
            // 操作
        } finally {
            lock.unlock();
        }
    }
}

在上面的例子中,多个线程在竞争锁时,将按照请求锁的顺序来获取锁,避免了某些线程长时间无法获取锁的情况。

2.4 读写锁

Lock接口的实现类ReentrantReadWriteLock提供了读写锁机制。读写锁允许多个线程同时进行读操作,但在写操作时需要互斥执行。这种机制可以显著提高读操作的并发性能。

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

public class LockExample {
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void read() {
        rwLock.readLock().lock();
        try {
            // 读操作
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public void write() {
        rwLock.writeLock().lock();
        try {
            // 写操作
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}

在上面的例子中,读操作可以并发执行,而写操作则需要互斥执行,从而提高了读操作的并发性能。

3. Lock接口的其他优势

3.1 条件变量

Lock接口提供了newCondition()方法,可以创建Condition对象。Condition对象类似于Objectwait()notify()方法,但提供了更灵活的线程等待和唤醒机制。通过Condition对象,可以实现更复杂的线程同步逻辑。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockExample {
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    public void await() throws InterruptedException {
        lock.lock();
        try {
            condition.await();
        } finally {
            lock.unlock();
        }
    }

    public void signal() {
        lock.lock();
        try {
            condition.signal();
        } finally {
            lock.unlock();
        }
    }
}

在上面的例子中,await()方法使当前线程等待,signal()方法唤醒等待的线程。通过Condition对象,可以实现更复杂的线程同步逻辑。

3.2 锁的可重入性

Lock接口的实现类ReentrantLock是可重入锁。可重入锁允许同一个线程多次获取同一个锁,而不会导致死锁。这种机制在某些复杂的同步场景下非常有用。

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

public class LockExample {
    private final Lock lock = new ReentrantLock();

    public void outer() {
        lock.lock();
        try {
            inner();
        } finally {
            lock.unlock();
        }
    }

    public void inner() {
        lock.lock();
        try {
            // 操作
        } finally {
            lock.unlock();
        }
    }
}

在上面的例子中,outer()方法和inner()方法都可以获取同一个锁,而不会导致死锁。

3.3 锁的监控

Lock接口的实现类ReentrantLock提供了getHoldCount()isHeldByCurrentThread()等方法,可以监控锁的状态。这些方法在某些调试和分析场景下非常有用。

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

public class LockExample {
    private final Lock lock = new ReentrantLock();

    public void doSomething() {
        lock.lock();
        try {
            System.out.println("Hold count: " + ((ReentrantLock) lock).getHoldCount());
            System.out.println("Is held by current thread: " + ((ReentrantLock) lock).isHeldByCurrentThread());
            // 操作
        } finally {
            lock.unlock();
        }
    }
}

在上面的例子中,getHoldCount()方法返回当前线程持有锁的次数,isHeldByCurrentThread()方法返回当前线程是否持有锁。

4. synchronizedLock的选择

尽管Lock接口提供了更多的功能和灵活性,但在某些场景下,synchronized关键字仍然是更好的选择。synchronized关键字的优点是简单易用,不需要显式地释放锁,且在某些情况下性能更好。因此,在选择使用synchronized还是Lock时,需要根据具体的需求和场景进行权衡。

4.1 简单场景

在简单的同步场景下,synchronized关键字通常是更好的选择。例如,当只需要保护一个简单的临界区时,synchronized关键字的简洁性和易用性使其成为首选。

public class SynchronizedExample {
    private final Object lock = new Object();

    public void doSomething() {
        synchronized (lock) {
            // 操作
        }
    }
}

在上面的例子中,doSomething()方法只需要保护一个简单的临界区,使用synchronized关键字更加简洁。

4.2 复杂场景

在复杂的同步场景下,Lock接口通常是更好的选择。例如,当需要实现可中断的锁获取、尝试获取锁、公平锁、读写锁等功能时,Lock接口提供了更多的灵活性和功能。

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

public class LockExample {
    private final Lock lock = new ReentrantLock();

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

在上面的例子中,doSomething()方法需要实现可中断的锁获取,使用Lock接口更加合适。

5. 总结

Java提供了synchronized关键字和Lock接口两种同步机制,它们各有优缺点。synchronized关键字简单易用,但在某些复杂的同步场景下存在局限性。Lock接口提供了更多的功能和灵活性,能够满足更复杂的同步需求。因此,在选择使用synchronized还是Lock时,需要根据具体的需求和场景进行权衡。

在实际开发中,建议在简单的同步场景下使用synchronized关键字,而在复杂的同步场景下使用Lock接口。通过合理选择和使用同步机制,可以有效地提高多线程程序的性能和可靠性。

推荐阅读:
  1. 备案要提供什么
  2. bootstrap框架提供了哪些内容

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

synchronized java lock

上一篇:如何进行Spark Shuffle实现

下一篇:java的http请求工具类HttpClientUtils怎么使用

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》