当线程执行到同步代码块时,会尝试获取锁对象的锁,以确保代码块在同一时间只能被一个线程执行。

1. 问题引入

同时开启两个线程,对同一个资源进行操作,线程之间互相干扰

什么是线程安全问题?

多线程同时修改同一资源,线程之间互相干扰,就会产生线程安全问题

public class ThreadSafe1 implements Runnable{  
  
    public static int count = 100;  
  
    @Override  
    public void run() {  
        if (count > 1) {  
            try {  
                // 模拟延时,造成资源争抢  
                Thread.sleep(30);  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            }  
            count--;  
            System.out.println(Thread.currentThread().getName() + ": 当前count值为" + count);  
        }  
    }  
  
    public static void main(String[] args) {  
        ThreadSafe1 threadSafe = new ThreadSafe1();  
        new Thread(threadSafe).start();  
        new Thread(threadSafe).start();  
    }  
}

2. synchronized 基本使用方法

2.1 代码块

synchronized(锁对象) {
	同步代码
}

锁对象可以是:

  • this: 当前实例对象的锁
  • class: 当前类的锁(静态同步)
  • 其他对象: 任何其他对象的锁
    Object objectLock = new Object();
synchronized (this) {  
    // 同步代码
}
private Object objectLock = new Object();
synchronized (objectLock) {  
    // 同步代码
}
public class ThreadSafe2 implements Runnable{  
  
    public static int count = 100;  
  
    private Object objectLock = new Object();  
  
    @Override  
    public void run() {   
        try {  
            Thread.sleep(30);  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
        if (count > 1) {  
            // this锁  
			synchronized (this) {  
			    count--;  
			    System.out.println(Thread.currentThread().getName() + ": 当前count值为" + count);  
			}  
// -------------------------------------------  
            // 对象锁  
//               synchronized (objectLock) {  
//                   count--;  
//                   System.out.println(Thread.currentThread().getName() + ": 当前count值为" + count);  
//               }  
        }  
    }
	public static void main(String[] args) {  
        ThreadSafe2 threadSafe = new ThreadSafe2();  
        new Thread(threadSafe).start();  
        new Thread(threadSafe).start();   
    }  
}

2.2 方法

  1. 普通方法

在普通方法上使用synchronized关键字,相当于使用了this锁

private synchronized void call() {  
	// 同步代码
}

// 相当于
private void call() {
	synchronized(this) {
		// 同步代码
	}
}
  1. 静态方法

在静态方法上使用synchronized关键字,相当于使用了class锁

private synchronized static void call() {  
	// 同步代码
}
// 相当于
private void call() {
	synchronized(ThreadSafe.class) {
		// 同步代码
	}
}

3. 锁对象

锁对象就是提供锁的对象,如果存在多个就无法保证线程安全。

在下面的代码中,实例化了两个ThreadSafe类,由于同步代码块使用的时this锁,线程会去找各自的实例this来获取锁,也就说两个线程都拿到了锁,因此会出现线程安全问题。

public class ThreadSafe implements Runnable{  
  
    public static int count = 100;  
  
    @Override  
    public void run() {  
        if (count > 1) {  
            try {  
                // 模拟延时,造成资源争抢  
                Thread.sleep(30);  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            }  
            synchronized (this) {  
			    count--;  
			    System.out.println(Thread.currentThread().getName() + ": 当前count值为" + count);  
			} 
        }  
    }  
  
    public static void main(String[] args) {  
        ThreadSafe threadSafe = new ThreadSafe();  
		ThreadSafe threadSafe_ = new ThreadSafe();  
		new Thread(threadSafe).start();  
		new Thread(threadSafe_).start();
    }  
}

4. 死锁问题

当线程间互相持有彼此需要的资源,且彼此都不释放对方所需资源,就会造成死锁问题

以下面的代码为例进行分析:

  1. 当多线程执行时,会出现这种情况,两个线程同时执行if语句的两个分支
  2. 可以
    • 假定线程A进入了true分支,获取到了this锁,现在将执行a()方法
    • 假定线程B进入了false分支,获取到了lock锁,现在将执行b()方法
  3. 同时
    • 线程A执行a()方法,需要获取lock锁,而lock锁被线程B持有
    • 线程B执行b()方法,需要获取this锁,而this锁被线程B持有
  4. 线程AB互相持有彼此需要的锁,且都没有设置超时时间,无法释放,这就造成了死锁
public class ThreadSafe implements Runnable{  
  
    private int count = 0;  
  
    private static final Object lock = new Object();  
  
    @Override  
    public void run() {  
        while (true) {  
            count++;  
            if (count%2 == 0) {  
                synchronized (this) {  
                    a();  
                }  
            } else {  
                synchronized (lock) {  
                    b();  
                }  
            }  
        }  
    }  
  
    void a() {  
        synchronized (lock) {  
            System.out.println(Thread.currentThread().getName() + ":a()");  
        }  
    }  
  
    void b() {  
        synchronized (this) {  
            System.out.println(Thread.currentThread().getName() + ":b()");  
        }  
    }  
  
    public static void main(String[] args) {  
        ThreadSafe3 threadSafe = new ThreadSafe();  
        new Thread(threadSafe).start();  
        new Thread(threadSafe).start();  
    }  
}