前言
上一次,我们讲到了一些线程安全的问题,以及导致线程不安全的原因,这一次,我们来讲解,如何来线程变得更安全。
使用synchoronized加锁
首先说明 synchoronized
不是一个类,也不是一个方法,他是一个关键字,类似于static,可以用于修饰,代码块,方法等等
使用方法
-
修饰普通的方法
- 使用synchronized的时候,本质上是在针对某个对象进行加锁
- 此时锁对象就是 this
-
修饰一个代码块
- 需要显示指定针对哪个对象加锁,不同的锁对象,可能会产生不一样的效果
- 需要显示指定针对哪个对象加锁,不同的锁对象,可能会产生不一样的效果
-
修饰一个静态方法
- 针对当前类的类对象加锁,也就是xx.class (反射)
volatile关键字
使用synchoronized
不同的是不是一个锁,而是一种标记,被修饰的变量,是所谓"不稳定的",也就是说,可能会发生变化,如果不及时的感知到变量的变化,可能会导致线程安全问题,观察图片总结volatile
的作用,让程序对此变量"敏感"
不可随意使用锁!!!!
死锁
产生死锁的条件
- 互斥使用:一个锁被一个线程占用了以后,其他线程无法占用
- 不可抢占:一个锁被一个线程占用了以后,其他的线程不能把这个锁给抢走
- 请求和保持:当一个线程占据了多把锁以后,除非显示的释放锁,否则这些锁始终都是该被线程持有的
- 环路等待: 等待关系成环了
死锁案例
以下是一个很简单的死锁的案例,在这个案例中,我们先创建一个DeadLock
对象,并创建两个线程,各自调用lockA
和 lockB
方法
在lockA
方法中,先是取得了锁a
然后 睡眠一会(不然执行的太快,还没开始竞争就结束了),然后等线程睡醒了以后,就会试图去取得锁b
,但是这个时候,锁b
已经被B线程取走了,所以这个时候A
线程就会去等待B
线程释放锁,但是这个时候B
线程也在等待A
线程释放锁,这个时候,就形成了死锁,以下是是实现代码,以及 运行截图
package org.example.thread;
class DeadLock{
public Object a = new Object();
public Object b = new Object();
public void lockA(){
synchronized (a){
System.out.println("得到锁A1");
try {
Thread.sleep(3000);
System.out.println("睡好了 我要获得锁1!");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b){
System.out.println("得到锁B1");
}
System.out.println("我即将释放锁1");
}
}
public void lockB(){
synchronized (b){
System.out.println("得到锁B2");
try {
Thread.sleep(3000);
System.out.println("睡好了 我要获得锁2!");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a){
System.out.println("得到锁A2");
}
System.out.println("我即将释放锁2");
}
}
}
public class Demo27 {
public static void main(String[] args) {
DeadLock d = new DeadLock();
new Thread(d::lockA).start();
new Thread(d::lockB).start();
}
}
如何解决死锁?
想要解决死锁,主要还是要从第四点切入,也就是,避免环路等待,避免出现
也就是说
- 如果需要多把锁相互配合,那么加锁时,规定好固定的顺序,所有的线程都遵守这个顺序去进行加锁,就不会出现换路等待
- 尽量少的使用嵌套锁
上述代码就可以优化为
package org.example.thread;
class DeadLock2{
public Object a = new Object();
public Object b = new Object();
public void lockA(){
synchronized (a){
System.out.println("得到锁A1");
try {
Thread.sleep(3000);
System.out.println("睡好了 我要获得锁1!");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b){
System.out.println("得到锁B1");
}
System.out.println("我即将释放锁1");
}
}
public void lockB(){
synchronized (a){
System.out.println("得到锁B2");
try {
Thread.sleep(3000);
System.out.println("睡好了 我要获得锁2!");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b){
System.out.println("得到锁A2");
}
System.out.println("我即将释放锁2");
}
}
}
public class Demo28 {
public static void main(String[] args) {
DeadLock2 d = new DeadLock2();
Thread A = new Thread(d::lockA);
Thread B = new Thread(d::lockB);
A.start();
B.start();
}
}
运行结果如下
标准库中的线程安全问题
在Java标准库中,不是所有的类,都是线程安全的,如果再多线程的情况下使用这些类,可能会出现一些问题
- 线程不安全的
- ArrayList
- LinkedList
- HashMap
- TreeMap
- HashSet
- TreeSet
- StringBuilder
- 线程安全的 也就是在一些可能出现线程安全问题的方法上 使用了synchoronized关键字
- Vector
- HashTable
- ConcurrentHashMap
- StringBuffer
- String (不包含synchoronized关键字,因为String本身是不可变对象)