synchronized 关键字是 Java 中解决并发问题的一种常用方法,也是最简单的一种方法,其作用有三个:
(1)互斥性:确保线程互斥的访问同步代码
(2)可见性:保证共享变量的修改能够及时可见
(3)有序性:有效解决重排序问题, 其用法也有三个:
- 修饰实例方法
- 修饰静态方法
- 修饰代码块
修饰实例方法
public class SynchronizedInstanceMethod implements Runnable{//共享资源(临界资源)static int i=0;//如果没有synchronized关键字,输出小于20000public synchronized void increase(){i++;}public void run() {for(int j=0;j<10000;j++){increase();}}public static void main(String[] args) throws InterruptedException {Thread1 t=new Thread1();Thread t1=new Thread(t);Thread t2=new Thread(t);t1.start();t2.start();t1.join();//主线程等待t1执行完毕t2.join();//主线程等待t2执行完毕System.out.println(i);}/*** 输出结果:* 20000*/
}
修饰静态方法
public class SynchronizedStaticMethod {//共享资源(临界资源)static int i = 0;//如果没有synchronized关键字,输出小于20000public static synchronized void increase() {i++;}public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(new Runnable() {public void run() {for (int j = 0; j < 10000; j++) {increase();}}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {for (int j = 0; j < 10000; j++) {increase();}}});t1.start();t2.start();t1.join();//主线程等待t1执行完毕t2.join();//主线程等待t2执行完毕System.out.println(i);}/*** 输出结果:* 20000*/
}
修饰代码块
public class SynchronizedCodeBlock implements Runnable{//共享资源(临界资源)static int i=0;@Overridepublic void run() {for(int j=0;j<10000;j++){//获得了String的类锁synchronized (String.class){i++;}}}public static void main(String[] args) throws InterruptedException {SynchronizedCodeBlock t=new SynchronizedCodeBlock();Thread t1=new Thread(t);Thread t2=new Thread(t);t1.start();t2.start();t1.join();t2.join();System.out.println(i);}/*** 输出结果:* 20000*/
}
总结
- synchronized 修饰的实例方法,多线程并发访问时,只能有一个线程进入,获得对象内置锁,其他线程阻塞等待,但在此期间线程仍然可以访问其他方法。
- synchronized 修饰的静态方法,多线程并发访问时,只能有一个线程进入,获得类锁,其他线程阻塞等待,但在此期间线程仍然可以访问其他方法。
- synchronized 修饰的代码块,多线程并发访问时,只能有一个线程进入,根据括号中的对象或者是类,获得相应的对象内置锁或者是类锁
- 每个类都有一个类锁,类的每个对象也有一个内置锁,它们是互不干扰的,也就是说一个线程可以同时获得类锁和该类实例化对象的内置锁,当线程访问非 synchronzied 修饰的方法时,并不需要获得锁,因此不会产生阻塞。
当修饰静态方法的时候,锁定的是当前类的 Class 对象;当修饰非静态方法的时候,锁定的是当前实例对象 this。
class SafeCalc {static long value = 0L;synchronized long get() {return value;}synchronized static void addOne() {value += 1;}
}
受保护资源和锁之间的关联关系是 N:1 的关系。还拿前面球赛门票的管理来类比,就是一个座位,我们只能用一张票来保护,如果多发了重复的票,那就要打架了。现实世界里,我们可以用多把锁来保护同一个资源,但在并发领域是不行的,并发领域的锁和现实世界的锁不是完全匹配的。不过倒是可以用同一把锁来保护多个资源,这个对应到现实世界就是我们所谓的“包场”了。
上边代码中,value 改成静态变量,把 addOne() 方法改成静态方法,此时 get() 方法和 addOne() 方法是否存在并发问题呢?