一、什么是锁?
在Java中,锁是用来控制多线程访问共享资源的机制。通过使用锁,可以确保在同一时刻只有一个线程能够访问共享资源,从而避免并发访问导致的数据不一致性和竞争条件问题。
二、synchronized锁
Java里面的锁主要有synchronized,reentrantLock,Lock三种。
synchronized关键字是Java语言提供的原生锁机制,可以用来修饰方法或代码块,实现对关键代码段的同步访问。当一个线程获取了某个对象的synchronized锁时,其他线程将会被阻塞,直到当前线程释放了该锁。
synchronized是Java中最基本的锁机制,可以用于实现对代码块或方法的同步。它属于独占锁、悲观锁、可重入锁、非公平锁。
-
独占锁
:独占锁是一种只允许一个线程获取锁的锁机制,也称为排他锁。当一个线程获取了独占锁后,其他线程必须等待该线程释放锁才能获取锁。独占锁可以保证临界区代码的原子性和线程安全性。 -
悲观锁
:悲观锁是一种假设会发生并发冲突的锁机制,它认为在临界区代码执行期间会有其他线程来竞争锁。因此,在使用悲观锁时,线程会先获取锁,然后再执行临界区代码,以确保数据的一致性和完整性。 -
可重入锁
:可重入锁是指一个线程可以多次获取同一把锁,而不会出现死锁的情况。在可重入锁中,线程每次获取锁时,会记录获取的次数,只有当释放锁的次数和获取锁的次数相等时,才会真正释放锁。 -
非公平锁
:非公平锁是一种不保证线程获取锁的顺序的锁机制。在非公平锁中,当有多个线程竞争同一把锁时,锁会随机分配给其中一个线程,而不考虑等待时间或优先级。相对于公平锁,非公平锁可能会导致某些线程长时间无法获取锁,但可以提高系统的吞吐量。
java
public class SynchronizedExample {
private int count = 0;
public synchronized void increment() {
count++;
}
public static void main(String[] args) {
SynchronizedExample example = new SynchronizedExample();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: " + example.count);
}
}
在上面的示例中,increment方法使用synchronized关键字修饰,确保了count变量的安全访问。两个线程分别调用increment方法来递增count变量,并最终输出结果为2000。
由于increment()方法使用了synchronized关键字修饰,当一个线程进入increment()方法时,会获取example对象的监视器锁,其他线程必须等待该线程释放锁后才能继续执行。这样可以保证对count变量的操作是线程安全的,避免了多线程并发访问导致的数据不一致问题。
三、ReentrantLock锁
ReentrantLock锁是Java中提供的显示锁实现,具有更灵活的锁定机制。与synchronized相比,ReentrantLock提供了更多的方法和功能,如可中断锁、尝试获取锁、超时获取锁、公平锁等。它继承了Lock接口,属于可重入锁、悲观锁、独占锁、互斥锁、同步锁。
ReentrantLock可以通过lock()方法获取锁,通过unlock()方法释放锁,也支持公平锁和非公平锁的机制。
java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockExample example = new ReentrantLockExample();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: " + example.count);
}
}
- ReentrantLockExample类定义了一个私有变量count用来存储计数值,并创建了一个ReentrantLock实例lock用于同步访问count。
- increment()方法是一个线程安全的方法,通过调用lock.lock()获取锁,然后对count进行自增操作,最后在finally块中释放锁。
- 在main方法中,创建了一个ReentrantLockExample实例example。
- 创建两个线程t1和t2,分别对example的increment()方法进行1000次递增操作。
- 启动线程t1和t2,然后使用t1.join()和t2.join()等待两个线程执行完成。
- 最后输出example的count值,即两个线程共同递增后的结果。
通过使用ReentrantLock,可以确保对共享资源的访问是线程安全的,避免了多个线程同时访问导致的数据竞争问题。通过lock和unlock方法来实现对count变量的安全访问。这种方式相比synchronized更为灵活,可以根据具体需求进行更多的控制。
四、Lock接口
Lock是Java中锁的通用接口,定义了锁的基本操作方法,如获取锁、释放锁、获取条件等。ReentrantLock实现了Lock接口,可以替代synchronized关键字进行同步操作。
java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
LockExample example = new LockExample();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
example.increment();
}
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + example.count);
}
}
在这段代码中,我们定义了一个LockExample类,其中包含一个count变量和一个ReentrantLock对象lock。在increment()方法中,我们使用lock.lock()获取锁,对count进行自增操作,然后使用lock.unlock()释放锁。
在main方法中,我们创建了5个线程,每个线程对LockExample对象的increment()方法进行1000次调用。最后输出最终的count值,确保多个线程对count的操作是同步的。
五、Lock和syncronized的区别
1.实现方式
- synchronized是Java语言的关键字,是在语言层面提供的原生支持,可以直接在方法或代码块上使用。
- Lock是一个接口,位于java.util.concurrent.locks包下,提供了更加灵活的锁定机制,需要通过其实现类(如ReentrantLock)来使用。
2.获取锁的方式
- synchronized是隐式获取锁的方式,当一个线程进入synchronized代码块或方法时,会自动获取对象的监视器锁。
- Lock接口提供了显式获取锁和释放锁的方法,例如lock()和unlock(),需要手动控制获取和释放锁的过程。
3.可中断性
- synchronized在获取锁时无法被中断,即线程在等待获取锁时无法被中断。
- Lock接口提供了可以响应中断的锁获取方式,可以在等待锁的过程中响应中断。
4.性能
通常情况下,synchronized的性能会比Lock接口的实现类(如ReentrantLock)要好,因为synchronized是在JVM层面进行优化的。 但是在某些情况下,特别是在高并发环境下,使用Lock接口可能会比synchronized更加高效。
5.可重入性
- synchronized是可重入的,同一个线程可以多次获取同一个对象的监视器锁。
- Lock接口的实现类(如ReentrantLock)也是可重入的,同一个线程可以多次获取同一个锁。
6.可重入性灵活性
- Lock接口提供了更多的灵活性和功能,例如设置超时时间、支持公平锁或非公平锁等,适用于复杂的线程同步需求。
- synchronized是一种简单且方便的线程同步机制,在一般情况下使用起来更加方便。
六、最后的话
通过合理使用锁机制,可以保证多线程程序的正确性和效率。通过锁机制确保了共享资源的安全访问。在多线程编程中和在实际开发中,应根据具体情况选择合适的锁技术,以确保多线程程序的稳定性和性能。
能力一般,水平有限,本文可能存在纰漏或错误,如有问题欢迎指正,感谢你阅读这篇文章,如果你觉得写得还行的话,不要忘记点赞、评论、收藏哦!祝生活愉快!