目录
锁的概念
锁在多线程环境中的作用是:
在Java中,常见的锁机制有以下几种:
形成死锁的条件
用java写一个死锁
如何避免死锁?
锁的概念
首先我们要明确锁是什么,在Java语言中,锁(Lock)是一种用于控制多个线程对共享资源进行访问的机制。它允许线程以互斥的方式对共享资源进行操作,以避免并发访问引发的数据损坏或不一致性。
简而言之,就是会将资源锁住,然后其他的线程就无法正常调用到该资源
锁在多线程环境中的作用是:
-
互斥性(Mutual Exclusion):当一个线程获得锁后,其他线程将无法获得相同的锁,它们将被阻塞并等待直到锁被释放。只有一个线程能够同时获得该锁,从而确保对共享资源的独占访问。
-
可见性(Visibility):锁的获取和释放的过程也会导致确保对共享变量的修改在不同线程之间可见。通过使用锁,在释放锁之前,线程将所做的更改刷新到共享内存中,以确保其他线程可以看到这些更改。
在Java中,常见的锁机制有以下几种:
-
synchronized关键字:用于修饰方法或代码块,可以实现对方法或代码块的互斥访问。
-
ReentrantLock类:是Java.util.concurrent包中提供的一个可重入锁实现,通过lock()和unlock()方法来控制锁的获取和释放。
-
ReadWriteLock接口:提供了读-写分离的锁,通过多个读锁可以并发读取,而只有一个写锁可以独占写入。这样可以提高读操作的性能。
锁的正确使用可以保证线程安全,防止竞态条件和数据不一致的问题。然而,错误的锁使用可能导致死锁现象,即多个线程相互等待对方持有的锁,从而导致程序无法继续执行。因此,在多线程编程中,锁的使用需要谨慎,并遵循良好的锁使用规范。
形成死锁的条件
两个或者多个线程相互等待
举一个例子:
线程1先执行上锁A 等待两秒
线程2执行锁住B 等待锁住A之后 释放B 进入等待
线程1停止等待,开始锁B 发现B已经被锁了 进入等待
这样两个线程相互等待对方释放资源才能够继续执行,因此导致了死锁
用java写一个死锁
public class Test {
private static final String A = "A";
private static final String B = "B";
public static void main(String[] args) {
new Test().test();
}
private void test(){
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (A){
try {
Thread.sleep(2000);
System.out.println("线程休眠结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B){
System.out.println("线程1执行完毕!");
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (B){
System.out.println("B已经被锁住");
synchronized (A){
System.out.println("线程2执行完毕!");
}
}
}
});
thread1.start();
thread2.start();
}
}
在执行之前先看下CPU的状态
运行代码之后
在发生死锁的情况下,因为涉及到的线程无法继续执行和释放资源,它们可能会一直占用CPU资源,导致CPU利用率的上升。由于线程所占用的资源(如堆栈空间)也无法被释放,这可能会导致内存的占用持续增长,而不会被回收。
之后按下win+r 调出cmd (以管理员身份运行)
输入一下命令查看当前java进程
findstr java.exe
可以看到当前有两个进程,但是通过这个并不能判断出是哪个进程出现了死锁
通过 一下命令查看这两个进程的详细信息
jstask <pid>
可以看到340这个进程出现了以下信息
如何避免死锁?
避免一个线程同时获取多个锁。
·避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
·尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
·对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。