一、目录

  1、多线程启动方式

  2、synchronized的基本用法

  3、深度解析synchronized

  4、同步方法与非同步方法是否能同时调用?

  5、同步锁是否可重入(可重入锁)?

  6、异常是否会导致锁释放?

  7、锁定某对象,对象属性改变是否会影响锁?指定其他对象是否会影响锁?

  8、synchronized编程建议

二、多线程启动方式

继承Thread重写run()或者实现Runnable接口。

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

 1 //实现runnable接口 2     static class MyThread implements Runnable{ 3         @Override 4         public void run() { 5              6         } 7     } 8      9     //继承Thread+重写run10     static class MThread extends Thread{11         @Override12         public void run() {13             super.run();14         }15     }16     17     //测试方式18     public static void main(String[] args) {19         new Thread(new MyThread(),"t").start();20         new MThread().start();21     }

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

二、synchronized的基本用法

1、实例变量对象作为锁对象

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

/**
 * synchronized 锁对象
 * @author qiuyongAaron */public class T1 {     private int count=10;     //利用Object实例对象标记互斥锁,每个线程进行同步代码块的时候,需要先去堆内存object获取锁标记,只有没有被其它线程标记的时候才能获得锁标记。
     Object object =new Object();     public void method(){           synchronized(object){
                count++;
                System.out.println(Thread.currentThread().getName()+":count="+count);
           }
     }
}/***锁定当前对象,原理跟上面一样,只是谈一下应用情况。
*@author qiuyongAaron*/public class T2 {     private int count=10;     public void method(){           synchronized(this){
                count++;
                System.out.println(Thread.currentThread().getName()+":count="+count);
           }
     }     //该种书写方式等价于上面的method
     public synchronized void cloneMethod(){
           count++;
          System.out.println(Thread.currentThread().getName()+":count="+count);
     }
}

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

总结:synchronized不是锁定代码块,它是在访问某段代码块的时候,去寻找锁定对象上的标记(实质上就是一个变量增减,这就是这个标记)。以T2为例,T2对象为锁定对象,假设开启5个线程,线程A最先竞争到锁,那么线程A在T2对象上进行标记,相当于标记变量加1。就在这时,其他4个线程竞争到锁以后,发现T2对象标记变量不为0,那么他们就被阻塞,等待线程A释放锁的时候,标记变量会减1使它变为0,其他锁就能竞争到锁。虚拟机:发生就近原则-锁定原则:释放锁先于获得锁,简而言之,只有线程A释放锁(锁定对象标记变量为0),其他线程才能获得锁(锁定对象标记+1)。

 

2、静态变量对象作为锁对象

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

/**
 * 锁定静态变量
 * @author qiuyongAaron */public class T3 {     public static int count=10;     public synchronized void method(){
           count++;
          System.out.println(Thread.currentThread().getName()+":count="+count);
     }     //等价于上述方法
     public static void cloneMethod(){           synchronized (T3.class) {//这里写this可以吗?
                count++;
                System.out.println(Thread.currentThread().getName()+":count="+count);
           }
     }
}

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

问题:为什么静态变量要写T3.class,不能写this?

回答:这需要了解反射与类加载过程才能透彻解析。类加载过程:类加载-->验证-->准备-->解析-->初始化-->使用卸载,在类加载阶段,将会把静态变量、常量全部加载在堆内存的方法区中,并且会生成Class对象,T3.class就相当于Class对象,然而this是T3对象,而什么时候能够产生T3对象?当应用程序调用new T3()的构造器时候,也就是在初始化阶段才会产生。所以静态变量作为锁定对象只能用T3.class,不能使用this对象。

总结:静态变量在类加载的时候就存入内存,而实例变量是要调用构造器的时候才能加载进内存。所以,T3.class是类加载产生,this是初始化产生,自然标记锁定对象的时候是用T3.class不用this。

三、深度解析synchronized

synchronized定义:互斥锁,保证原子性、可见性。也就是,当线程A获得锁,其他线程全部被阻塞。之前解析过不过多赘述。

多线程不加锁:

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

 1 //多线程不加锁! 2 public class T4 { 3      public static void main(String[] args) { 4            MyThread t=new MyThread(); 5            Thread t1=new Thread(t,"t1"); 6            Thread t2=new Thread(t,"t2"); 7            t1.start(); 8            t2.start(); 9      }10 11      static class MyThread implements Runnable{12            private int value =0;13            @Override14            public void run() {15 16                 for(int i=0;i<5;i++){17                      value++;18                      System.out.println(Thread.currentThread().getName()+":"+this.value);19                 }20            }21      }22 }23 24 //运行结果:每次运行结果都不同25 t1:2 t2:2 t1:3 t2:4 t1:5 t2:6 t1:7 t2:8 t1:9 t2:10

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

多线程加锁:

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

//多线程加锁!public class T5 {     public static void main(String[] args) {
           MyThread t=new MyThread();
           Thread t1=new Thread(t,"t1");
           Thread t2=new Thread(t,"t2");
           t1.start();
           t2.start();
     }     static class MyThread implements Runnable{           private int value =0;
           @Override           public synchronized void run() {                for(int i=0;i<5;i++){
                     value++;
                     System.out.println(Thread.currentThread().getName()+":"+this.value);
                }
           }
     }
}
运行结果:
t1:1 t1:2 t1:3 t1:4 t1:5 t2:6 t2:7 t2:8 t2:9 t2:10

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

显然,加了同步互斥锁的例子程序符合我们业务需求,那么想一下这是为什么?

先谈Java内存模型:

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

分析:在虚拟机中,堆内存用于存储共享数据(实例对象),堆内存也就是这里说的主内存。

   每个线程将会在堆内存中开辟一块空间叫做线程的工作内存,附带一块缓存区用于存储共享数据副本。那么,共享数据在堆内存当中,线程通信就是通过主内存为中介,线程在本地内存读并且操作完共享变量操作完毕以后,把值写入主内存。

 

分析程序1:

  • t1从主存中读取共享变量value:0,并且执行完value++后value:1,写入主存。

  • t2启动读取主存value:1到工作内存,执行并打印value为2,3。

  • t2读取的是它工作内存的值,所以这时t1的本地内存并没有改变还是1,执行打印输入value:2。

  • 同样逻辑执行...

  • 来看t2:6、t2:8、t1:7、t1:9,为什么?

  • 当t2在工作内存操作完共享变量,t2把共享变量为value:6写入主存。

  • 就在这时,t1从主存读取共享变量value:6并且value++为7,还没来得及打印。

  • t2从主存读取共享变量value:7,value++,打印value:8,并且写入主存。

  • 这时,继续之前的操作value++,自然打印的值还是7,再读取主存值value:8

  • 这时t1打印value:9,value:10。

 

分析程序2:

  • 在虚拟机的先行发生原则中(happen-before)的锁定原则:对某一个对象加锁的时候,它接锁先于加锁,意思就是必须等线程A锁释放,才能被线程B访问。

  • 回到这个小程序,t1启动、t2被阻塞不能访问共享变量。之前,我们谈过java内存模型,假设线t1启动读取共享数据,并且会把共享数据写入到工作内存的缓存中,t1在本地内存操作完,待它操作完不把数据写回主存,这样即便t2被堵塞也没用?所以,虚拟机规定,线程unlock的时候必须把数据刷新到主存,lock的时候必须从主存刷新数据到工作内存。

  • 什么意思?最开始主存共享变量value:0,t1获得同步锁,t2被阻塞。t1操作value:1-5,假设t1在本地内存操作完就马上释放锁并不把value写入主存,这时t2获得同步锁,从主存读到的共享变量依然为0,这虚拟机岂能容忍?所以,虚拟机规定,t1必须unlock之前把数据从线程工作内存刷新到主存,t2必须lock以后把数据从主存刷新到线程工作内存。

四、同步方法与非同步方法是否能同时调用?

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

 1 /** 2  * 线程是否可以同时调用同步方法与非同步方法? 3  * @author qiuyongAaron 4  */ 5 public class T6 { 6  7      public synchronized void m1() { 8            System.out.println(Thread.currentThread().getName() + " m1 start..."); 9            try {10                 Thread.sleep(10000);11            } catch (InterruptedException e) {12                 e.printStackTrace();13            }14            System.out.println(Thread.currentThread().getName() + " m1 end");15      }16 17      public void m2() {18            try {19                 Thread.sleep(5000);20            } catch (InterruptedException e) {21                 e.printStackTrace();22            }23            System.out.println(Thread.currentThread().getName() + " m2 ");24      }25 26      public static void main(String[] args) {27            T6 t = new T6();28            new Thread(()->t.m1(),"t1").start();29            new Thread(()->t.m2(),"t2").start();30      }31 }32 //运行结果:33 t1:start!34 t2:start!35 t1:end!

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

 总结:显然可以,首先synchronized同步互斥锁是锁定对象,t1锁定的T6对象。线程t1去访问代码块t.m1()的时候会去申请锁,去查看锁定标记是否为0,再决定是否阻塞。然而线程t2访问t.m2()都不用申请锁,所以你锁定标记为什么,与我有什么关系?所以,上述问题当然是成立!

五、同步互斥锁是否可重入(可重入锁)?

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

 1 /** 2  * 当锁定同一个对象的时候,锁只是在对象添加标记,加锁一次标记+1,解锁一次标记-1,直到标记为0释放锁。 3  * 可重入锁 4  * @author qiuyongAaron 5  */ 6 public class T7 { 7      public synchronized void m1(){ 8            try { 9                 Thread.sleep(5000);10            } catch (Exception e) {11                 e.printStackTrace();12            }13            m2();14      }15 16      public synchronized void m2(){17            try {18                 Thread.sleep(5000);19            } catch (Exception e) {20                 e.printStackTrace();21            }22      }23 }

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

总结:synchronized同步互斥锁,支持可重入。在开篇我们就谈了,申请锁意味着对锁定对象的标记变量值修改,如果是同一个锁定变量,那么没重入一次,锁标记变量+1。如果想锁释放,那么必须释放锁-1,直到标记变量为0,锁才能被释放被其他线程占用。

六、异常是否会导致锁释放?

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

/**
 * 异常将导致锁释放!
 * @author qiuyongAaron */public class T9 {     public synchronized void m1(){           int i=0;
          System.out.println(Thread.currentThread().getName()+":start!");           while(true){                if(i==10){
                     System.out.println(5/0);
                }
                i++;
           }
     }     public void m2(){
          System.out.println(Thread.currentThread().getName()+":start!");           try {
                Thread.sleep(10000);
           } catch (InterruptedException e) {
                e.printStackTrace();
           }
          System.out.println(Thread.currentThread().getName()+":end!");
     }     public static void main(String[] args) {
           T9 t=new T9();           new Thread(()->t.m1(),"t1").start();           try {
                TimeUnit.SECONDS.sleep(2);
           } catch (InterruptedException e) {
                e.printStackTrace();
           }           new Thread(()->t.m2(),"t2").start();
     }

}
运行结果:
t1:start!Exception in thread "t1" java.lang.ArithmeticException: / by zero
     at com.ccut.aaron.synchronize.T9.m1(T9.java:12)
     at com.ccut.aaron.synchronize.T9.lambda$0(T9.java:30)
     at java.lang.Thread.run(Thread.java:745)
t2:start!t2:end!

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

总结:答案是产生异常将会释放锁,所以在编写代码时候需要处理异常。从例子程序可看出,如果不释放锁的话,t1一直占用锁,而t2不可能获得锁。从运行结果看出,t2获得锁资源,所以证明了原命题。

七、锁定某对象,对象属性改变是否会影响锁?指定其他对象是否会影响锁?

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

/**
 * 锁定对象改变属性无影响,如果锁定对象指定新对象,锁定对象将会改变!
 * @author xiaoyongAaron */public class T10 {
     Object o=new Object();     public void m(){           synchronized(o){                while(true){                     try {
                           TimeUnit.SECONDS.sleep(1);
                     } catch (InterruptedException e) {
                           e.printStackTrace();
                     }
                     System.out.println(Thread.currentThread().getName());
                }
           }
     }     public static void main(String[] args) {
           T10 t=new T10();           new Thread(()->t.m(),"t1").start();           try {
                TimeUnit.SECONDS.sleep(1);
           } catch (InterruptedException e) {
                e.printStackTrace();
           }

           t.o=new Object();           new Thread(()->t.m(),"t2").start();
     }
}
运行结果:
t1 t1 t2

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

总结:从运行结果看出原命题的答案是,修改锁定变量的属性不会改变锁,锁定变量指定新对象将会报错。看例子程序,假设锁没有转移到新的实例变量,那么t2将会一直被阻塞。

八、synchronized编程建议

1、尽量锁定有共享数据的代码块,这是并发编程的优化中的锁粗化。

2、不要用常量作为锁定对象,因为常量池的常量同时被两个地方引用将会产生很大的问题。

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

/***锁粗化
*@author qiuyongAaron*/public void T11{     int count=0;     public synchronized void m(){         for(int i=0;i<10;i++){}
         System.out.println("hello world!");         synchronized(this){
             count++; 
         }
     }
}/***不要使用常量作为锁定对象!!
*他们是同一个锁定对象!!
*@author qiuyongAaron*/public void T11{
     String s1 = "Hello";
     String s2 = "Hello";     void m1() {        synchronized(s1) {}
     }     void m2() {         synchronized(s2) {}
     }
   
}

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

 九、版权声明

  作者:邱勇Aaron

  出处:http://www.cnblogs.com/qiuyong/

  您的支持是对博主深入思考总结的最大鼓励。

  本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,尊重作者的劳动成果。

http://www.cnblogs.com/qiuyong/p/7068258.html