一、目录

  1、多线程启动方式

  2、synchronized的基本用法

  3、深度解析synchronized

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

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

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

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

  8、synchronized编程建议

二、多线程启动方式

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

 //实现runnable接口
static class MyThread implements Runnable{
@Override
public void run() { }
} //继承Thread+重写run
static class MThread extends Thread{
@Override
public void run() {
super.run();
}
} //测试方式
public static void main(String[] args) {
new Thread(new MyThread(),"t").start();
new MThread().start();
}

二、synchronized的基本用法

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

/**
* 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;
 </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> method(){
</span><span style="color: #0000ff;">synchronized</span>(<span style="color: #0000ff;">this</span><span style="color: #000000;">){
count</span>++<span style="color: #000000;">;
System.out.println(Thread.currentThread().getName()</span>+":count="+<span style="color: #000000;">count);
}
} </span><span style="color: #008000;">//</span><span style="color: #008000;">该种书写方式等价于上面的method</span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">synchronized</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> cloneMethod(){
count</span>++<span style="color: #000000;">;
System.out.println(Thread.currentThread().getName()</span>+":count="+<span style="color: #000000;">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 int count=10;
 </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">synchronized</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> method(){
count</span>++<span style="color: #000000;">;
System.out.println(Thread.currentThread().getName()</span>+":count="+<span style="color: #000000;">count);
} </span><span style="color: #008000;">//</span><span style="color: #008000;">等价于上述方法</span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> cloneMethod(){
</span><span style="color: #0000ff;">synchronized</span> (T3.<span style="color: #0000ff;">class</span>) {<span style="color: #008000;">//</span><span style="color: #008000;">这里写this可以吗?</span>
count++<span style="color: #000000;">;
System.out.println(Thread.currentThread().getName()</span>+":count="+<span style="color: #000000;">count);
}
}

}

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

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

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

三、深度解析synchronized

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

多线程不加锁:

 //多线程不加锁!
public class T4 {
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 void run() { for(int i=0;i<5;i++){
value++;
System.out.println(Thread.currentThread().getName()+":"+this.value);
}
}
}
} //运行结果:每次运行结果都不同
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=new Thread(t,"t1");
Thread t2=new Thread(t,"t2");
t1.start();
t2.start();
}
 </span><span style="color: #0000ff;">static</span> <span style="color: #0000ff;">class</span> MyThread <span style="color: #0000ff;">implements</span><span style="color: #000000;"> Runnable{
</span><span style="color: #0000ff;">private</span> <span style="color: #0000ff;">int</span> value =0<span style="color: #000000;">;
@Override
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">synchronized</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> run() { </span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i=0;i&lt;5;i++<span style="color: #000000;">){
value</span>++<span style="color: #000000;">;
System.out.println(Thread.currentThread().getName()</span>+":"+<span style="color: #0000ff;">this</span><span style="color: #000000;">.value);
}
}
}

}

运行结果:

t1:1 t1:2 t1:3 t1:4 t1:5 t2:6 t2:7 t2:8 t2:9 t2:10

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

先谈Java内存模型:

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

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

分析程序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以后把数据从主存刷新到线程工作内存。

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

 /**
* 线程是否可以同时调用同步方法与非同步方法?
* @author qiuyongAaron
*/
public class T6 { public synchronized void m1() {
System.out.println(Thread.currentThread().getName() + " m1 start...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m1 end");
} public void m2() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m2 ");
} public static void main(String[] args) {
T6 t = new T6();
new Thread(()->t.m1(),"t1").start();
new Thread(()->t.m2(),"t2").start();
}
}
//运行结果:
t1:start!
t2:start!
t1:end!

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

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

 /**
* 当锁定同一个对象的时候,锁只是在对象添加标记,加锁一次标记+1,解锁一次标记-1,直到标记为0释放锁。
* 可重入锁
* @author qiuyongAaron
*/
public class T7 {
public synchronized void m1(){
try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
m2();
} public synchronized void m2(){
try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
}
}

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

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

/**
* 异常将导致锁释放!
* @author qiuyongAaron
*/
public class T9 {
 </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">synchronized</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> m1(){
</span><span style="color: #0000ff;">int</span> i=0<span style="color: #000000;">;
System.out.println(Thread.currentThread().getName()</span>+":start!"<span style="color: #000000;">);
</span><span style="color: #0000ff;">while</span>(<span style="color: #0000ff;">true</span><span style="color: #000000;">){
</span><span style="color: #0000ff;">if</span>(i==10<span style="color: #000000;">){
System.out.println(</span>5/0<span style="color: #000000;">);
}
i</span>++<span style="color: #000000;">;
}
} </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> m2(){
System.out.println(Thread.currentThread().getName()</span>+":start!"<span style="color: #000000;">);
</span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
Thread.sleep(</span>10000<span style="color: #000000;">);
} </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()</span>+":end!"<span style="color: #000000;">);
} </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> main(String[] args) {
T9 t</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> T9();
</span><span style="color: #0000ff;">new</span> Thread(()-&gt;t.m1(),"t1"<span style="color: #000000;">).start();
</span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
TimeUnit.SECONDS.sleep(</span>2<span style="color: #000000;">);
} </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (InterruptedException e) {
e.printStackTrace();
}
</span><span style="color: #0000ff;">new</span> Thread(()-&gt;t.m2(),"t2"<span style="color: #000000;">).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();
 </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> m(){
</span><span style="color: #0000ff;">synchronized</span><span style="color: #000000;">(o){
</span><span style="color: #0000ff;">while</span>(<span style="color: #0000ff;">true</span><span style="color: #000000;">){
</span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
TimeUnit.SECONDS.sleep(</span>1<span style="color: #000000;">);
} </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
} </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> main(String[] args) {
T10 t</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> T10(); </span><span style="color: #0000ff;">new</span> Thread(()-&gt;t.m(),"t1"<span style="color: #000000;">).start(); </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
TimeUnit.SECONDS.sleep(</span>1<span style="color: #000000;">);
} </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (InterruptedException e) {
e.printStackTrace();
} t.o</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> Object(); </span><span style="color: #0000ff;">new</span> Thread(()-&gt;t.m(),"t2"<span style="color: #000000;">).start();
}

}

运行结果:

t1 t1 t2

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

八、synchronized编程建议

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

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

/**
*锁粗化
*@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";
 </span><span style="color: #0000ff;">void</span><span style="color: #000000;"> m1() {
</span><span style="color: #0000ff;">synchronized</span><span style="color: #000000;">(s1) {}
} </span><span style="color: #0000ff;">void</span><span style="color: #000000;"> m2() {
</span><span style="color: #0000ff;">synchronized</span><span style="color: #000000;">(s2) {}
}

}

九、版权声明

  作者:邱勇Aaron

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

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

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

  参考:深入理解JVM、马士兵并发编程、并发编程实践

并发编程(一):从头到脚解读synchronized的更多相关文章

  1. 并发编程之第三篇(synchronized)

    并发编程之第三篇(synchronized) 3. 自旋优化 4. 偏向锁 撤销-其它线程使用对象 撤销-调用wait/notify 批量重偏向 批量撤销 5. 锁消除 4.7 wait/notify ...

  2. Java并发编程原理与实战九:synchronized的原理与使用

    一.理论层面 内置锁与互斥锁 修饰普通方法.修饰静态方法.修饰代码块 package com.roocon.thread.t3; public class Sequence { private sta ...

  3. Java并发编程总结1——线程状态、synchronized

    以下内容主要总结自<Java多线程编程核心技术>,不定时补充更新. 一.线程的状态 Java中,线程的状态有以下6类:NEW, RUNNABLE, BLOCKED, WAITING, TI ...

  4. Java并发编程的艺术(三)——synchronized

    什么是synchronized synchronized可以保证某个代码块或者方法被一个线程占有,保证了一个线程的可先性.java 1.6之前是重量级锁,在1.6进行了各种优化,就不那么重了,并引入了 ...

  5. 《JAVA高并发编程详解》-volatile和synchronized

  6. 【并发编程】synchronized的使用场景和原理简介

    1. synchronized使用 1.1 synchronized介绍 在多线程并发编程中synchronized一直是元老级角色,很多人都会称呼它为重量级锁.但是,随着Java SE 1.6对sy ...

  7. Java 多线程并发编程一览笔录

    Java 多线程并发编程一览笔录 知识体系图: 1.线程是什么? 线程是进程中独立运行的子任务. 2.创建线程的方式 方式一:将类声明为 Thread 的子类.该子类应重写 Thread 类的 run ...

  8. 【Java并发编程实战】-----synchronized

    在我们的实际应用当中可能经常会遇到这样一个场景:多个线程读或者.写相同的数据,访问相同的文件等等.对于这种情况如果我们不加以控制,是非常容易导致错误的.在java中,为了解决这个问题,引入临界区概念. ...

  9. Java并发编程实例(synchronized)

    此处用一个小程序来说明一下,逻辑是一个计数器(int i):主要的逻辑功能是,如果同步监视了资源i,则不输出i的值,但如果没有添加关键字synchronized,因为是两个线程并发执行,所以会输出i的 ...

随机推荐

  1. Charles抓取https请求详解

    大家好,我是TT,互联网测试行业多年,没有牛逼的背景,也没有什么可炫耀的,唯独比他人更努力,在职场打拼.遇到过的坑,走过的弯路,愿意与大家分享,分享自己的经验,少走弯路.首发于个人公众号[测试架构师] ...

  2. 软件开发的一些"心法"

    从事软件开发也有好几年了,和一开始那个懵懵懂懂的小菜鸟相比,自己也感觉到了一些变化. 也许是熟能生巧, 趟过很多坑,但核心的绝不是这些细节的东西. 打个比方,如果说对某种语言的特性和技巧的掌握属于身法 ...

  3. 分针网—IT教育:作为PHP开发人员容易忽视的几个重点

    无论是学习什么样的一个开发.ASP开发.java开发.当学习还不是很久的时候,一般都是不知道它们的精华是在哪里,而现在很多的php程序员也是不知道PHP的精华所在,为什么perl在当年在商界如此的出名 ...

  4. OC 动态类型和静态类型

    多态 允许不同的类定义相同的方法 动态类型 程序直到执行时才能确定所属的类 静态类型 将一个变量定义为特定类的对象时,使用的是静态形态 将一个变量定义为特定类的对象时,使用的是静态类型,在编译的时候就 ...

  5. java泛型探索——介绍篇

    1. 泛型出现前后代码对比 先来看看泛型出现前,代码是这么写的: List words = new ArrayList(); words.add("Hello "); words. ...

  6. vue视频学习笔记02

    video 2 vue制作weibo交互 vue-> 1.0vue-resource ajax php服务器环境(node) this.$http.get()/post()/jsonp() th ...

  7. XSS攻击及预防

    跨站脚本攻击(Cross Site Scripting),为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS.恶意攻击者往Web页面里插 ...

  8. JAVA CyclicBarrier类详解

    一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point).在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时CyclicBarrie ...

  9. MVC中的Ajax与增删改查

    自入手新项目以来,一直处于加班状态,博客也有两周没更,刚刚完成项目的两个模组,稍有喘息之机,写写关于项目中 的增删改查,这算是一个老生常谈的问题了,就连基本的教材书上都有.刚看书的时候,以为 没什么可 ...

  10. 关于初学loadrunner的心得体会

    自参加工作两年以来,深感个人知识底蕴浅薄,为此,自身也在多方寻找所需业务技能.loadrunner负载测试工具,作为性能测试典型工具之一,对于我个人的知识的丰富化起到一定作用,但也仅仅是对工作能力的略 ...