多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多个线程同时访问时可能出现的问题。
同步机制可以使用synchronized关键字实现。
当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
当synchronized方法执行完或发生异常时,会自动释放锁。
下面通过一个例子来对synchronized关键字的用法进行解析。
1.是否使用synchronized关键字的不同
1 public class test_Thread_1 { 2 3 public static void main(String[] args) 4 { 5 Example example = new Example(); 6 Thread t1 = new Thread1(example); 7 Thread t2 = new Thread1(example); 8 t1.start(); 9 t2.start(); 10 } 11 12 } 13 class Example 14 { 15 public synchronized void exec() 16 { 17 for(int i=0;i<10;i++){ 18 try { 19 Thread.sleep(500); 20 } catch (InterruptedException e) { 21 // TODO Auto-generated catch block 22 e.printStackTrace(); 23 } 24 System.out.println("hello:"+i); 25 } 26 } 27 } 28 29 class Thread1 extends Thread 30 { 31 private Example example; 32 public Thread1(Example example){ 33 this.example = example; 34 } 35 36 public void run(){ 37 example.exec(); 38 } 39 40 }
是否在execute()方法前加上synchronized关键字,这个例子程序的执行结果会有很大的不同。
如果不加synchronized关键字,则两个线程同时执行execute()方法,输出是两组并发的。
如果加上synchronized关键字,则会先输出一组,然后再输出下一组,说明两个线程是顺次执行的。
2.多个方法的多线程情况
1 public class test_Thread_2 2 { 3 4 public static void main(String[] args){ 5 Example1 example = new Example1(); 6 Thread t1 = new Thread3(example); 7 Thread t2 = new Thread4(example); 8 t1.start(); 9 t2.start(); 10 } 11 12 } 13 14 class Example1 15 { 16 public synchronized void exec3() 17 { 18 for(int i=0;i<5;i++){ 19 try { 20 Thread.sleep((long) Math.random() * 1000); 21 //Thread.sleep(500); 22 } catch (InterruptedException e) { 23 // TODO Auto-generated catch block 24 e.printStackTrace(); 25 } 26 System.out.println("hello:"+i); 27 } 28 } 29 30 public synchronized void exec4() 31 { 32 try { 33 Thread.sleep((long) Math.random() * 1000); 34 //Thread.sleep(500); 35 } catch (InterruptedException e) { 36 // TODO Auto-generated catch block 37 e.printStackTrace(); 38 } 39 40 for(int i=0;i<5;i++){ 41 System.out.println("world:"+i); 42 } 43 } 44 } 45 46 class Thread3 extends Thread 47 { 48 private Example1 example; 49 50 public Thread3(Example1 example) { 51 // TODO Auto-generated constructor stub 52 this.example = example; 53 } 54 55 public void run(){ 56 example.exec3(); 57 } 58 } 59 60 class Thread4 extends Thread 61 { 62 private Example1 example; 63 public Thread4(Example1 example) { 64 // TODO Auto-generated constructor stub 65 this.example = example; 66 } 67 public void run(){ 68 example.exec4(); 69 } 70 }
如果去掉synchronized关键字,则两个方法并发执行,并没有相互影响。
但是如例子程序中所写,即便是两个方法:
执行结果永远是执行完一个线程的输出再执行另一个线程的。
说明:
如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的。
结论:
当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
Java中的每个对象都有一个锁(lock),或者叫做监视器(monitor),当一个线程访问某个对象的synchronized方法时,将该对象上锁,其他任何线程都无法再去访问该对象的synchronized方法了(这里是指所有的同步方法,而不仅仅是同一个方法),直到之前的那个线程执行方法完毕后(或者是抛出了异常),才将该对象的锁释放掉,其他线程才有可能再去访问该对象的synchronized方法。
注意这时候是给对象上锁,如果是不同的对象,则各个对象之间没有限制关系。
尝试在代码中构造第二个线程对象时传入一个新的Example对象,则两个线程的执行之间没有什么制约关系。
3.考虑静态的同步方法
当一个synchronized关键字修饰的方法同时又被static修饰,之前说过,非静态的同步方法会将对象上锁,但是静态方法不属于对象,而是属于类,它会将这个方法所在的类的Class对象上锁。
一个类不管生成多少个对象,它们所对应的是同一个Class对象。
1 public class test_Thread_3 { 2 3 public static void main(String[] args) 4 { 5 Example2 example = new Example2(); 6 Thread t1 = new Thread5(example); 7 example = new Example2(); 8 Thread t2 = new Thread6(example); 9 t1.start(); 10 t2.start(); 11 12 } 13 14 } 15 16 class Example2 17 { 18 public static synchronized void exec5() 19 { 20 for(int i=0;i<5;i++){ 21 try { 22 Thread.sleep(500); 23 } catch (InterruptedException e) { 24 // TODO Auto-generated catch block 25 e.printStackTrace(); 26 } 27 System.out.println("hello:"+i); 28 } 29 } 30 31 public static synchronized void exec6() 32 { 33 for(int i=0;i<5;i++){ 34 try { 35 Thread.sleep(500); 36 } catch (InterruptedException e) { 37 // TODO Auto-generated catch block 38 e.printStackTrace(); 39 } 40 System.out.println("world:"+i); 41 } 42 } 43 } 44 45 class Thread5 extends Thread 46 { 47 private Example2 example; 48 public Thread5(Example2 example){ 49 this.example = example; 50 } 51 public void run(){ 52 example.exec5(); 53 } 54 } 55 56 class Thread6 extends Thread 57 { 58 private Example2 example; 59 public Thread6(Example2 example){ 60 this.example = example; 61 } 62 public void run(){ 63 example.exec6(); 64 } 65 }
所以如果是静态方法的情况(execute()和execute2()都加上static关键字),即便是向两个线程传入不同的Example对象,这两个线程仍然是互相制约的,必须先执行完一个,再执行下一个。
结论:
如果某个synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchronized方法所在的类所对应的Class对象。Java中,无论一个类有多少个对象,这些对象会对应唯一一个Class对象,因此当线程分别访问同一个类的两个对象的两个static,synchronized方法时,它们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始。
4. synchronized块
synchronized块写法:
synchronized(object)
{
}
表示线程在执行的时候会将object对象上锁。(注意这个对象可以是任意类的对象,也可以使用this关键字)。
这样就可以自行规定上锁对象。
1 public class test_Thread_4 2 { 3 public static void main(String[] args){ 4 Example3 example = new Example3(); 5 Thread t1 = new Thread7(example); 6 Thread t2 = new Thread8(example); 7 t1.start(); 8 t2.start(); 9 } 10 } 11 12 class Example3 13 { 14 private Object object = new Object(); 15 public void exec1(){ 16 synchronized (object) 17 { 18 for(int i=0;i<5;i++){ 19 try { 20 Thread.sleep(500); 21 } catch (InterruptedException e) { 22 // TODO Auto-generated catch block 23 e.printStackTrace(); 24 } 25 System.out.println("hello:"+i); 26 } 27 } 28 } 29 30 public void exec2(){ 31 synchronized (object) { 32 for(int i=0;i<5;i++){ 33 try { 34 Thread.sleep(500); 35 } catch (InterruptedException e) { 36 // TODO Auto-generated catch block 37 e.printStackTrace(); 38 } 39 System.out.println("world:"+i); 40 } 41 } 42 } 43 } 44 45 class Thread7 extends Thread 46 { 47 private Example3 example; 48 49 public Thread7(Example3 example) { 50 // TODO Auto-generated constructor stub 51 this.example = example; 52 } 53 54 public void run(){ 55 example.exec1(); 56 } 57 } 58 59 class Thread8 extends Thread 60 { 61 private Example3 example; 62 63 public Thread8(Example3 example) { 64 // TODO Auto-generated constructor stub 65 this.example = example; 66 } 67 68 public void run(){ 69 example.exec2(); 70 } 71 }
例子程序4所达到的效果和例子程序2的效果一样,都是使得两个线程的执行顺序进行,而不是并发进行,当一个线程执行时,将object对象锁住,另一个线程就不能执行对应的块。
synchronized方法实际上等同于用一个synchronized块包住方法中的所有语句,然后在synchronized块的括号中传入this关键字。当然,如果是静态方法,需要锁定的则是class对象。
可能一个方法中只有几行代码会涉及到线程同步问题,所以synchronized块比synchronized方法更加细粒度地控制了多个线程的访问,只有synchronized块中的内容不能同时被多个线程所访问,方法中的其他语句仍然可以同时被多个线程所访问(包括synchronized块之前的和之后的)。
注意:被synchronized保护的数据应该是私有的。
结论:
synchronized方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized方法;
synchronized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内、synchronized块之外的其他代码是可以被多个线程同时访问到的。