一、Synchronized简介
一句话说出synchronized关键字的作用
能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的作用。
synchronized的地位
- synchronized是Java的关键字,被Java语言原生支持
- 是最基本的互斥同步手段
- 是并发编程中的元老级角色,是并发编程的必学内容
二、不使用并发手段会有什么后果
在两个线程中对同一个变量进行++操作,执行10万次,代码如下:
public class DisappearRequest1 implements Runnable{static DisappearRequest1 instance = new DisappearRequest1();static int i = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(instance);Thread t2 = new Thread(instance);t1.start();t2.start();t1.join();t2.join();System.out.println(i);}@Overridepublic void run() {for (int j = 0; j < 100000; j++) {i ++ ;}}}
output:133951
我们会发现其执行结果并不符合我们的预期 那为什么会出现这样的结果呢?
原因
i++ 它看上去只是一个操作,实际上包含了三个操作:
- 读取i
- 将i+1
- 将i的值写入内存中
三、Synchronized的两种用法
3.1、对象锁
包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己指定锁对象)
3.1.1 方法锁
方法锁形式:synchronized修饰普通方法,锁对象默认为this
示例代码:
public class SynchronizedObjectMethod3 implements Runnable{static SynchronizedObjectMethod3 instance = new SynchronizedObjectMethod3();public static void main(String[] args) {Thread t1 = new Thread(instance);Thread t2 = new Thread(instance);t1.start();t2.start();while (t1.isAlive() || t2.isAlive()){}System.out.println("finished");}@Overridepublic void run() {method();}public synchronized void method() {System.out.println("我是对象锁的方法锁形式,我叫:" + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "执行完毕");}}
output:
我是对象锁的方法锁形式,我叫:Thread-0Thread-0执行完毕我是对象锁的方法锁形式,我叫:Thread-1Thread-1执行完毕finished
3.1.2 代码块锁
代码块形式:手动指定锁对象
示例代码: 首先这里我们指定this为锁对象
public class SynchronizedObjectCodeBlock2 implements Runnable{static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();@Overridepublic void run() {synchronized (this) {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(instance);Thread t2 = new Thread(instance);t1.start();t2.start();while (t1.isAlive() || t2.isAlive()){}System.out.println("finished");}}
output:
我是线程Thread-0Thread-0执行完毕我是线程Thread-1Thread-1执行完毕finished
接下来我们通过new一个Object来作为锁对象来使用:
public class SynchronizedObjectCodeBlock2 implements Runnable{static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();Object lock = new Object();@Overridepublic void run() {synchronized (lock) {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(instance);Thread t2 = new Thread(instance);t1.start();t2.start();while (t1.isAlive() || t2.isAlive()){}System.out.println("finished");}}
output:
我是线程Thread-0Thread-0执行完毕我是线程Thread-1Thread-1执行完毕finished
接下来我们new两个Object来分别作为两个代码块的锁对象:
public class SynchronizedObjectCodeBlock2 implements Runnable{static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();Object lock1 = new Object();Object lock2 = new Object();@Overridepublic void run() {synchronized (lock1) {System.out.println("我是lock1," + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "执行完毕,我是lock1");}synchronized (lock2) {System.out.println("我是lock2," + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "执行完毕,我是lock2");}}public static void main(String[] args) {Thread t1 = new Thread(instance);Thread t2 = new Thread(instance);t1.start();t2.start();while (t1.isAlive() || t2.isAlive()){}System.out.println("finished");}}
output:
我是lock1,Thread-0Thread-0执行完毕,我是lock1我是lock2,Thread-0我是lock1,Thread-1Thread-1执行完毕,我是lock1Thread-0执行完毕,我是lock2我是lock2,Thread-1Thread-1执行完毕,我是lock2finished
3.2、类锁
指synchronized修饰静态的方法或指定锁为Class对象,Java类可能有很多个对象,但只有一个Class对象,所谓的类锁,不过是Class对象的锁而已,类锁只能在同一时刻被一个对象拥有。
共有两种形式的类锁:
形式一、synchronized加在static方法上
形式二、synchronized(*.class)代码块
3.2.1 第一种形式类锁
示例代码:
public class SynchronizedClassStatic4 implements Runnable {static SynchronizedClassStatic4 instance1 = new SynchronizedClassStatic4();static SynchronizedClassStatic4 instance2 = new SynchronizedClassStatic4();public static void main(String[] args) {Thread t1 = new Thread(instance1);Thread t2 = new Thread(instance2);t1.start();t2.start();while (t1.isAlive() || t2.isAlive()) {}System.out.println("finished");}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() + "执行完毕");}@Overridepublic void run() {method();}}
output:
我是类锁的第一种形式,我叫Thread-0Thread-0执行完毕我是类锁的第一种形式,我叫Thread-1Thread-1执行完毕finished
3.2.2 第二种形式类锁
示例代码:
public class SynchronizedClassClass5 implements Runnable{static SynchronizedClassClass5 instance1 = new SynchronizedClassClass5();static SynchronizedClassClass5 instance2 = new SynchronizedClassClass5();public static void main(String[] args) {Thread t1 = new Thread(instance1);Thread t2 = new Thread(instance2);t1.start();t2.start();while (t1.isAlive() || t2.isAlive()) {}System.out.println("finished");}private void method() {synchronized (SynchronizedClassClass5.class) {System.out.println("我是类锁的第二种形式,我叫:" + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "执行完毕");}}@Overridepublic void run() {method();}}
output:
我是类锁的第二种形式,我叫:Thread-0Thread-0执行完毕我是类锁的第二种形式,我叫:Thread-1Thread-1执行完毕finished
四、多线程访问同步方法的7种情况
- 两个线程同时访问一个对象的同步方法
- 两个线程访问的是两个对象的同步方法
- 两个线程访问的是synchronized的静态方法
- 同时访问同步方法和非同步方法
- 访问同一个对象的不同的普通同步方法
- 同时访问静态synchronized和非静态synchronized方法
- 方法抛异常后,会释放锁
