1、synchronized的概念
synchronized 是 Java 中的关键字,是利用锁的机制来实现同步的。
锁机制有如下两种特性:
-
互斥性:即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问。互斥性我们也往往称为操作的原子性。
-
可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。
2、synchronized的特性
- 一把锁只能同时被一个线程获取,没有获得锁的线程只能阻塞等待
- synchronized修饰的方法,无论方法正常执行完毕还是抛出异常,都会释放锁
- 每个实例都对应有自己的一把锁(this),不同实例之间互不影响;例如锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象公用同一把锁
3、synchronized锁的用法
3.1 对象锁
包括方法锁(默认锁对象为this,当前实例对象)和同步代码块锁(自己指定锁对象)
也就是说对象锁,可以自己指定锁定任意一个对象,也可以直接锁定当前对象。
3.1.1 代码块形式:手动指定锁定对象,也可是是this,也可以是自定义的锁
- 示例1:同步代码块形式——锁为this
public class SynchronizedObjectLock implements Runnable{
private static SynchronizedObjectLock instance = new SynchronizedObjectLock();
@Override
public void run() {
/**
* 同步代码块形式——锁为this
* 两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行
*/
synchronized (this) {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(Duration.ofSeconds(3).toMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
}
}
- 示例2:手动指定锁定对象
public class SynchronizedObjectLock implements Runnable {
private static SynchronizedObjectLock instance = new SynchronizedObjectLock();
// 创建2把锁
Object block1 = new Object();
Object block2 = new Object();
@Override
public void run() {
/**
* 这个代码块使用的是第一把锁,
* 当他释放后,后面的代码块由于使用的是第二把锁,因此可以马上执行
*/
synchronized (block1) {
System.out.println("block1锁,我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(Duration.ofSeconds(3).toMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("block1锁," + Thread.currentThread().getName() + "结束");
}
synchronized (block2) {
System.out.println("block2锁,我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(Duration.ofSeconds(3).toMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("block2锁," + Thread.currentThread().getName() + "结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
}
}
3.1.2 方法锁形式:synchronized修饰普通方法,锁对象默认为this
- 示例:
public class SynchronizedObjectLock implements Runnable {
private static SynchronizedObjectLock instance = new SynchronizedObjectLock();
@Override
public void run() {
method();
}
public synchronized void method() {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(Duration.ofSeconds(3).toMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
}
}
输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
3.2 类锁
synchronized用在普通方法上,默认的锁就是this,当前实例;
synchronized用在静态的方法上,锁对象为Class对象(假设类名为User,锁对象就是User.class )
3.2.1 synchronize修饰静态方法
示例:
public class SynchronizedObjectLock implements Runnable {
private static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();
private static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();
@Override
public void run() {
method();
}
/**
* synchronized用在静态方法上,默认的锁就是当前所在的Class类,
* 所以无论是哪个线程访问它,需要的锁都只有一把
*/
public synchronized static void method() {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
public static void main(String[] args) {
//因为 method 方法是静态的, 虽然 instance1 和 instance2是2个不同的对象
//但是用的锁是SynchronizedObjectLock.class, 是同一把锁, 所以代码不会串行
//如果 method 方法不是静态的, synchronized用的锁就是当前this对象,属于不同的锁,代码就可以串行
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
}
}
3.2.2 synchronized指定锁对象为Class对象
示例:
public class SynchronizedObjectLock implements Runnable {
private static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();
private static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();
@Override
public void run() {
// 所有线程需要的锁都是同一把
synchronized (SynchronizedObjectLock.class) {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
}
}
上面的例子中,锁对象是 SynchronizedObjectLock.class,所以代码不能串行。
运行结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束