1.Synchronized使用范围:
-
同步普通方法:锁的是当前对象
1 //包含synchronized修饰的同步方法的类addCountClass 2 public class addCountClass { 3 4 private int count = 0; 5 6 synchronized public void addCount(String user) 7 { 8 try 9 {10 if(user.equals("a")) 11 {12 count = 100;13 System.out.println("a set count = 100,over");14 Thread.sleep(3000);15 }else 16 {17 count = 200;18 System.out.println("b set count = 200,over");19 }20 System.out.println(user+" count = "+count);21 22 } catch (Exception e) {23 e.printStackTrace();24 }25 26 }27 }
//线程类Apublic class ThreadA extends Thread { private addCountClass addcount1; public ThreadA(addCountClass addcount) { super(); this.addcount1 = addcount; } public void run() { super.run(); addcount1.addCount("a"); }}
//线程类Bpublic class ThreadB extends Thread { private addCountClass addcount2; public ThreadB(addCountClass addcount) { super(); this.addcount2 = addcount; } public void run() { super.run(); addcount2.addCount("b"); } }
//测试类public class MainClass { public static void main(String[] args) { addCountClass addCount1 = new addCountClass(); addCountClass addCount2 = new addCountClass(); //线程th1启动后,调用addCount方法时,是用对象addCount1来调用的 Thread th1 = new ThreadA(addCount1); //线程th2启动后,调用addCount方法时,是用对象addCount2来调用的 Thread th2 = new ThreadB(addCount2); th1.start(); th2.start(); }}
//输出结果a set count = 100,overb set count = 200,overb count = 200a count = 100/*分析上述结果可知,因为类中Synchronized关键字修饰的是普通方法, *因此锁定的是当前的对象,所以两次调用时分别锁定了addCount1和 *addCount2,所以两个线程都能够正常访问,互不影响*/
-
同步静态方法:锁的是当前class(类)对象
1 //将上一个例子中的addCount方法修改为类中的static方法 2 public class addCountClass { 3 4 private static int count = 0; 5 6 synchronized public static void addCount(String user) 7 { 8 try 9 {10 if(user.equals("a")) 11 {12 count = 100;13 System.out.println("a set count = 100,over");14 Thread.sleep(3000);15 }else 16 {17 count = 200;18 System.out.println("b set count = 200,over");19 }20 System.out.println(user+" count = "+count);21 22 } catch (Exception e) {23 e.printStackTrace();24 }25 }26 }
//运行结果a set count = 100,overa count = 100b set count = 200,overb count = 200/* *此时可以看到,执行结果只可能是一个线程完全执行完addCount方法后 *另一个线程才能够进入该方法,这是因为此时Synchronized修饰的是类 *中的静态方法,因此锁定的当前的class对象,即当前类。因此无论是 *addCount1还是addCount2来调用addCount方法,都会锁定当前类对象*/
-
同步代码块:锁的是()中的对象,synchronized(this)代码块也是锁定当前对象
当某个需要同步的方法中并不是所有的部分都需要同步时,可将需要同步的代码块用synchronized来修饰,可以提高程序并发效果。
1 //案例1,整个方法都加上了Syncchronized关键字 2 public class Task { 3 4 private int a = 0; 5 synchronized public void longTimeTask() 6 { 7 //模拟一个长时间的无需同步的任务 8 System.out.println("no need Synchronized task start"); 9 try {10 Thread.sleep(3000);11 } catch (InterruptedException e) {12 e.printStackTrace();13 }14 System.out.println("no need Synchronized task end");15 16 //需要同步的任务17 System.out.println("Synchronized task start");18 try {19 Thread.sleep(1000);20 } catch (InterruptedException e) {21 e.printStackTrace();22 }23 System.out.println("Synchronized task end");24 }25 }
1 //Thread类A 2 public class ThreadA extends Thread { 3 private Task task; 4 public ThreadA(Task task) 5 { 6 super(); 7 this.task = task; 8 } 9 10 public void run() 11 {12 super.run();13 task.longTimeTask();14 }15 }
1 public class ThreadB extends Thread { 2 private Task task; 3 public ThreadB(Task task) 4 { 5 super(); 6 this.task = task; 7 } 8 9 public void run() 10 {11 super.run();12 task.longTimeTask();13 }14 }
1 //测试类 2 public class MainClass2 { 3 4 public static void main(String[] args) throws Exception { 5 long startTime = System.currentTimeMillis(); 6 7 Task task = new Task(); 8 Thread th1 = new ThreadA(task); 9 Thread th2 = new ThreadB(task);10 11 th1.start();12 th2.start();13 14 th2.join();15 16 long totalTime = System.currentTimeMillis()-startTime;17 System.out.println("程序总计用时:"+ totalTime);18 19 }20 21 }
//输出结果no need Synchronized task startno need Synchronized task endSynchronized task startSynchronized task endno need Synchronized task startno need Synchronized task endSynchronized task startSynchronized task end程序总计用时:8006毫秒
分析上述结果可知,两个线程分别去调用加锁方法,第二个需要等到第一个执行完加锁方法中的全部步骤后才可进入执行,即使其中有一部分是无需线程同步的。
修改上述加锁部分,将Synchronized关键字只加到需要同步的代码块上:
1 public class Task { 2 3 private int a = 0; 4 public void longTimeTask() 5 { 6 //模拟一个长时间的无需同步的任务 7 System.out.println("no need Synchronized task start"); 8 try { 9 Thread.sleep(3000);10 } catch (InterruptedException e) {11 e.printStackTrace();12 }13 System.out.println("no need Synchronized task end");14 15 //只在需要同步的任务前加上synchronized16 synchronized(this) 17 {18 System.out.println("Synchronized task start");19 try {20 Thread.sleep(1000);21 } catch (InterruptedException e) {22 e.printStackTrace();23 }24 System.out.println("Synchronized task end");25 }26 }27 }
最终程序执行结果为:
//程序运行结果no need Synchronized task startno need Synchronized task startno need Synchronized task endno need Synchronized task endSynchronized task startSynchronized task endSynchronized task startSynchronized task end程序总计用时:5010毫秒
分析上述结果可知,此时线程th1和th2访问longTimeTask方法,但是其中一部分为无需线程同步的,两个线程可以并发执行,而加上了Sychronized方法的代码块。则会在第一个线程进入时加上
task对象锁,第二个线程进入该代码块时不能获取到该锁,所以该代码块无法并行,最终使用代码块加锁的方式,在不影响程序正常执行的情况下,提高了程序并发。
2.实现原理:JVM
是通过进入、退出对象监视器( Monitor
)来实现对方法、同步块的同步的。
具体实现是在编译之后在同步方法调用前加入一个 monitor.enter
指令,在退出方法和异常处插入 monitor.exit
的指令。
其本质就是对一个对象监视器( Monitor
)进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。
而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 monitor.exit
之后才能尝试继续获取锁。