线程安全是并发程序的根基,若不能保证执行结果的正确性,那么程序并发也没什么意义。

关键字synchronized

关键字 synchronized 的作用是实现线程之间的同步。它的工作是对同步的代码加锁。
关键字 synchronized 有多种用法:

  • 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
  • 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
  • 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。

指定加锁对象

  1. package com.demo.base;
  2. public class ThreadDemo {
  3. public static void main(String[] args) throws InterruptedException {
  4. MyRunnable myRunnable = new MyRunnable();
  5. Thread t1 = new Thread(myRunnable, "T1");
  6. Thread t2 = new Thread(myRunnable, "T2");
  7. t1.start();
  8. t2.start();
  9. t1.join();
  10. t2.join();
  11. System.out.println("count = " + myRunnable.count);
  12. }
  13. }
  14. class MyRunnable implements Runnable {
  15. private Object o = new Object();
  16. int count = 0;
  17. @Override
  18. public void run() {
  19. for(int i=0; i<100000; i++){
  20. synchronized (this.o) {
  21. count++;
  22. }
  23. }
  24. }
  25. }

直接作用于实例方法

  1. package com.demo.base;
  2. public class ThreadDemo {
  3. public static void main(String[] args) throws InterruptedException {
  4. MyRunnable myRunnable = new MyRunnable();
  5. Thread t1 = new Thread(myRunnable, "T1");
  6. Thread t2 = new Thread(myRunnable, "T2");
  7. t1.start();
  8. t2.start();
  9. t1.join();
  10. t2.join();
  11. System.out.println("count = " + myRunnable.count);
  12. }
  13. }
  14. class MyRunnable implements Runnable {
  15. int count = 0;
  16. @Override
  17. public void run() {
  18. for(int i=0; i<100000; i++){
  19. task();
  20. }
  21. }
  22. private synchronized void task(){
  23. count++;
  24. }
  25. }

直接作用于静态方法

  1. package com.demo.base;
  2. public class ThreadDemo {
  3. public static void main(String[] args) throws InterruptedException {
  4. MyRunnable myRunnable = new MyRunnable();
  5. Thread t1 = new Thread(myRunnable, "T1");
  6. Thread t2 = new Thread(myRunnable, "T2");
  7. t1.start();
  8. t2.start();
  9. t1.join();
  10. t2.join();
  11. System.out.println("count = " + myRunnable.count);
  12. }
  13. }
  14. class MyRunnable implements Runnable {
  15. static int count = 0;
  16. @Override
  17. public void run() {
  18. for(int i=0; i<100000; i++){
  19. task();
  20. }
  21. }
  22. private static synchronized void task(){
  23. count++;
  24. }
  25. }

注意

  • 关键字 synchronized 除了用于线程安全,还可以保证线程间的可见性和有序性,在作用上可以完成代替关键字 volatile,但关键字 synchronized 会使代码块进入同步执行即串行执行,会影响程序的执行效率。
  • 为了尽量提高程序的执行效率,在使用关键字 synchronized 时,尽量缩小同步代码块的大小。

synchronized锁可重入

synchronized锁可重入指的就是当一个线程持有了某一个对象锁,这时该线程就可以直接进入该对象锁的其他同步代码块,可重入最大的作用是避免死锁。死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

  1. public class ThreadDemo {
  2. public static void main(String[] args) throws InterruptedException {
  3. Runnable runnable = new Runnable(){
  4. @Override
  5. public void run() {
  6. synchronized (this){
  7. System.out.println(Thread.currentThread().getName() + "线程抢到了锁");
  8. try {
  9. Thread.sleep(100);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. a();
  14. }
  15. }
  16. public synchronized void a(){
  17. System.out.println(Thread.currentThread().getName() + "线程执行a方法");
  18. }
  19. };
  20. for(int i=0; i<100; i++){
  21. new Thread(runnable).start();
  22. }
  23. }
  24. }

synchronized锁误区

  1. public class ThreadDemo {
  2. public static void main(String[] args) throws InterruptedException {
  3. Runnable runnable = new Runnable(){
  4. private Integer num = 0;
  5. @Override
  6. public void run() {
  7. num++;
  8. synchronized (num){
  9. System.out.println(Thread.currentThread().getName() + "线程抢到了锁");
  10. System.out.println(Thread.currentThread().getName() + "线程释放了锁");
  11. }
  12. }
  13. };
  14. for(int i=0; i<100; i++){
  15. new Thread(runnable).start();
  16. }
  17. }
  18. }

实例变量num是Integer包装类对象,每次num++之后,本质已经与之前的num不是同一个对象了,不同线程进入同步代码块时申请的也就不是同一个对象锁了。