1、多线程启动方式
2、synchronized 的基本用法
3、深度解析 synchronized
4、同步方法与非同步方法是否能同时调用?
5、同步锁是否可重入(可重入锁)?
6、异常是否会导致锁释放?
7、锁定某对象,对象属性改变是否会影响锁?指定其他对象是否会影响锁?
8、synchronized 编程建议
继承 Thread 重写 run() 或者实现 Runnable 接口。
- 1 //实现runnable接口
- 2 static classMyThreadimplements Runnable{
- 3 @Override
- 4 public void run() {
- 5
- 6 }
- 7 }
- 8
- 9 //继承Thread+重写run
- 10 static classMThreadextends Thread{
- 11 @Override
- 12 public void run() {
- 13 super.run();
- 14 }
- 15 }
- 16
- 17 //测试方式
- 18 public static void main(String[] args) {
- 19 newThread(newMyThread(),"t").start();
- 20 new MThread().start();
- 21}
1、实例变量对象作为锁对象
- /**
- * synchronized 锁对象
- * @author qiuyongAaron
- */
- public class T1 {
- private intcount=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 intcount=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);
- }
- }
总结:synchronized 不是锁定代码块,它是在访问某段代码块的时候,去寻找锁定对象上的标记(实质上就是一个变量增减,这就是这个标记)。以 T2 为例,T2 对象为锁定对象,假设开启 5 个线程,线程 A 最先竞争到锁,那么线程 A 在 T2 对象上进行标记,相当于标记变量加 1。就在这时,其他 4 个线程竞争到锁以后,发现 T2 对象标记变量不为 0,那么他们就被阻塞,等待线程 A 释放锁的时候,标记变量会减 1 使它变为 0,其他锁就能竞争到锁。虚拟机:发生就近原则 - 锁定原则:释放锁先于获得锁,简而言之,只有线程 A 释放锁(锁定对象标记变量为 0),其他线程才能获得锁(锁定对象标记 + 1)。
2、静态变量对象作为锁对象
- /**
- * 锁定静态变量
- * @author qiuyongAaron
- */
- public class T3 {
- public static intcount=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);
- }
- }
- }
问题:为什么静态变量要写 T3.class,不能写 this
回答:这需要了解反射与类加载过程才能透彻解析。类加载过程:类加载 --> 验证 --> 准备 --> 解析 --> 初始化 --> 使用卸载,在类加载阶段,将会把静态变量、常量全部加载在堆内存的方法区中,并且会生成 Class 对象,T3.class 就相当于 Class 对象,然而 this 是 T3 对象,而什么时候能够产生 T3 对象?当应用程序调用 new T3() 的构造器时候,也就是在初始化阶段才会产生。所以静态变量作为锁定对象只能用 T3.class,不能使用 this 对象。
总结:静态变量在类加载的时候就存入内存,而实例变量是要调用构造器的时候才能加载进内存。所以,T3.class 是类加载产生,this 是初始化产生,自然标记锁定对象的时候是用 T3.class 不用 this。
synchronized 定义:互斥锁,保证原子性、可见性。也就是,当线程 A 获得锁,其他线程全部被阻塞。之前解析过不过多赘述。
多线程不加锁:
- 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@Override 14 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
多线程加锁:
- //多线程加锁!
- public class T5 {
- public static void main(String[] args) {
- MyThread t=new MyThread();
- Thread t1=newThread(t,"t1");
- Thread t2=newThread(t,"t2");
- t1.start();
- t2.start();
- }
- static classMyThreadimplements Runnable{
- private intvalue =0;
- @Override
- public synchronized void run() {
- for(inti=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 内存模型:
分析:在虚拟机中,堆内存用于存储共享数据(实例对象),堆内存也就是这里说的主内存。
每个线程将会在堆内存中开辟一块空间叫做线程的工作内存,附带一块缓存区用于存储共享数据副本。那么,共享数据在堆内存当中,线程通信就是通过主内存为中介,线程在本地内存读并且操作完共享变量操作完毕以后,把值写入主内存。
分析程序 1:
分析程序 2:
- 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 !
总结:显然可以,首先 synchronized 同步互斥锁是锁定对象,t1 锁定的 T6 对象。线程 t1 去访问代码块 t.m1() 的时候会去申请锁,去查看锁定标记是否为 0,再决定是否阻塞。然而线程 t2 访问 t.m2() 都不用申请锁,所以你锁定标记为什么,与我有什么关系?所以,上述问题当然是成立!
- 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
- }
总结:synchronized 同步互斥锁,支持可重入。在开篇我们就谈了,申请锁意味着对锁定对象的标记变量值修改,如果是同一个锁定变量,那么没重入一次,锁标记变量 + 1。如果想锁释放,那么必须释放锁 - 1,直到标记变量为 0,锁才能被释放被其他线程占用。
- /**
- * 异常将导致锁释放!
- * @author qiuyongAaron
- */
- public class T9 {
- public synchronized void m1(){
- inti=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();
- newThread(()->t.m1(),"t1").start();
- try {
- TimeUnit.SECONDS.sleep(2);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- newThread(()->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!
总结:答案是产生异常将会释放锁,所以在编写代码时候需要处理异常。从例子程序可看出,如果不释放锁的话,t1 一直占用锁,而 t2 不可能获得锁。从运行结果看出,t2 获得锁资源,所以证明了原命题。
- /**
- * 锁定对象改变属性无影响,如果锁定对象指定新对象,锁定对象将会改变!
- * @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();
- newThread(()->t.m(),"t1").start();
- try {
- TimeUnit.SECONDS.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- t.o=new Object();
- newThread(()->t.m(),"t2").start();
- }
- }
- 运行结果:
- t1 t1 t2
总结:从运行结果看出原命题的答案是,修改锁定变量的属性不会改变锁,锁定变量指定新对象将会报错。看例子程序,假设锁没有转移到新的实例变量,那么 t2 将会一直被阻塞。
1、尽量锁定有共享数据的代码块,这是并发编程的优化中的粗话。
2、不要用常量作为锁定对象,因为常量池的常量同时被两个地方引用将会产生很大的问题。
- /**
- *锁粗化
- *@author qiuyongAaron
- */
- public void T11{
- intcount=0;
- public synchronized void m(){
- for(inti=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) {}
- }
- }
来源: http://www.cnblogs.com/qiuyong/p/7068258.html