线程安全是并发程序的根基,若不能保证执行结果的正确性,那么程序并发也没什么意义。
关键字synchronized
关键字 synchronized 的作用是实现线程之间的同步。它的工作是对同步的代码加锁。
关键字 synchronized 有多种用法:
- 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
- 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
- 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
指定加锁对象
package com.demo.base;public class ThreadDemo {public static void main(String[] args) throws InterruptedException {MyRunnable myRunnable = new MyRunnable();Thread t1 = new Thread(myRunnable, "T1");Thread t2 = new Thread(myRunnable, "T2");t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + myRunnable.count);}}class MyRunnable implements Runnable {private Object o = new Object();int count = 0;@Overridepublic void run() {for(int i=0; i<100000; i++){synchronized (this.o) {count++;}}}}
直接作用于实例方法
package com.demo.base;public class ThreadDemo {public static void main(String[] args) throws InterruptedException {MyRunnable myRunnable = new MyRunnable();Thread t1 = new Thread(myRunnable, "T1");Thread t2 = new Thread(myRunnable, "T2");t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + myRunnable.count);}}class MyRunnable implements Runnable {int count = 0;@Overridepublic void run() {for(int i=0; i<100000; i++){task();}}private synchronized void task(){count++;}}
直接作用于静态方法
package com.demo.base;public class ThreadDemo {public static void main(String[] args) throws InterruptedException {MyRunnable myRunnable = new MyRunnable();Thread t1 = new Thread(myRunnable, "T1");Thread t2 = new Thread(myRunnable, "T2");t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + myRunnable.count);}}class MyRunnable implements Runnable {static int count = 0;@Overridepublic void run() {for(int i=0; i<100000; i++){task();}}private static synchronized void task(){count++;}}
注意
- 关键字 synchronized 除了用于线程安全,还可以保证线程间的可见性和有序性,在作用上可以完成代替关键字 volatile,但关键字 synchronized 会使代码块进入同步执行即串行执行,会影响程序的执行效率。
- 为了尽量提高程序的执行效率,在使用关键字 synchronized 时,尽量缩小同步代码块的大小。
synchronized锁可重入
synchronized锁可重入指的就是当一个线程持有了某一个对象锁,这时该线程就可以直接进入该对象锁的其他同步代码块,可重入最大的作用是避免死锁。死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {Runnable runnable = new Runnable(){@Overridepublic void run() {synchronized (this){System.out.println(Thread.currentThread().getName() + "线程抢到了锁");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}a();}}public synchronized void a(){System.out.println(Thread.currentThread().getName() + "线程执行a方法");}};for(int i=0; i<100; i++){new Thread(runnable).start();}}}
synchronized锁误区
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {Runnable runnable = new Runnable(){private Integer num = 0;@Overridepublic void run() {num++;synchronized (num){System.out.println(Thread.currentThread().getName() + "线程抢到了锁");System.out.println(Thread.currentThread().getName() + "线程释放了锁");}}};for(int i=0; i<100; i++){new Thread(runnable).start();}}}
实例变量num是Integer包装类对象,每次num++之后,本质已经与之前的num不是同一个对象了,不同线程进入同步代码块时申请的也就不是同一个对象锁了。
