一、Synchronized简介

一句话说出synchronized关键字的作用
能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的作用。

synchronized的地位

  • synchronized是Java的关键字,被Java语言原生支持
  • 是最基本的互斥同步手段
  • 是并发编程中的元老级角色,是并发编程的必学内容

二、不使用并发手段会有什么后果

在两个线程中对同一个变量进行++操作,执行10万次,代码如下:

  1. public class DisappearRequest1 implements Runnable{
  2. static DisappearRequest1 instance = new DisappearRequest1();
  3. static int i = 0;
  4. public static void main(String[] args) throws InterruptedException {
  5. Thread t1 = new Thread(instance);
  6. Thread t2 = new Thread(instance);
  7. t1.start();
  8. t2.start();
  9. t1.join();
  10. t2.join();
  11. System.out.println(i);
  12. }
  13. @Override
  14. public void run() {
  15. for (int j = 0; j < 100000; j++) {
  16. i ++ ;
  17. }
  18. }
  19. }

output:
133951
我们会发现其执行结果并不符合我们的预期 那为什么会出现这样的结果呢?

原因
i++ 它看上去只是一个操作,实际上包含了三个操作:

  1. 读取i
  2. 将i+1
  3. 将i的值写入内存中

三、Synchronized的两种用法

3.1、对象锁

包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己指定锁对象)

3.1.1 方法锁

方法锁形式:synchronized修饰普通方法,锁对象默认为this
示例代码:

  1. public class SynchronizedObjectMethod3 implements Runnable{
  2. static SynchronizedObjectMethod3 instance = new SynchronizedObjectMethod3();
  3. public static void main(String[] args) {
  4. Thread t1 = new Thread(instance);
  5. Thread t2 = new Thread(instance);
  6. t1.start();
  7. t2.start();
  8. while (t1.isAlive() || t2.isAlive()){}
  9. System.out.println("finished");
  10. }
  11. @Override
  12. public void run() {
  13. method();
  14. }
  15. public synchronized void method() {
  16. System.out.println("我是对象锁的方法锁形式,我叫:" + Thread.currentThread().getName());
  17. try {
  18. Thread.sleep(3000);
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. System.out.println(Thread.currentThread().getName() + "执行完毕");
  23. }
  24. }

output:

  1. 我是对象锁的方法锁形式,我叫:Thread-0
  2. Thread-0执行完毕
  3. 我是对象锁的方法锁形式,我叫:Thread-1
  4. Thread-1执行完毕
  5. finished

3.1.2 代码块锁

代码块形式:手动指定锁对象
示例代码: 首先这里我们指定this为锁对象

  1. public class SynchronizedObjectCodeBlock2 implements Runnable{
  2. static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();
  3. @Override
  4. public void run() {
  5. synchronized (this) {
  6. System.out.println("我是线程" + Thread.currentThread().getName());
  7. try {
  8. Thread.sleep(3000);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. System.out.println(Thread.currentThread().getName() + "执行完毕");
  13. }
  14. }
  15. public static void main(String[] args) {
  16. Thread t1 = new Thread(instance);
  17. Thread t2 = new Thread(instance);
  18. t1.start();
  19. t2.start();
  20. while (t1.isAlive() || t2.isAlive()){}
  21. System.out.println("finished");
  22. }
  23. }

output:

  1. 我是线程Thread-0
  2. Thread-0执行完毕
  3. 我是线程Thread-1
  4. Thread-1执行完毕
  5. finished

接下来我们通过new一个Object来作为锁对象来使用:

  1. public class SynchronizedObjectCodeBlock2 implements Runnable{
  2. static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();
  3. Object lock = new Object();
  4. @Override
  5. public void run() {
  6. synchronized (lock) {
  7. System.out.println("我是线程" + Thread.currentThread().getName());
  8. try {
  9. Thread.sleep(3000);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println(Thread.currentThread().getName() + "执行完毕");
  14. }
  15. }
  16. public static void main(String[] args) {
  17. Thread t1 = new Thread(instance);
  18. Thread t2 = new Thread(instance);
  19. t1.start();
  20. t2.start();
  21. while (t1.isAlive() || t2.isAlive()){}
  22. System.out.println("finished");
  23. }
  24. }

output:

  1. 我是线程Thread-0
  2. Thread-0执行完毕
  3. 我是线程Thread-1
  4. Thread-1执行完毕
  5. finished

接下来我们new两个Object来分别作为两个代码块的锁对象:

  1. public class SynchronizedObjectCodeBlock2 implements Runnable{
  2. static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();
  3. Object lock1 = new Object();
  4. Object lock2 = new Object();
  5. @Override
  6. public void run() {
  7. synchronized (lock1) {
  8. System.out.println("我是lock1," + Thread.currentThread().getName());
  9. try {
  10. Thread.sleep(3000);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. System.out.println(Thread.currentThread().getName() + "执行完毕,我是lock1");
  15. }
  16. synchronized (lock2) {
  17. System.out.println("我是lock2," + Thread.currentThread().getName());
  18. try {
  19. Thread.sleep(3000);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. System.out.println(Thread.currentThread().getName() + "执行完毕,我是lock2");
  24. }
  25. }
  26. public static void main(String[] args) {
  27. Thread t1 = new Thread(instance);
  28. Thread t2 = new Thread(instance);
  29. t1.start();
  30. t2.start();
  31. while (t1.isAlive() || t2.isAlive()){}
  32. System.out.println("finished");
  33. }
  34. }

output:

  1. 我是lock1Thread-0
  2. Thread-0执行完毕,我是lock1
  3. 我是lock2Thread-0
  4. 我是lock1Thread-1
  5. Thread-1执行完毕,我是lock1
  6. Thread-0执行完毕,我是lock2
  7. 我是lock2Thread-1
  8. Thread-1执行完毕,我是lock2
  9. finished

3.2、类锁

指synchronized修饰静态的方法或指定锁为Class对象,Java类可能有很多个对象,但只有一个Class对象,所谓的类锁,不过是Class对象的锁而已,类锁只能在同一时刻被一个对象拥有。
共有两种形式的类锁:
形式一、synchronized加在static方法上
形式二、synchronized(*.class)代码块

3.2.1 第一种形式类锁

示例代码:

  1. public class SynchronizedClassStatic4 implements Runnable {
  2. static SynchronizedClassStatic4 instance1 = new SynchronizedClassStatic4();
  3. static SynchronizedClassStatic4 instance2 = new SynchronizedClassStatic4();
  4. public static void main(String[] args) {
  5. Thread t1 = new Thread(instance1);
  6. Thread t2 = new Thread(instance2);
  7. t1.start();
  8. t2.start();
  9. while (t1.isAlive() || t2.isAlive()) {}
  10. System.out.println("finished");
  11. }
  12. public synchronized static void method() {
  13. System.out.println("我是类锁的第一种形式,我叫" + Thread.currentThread().getName());
  14. try {
  15. Thread.sleep(3000);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. System.out.println(Thread.currentThread().getName() + "执行完毕");
  20. }
  21. @Override
  22. public void run() {
  23. method();
  24. }
  25. }

output:

  1. 我是类锁的第一种形式,我叫Thread-0
  2. Thread-0执行完毕
  3. 我是类锁的第一种形式,我叫Thread-1
  4. Thread-1执行完毕
  5. finished

3.2.2 第二种形式类锁

示例代码:

  1. public class SynchronizedClassClass5 implements Runnable{
  2. static SynchronizedClassClass5 instance1 = new SynchronizedClassClass5();
  3. static SynchronizedClassClass5 instance2 = new SynchronizedClassClass5();
  4. public static void main(String[] args) {
  5. Thread t1 = new Thread(instance1);
  6. Thread t2 = new Thread(instance2);
  7. t1.start();
  8. t2.start();
  9. while (t1.isAlive() || t2.isAlive()) {}
  10. System.out.println("finished");
  11. }
  12. private void method() {
  13. synchronized (SynchronizedClassClass5.class) {
  14. System.out.println("我是类锁的第二种形式,我叫:" + Thread.currentThread().getName());
  15. try {
  16. Thread.sleep(3000);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. System.out.println(Thread.currentThread().getName() + "执行完毕");
  21. }
  22. }
  23. @Override
  24. public void run() {
  25. method();
  26. }
  27. }

output:

  1. 我是类锁的第二种形式,我叫:Thread-0
  2. Thread-0执行完毕
  3. 我是类锁的第二种形式,我叫:Thread-1
  4. Thread-1执行完毕
  5. finished

四、多线程访问同步方法的7种情况

  1. 两个线程同时访问一个对象的同步方法
  2. 两个线程访问的是两个对象的同步方法
  3. 两个线程访问的是synchronized的静态方法
  4. 同时访问同步方法和非同步方法
  5. 访问同一个对象的不同的普通同步方法
  6. 同时访问静态synchronized和非静态synchronized方法
  7. 方法抛异常后,会释放锁