Synchronized 理解

用法:1.同步方法。2.同步静态方法。3同步代码块。

理解Synchronized 的关键是“锁” (原理在最后)

同步代码有“锁”者执行。所谓的锁必须是同一个。静态的方法是该类的.class ,而非静态的或代码块指的是同一个对象。

来说说不同情况下的锁的情形。

一:类中有普通同步方法 

package test;

public class Test {
public synchronized void fun() {
System.out.println("fun start");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun end");
} public static void main(String[] args) { Test test = new Test();
Thread t = new Thread(new Runnable() { @Override
public void run() {
test.fun();
}
});
t.start();
Thread t2 = new Thread(new Runnable() { @Override
public void run() {
test.fun();
}
});
t2.start(); } }

显然是同步执行的。就是t线程执行结束,t1才能进度,没有什么异议。

现在做一个改变

 package test;

 public class Test {
public synchronized void fun() {
System.out.println("fun start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun end");
} public synchronized void funNoSyc() {
System.out.println("funNoSyc start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("funNoSyc end");
} public static void main(String[] args) { Test test = new Test();
Test test1 = new Test();
Thread t = new Thread(new Runnable() { @Override
public void run() {
test.fun();
}
});
t.start();
Thread t2 = new Thread(new Runnable() { @Override
public void run() {
test1.fun();
}
});
t2.start(); } }

Test new了两个不同对象,看一下执行结果。

可以看到同时执行的,根本没有同步。原是,两个对象,不是同一把“锁” ,很好理解吧。

结论:方法的同步对使用了同一同步类(同一锁)的操作起作用。

接下来再变一下,如果类同有多个同步方法,调用过程会是啥样?

两个同步方法,1和2。使用同一个对象"test".线程1调用fun 线程2调fun1 .看结果。代码下。

package test;

public class Test {
public synchronized void fun() {
System.out.println("fun start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun end");
} public synchronized void fun1() {
System.out.println("fun1 start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun1 end");
} public static void main(String[] args) { Test test = new Test();
Thread t = new Thread(new Runnable() { @Override
public void run() {
test.fun();
}
});
t.start();
Thread t2 = new Thread(new Runnable() { @Override
public void run() {
test.fun1();
}
});
t2.start();
} }

运行结果

结果可以看出,运行fun的时候,fun1不能运行,只能等待。

结论:方法上加 synchronized 隐式加了synchronize(this).所以同一个类中,不同的同步方法使用的是同一个锁,只要一个同步方法在执行,其它方法只能等待。

二:类中同步的静态方法

看代码

package test;

public class Test {
public static synchronized void fun() {
System.out.println("fun start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun end");
} public static void main(String[] args) { Test test = new Test();
Thread t = new Thread(new Runnable() { @Override
public void run() {
Test.fun();
}
});
t.start();
Thread t2 = new Thread(new Runnable() { @Override
public void run() {
Test.fun();
}
});
t2.start(); } }

运行结果 是同步的。先后执行。

改一下,用不同的对象去执行:

最后结果一样的。

如果类中有多个静态方法

package test;

public class Test {
public static synchronized void fun() {
System.out.println("fun start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun end");
} public static synchronized void fun1() {
System.out.println("fun1 start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun1 end");
} public static void main(String[] args) { Test test = new Test();
Test test1 = new Test();
Thread t = new Thread(new Runnable() { @Override
public void run() {
test.fun();
}
});
t.start();
Thread t2 = new Thread(new Runnable() { @Override
public void run() {
test1.fun1();
}
});
t2.start(); } }

执行结果:

一个静态方法在执行,另一个则等待。

结论:所有静态法都使用的同一个锁,就是该类的.class。所以所有静态方法都是互斥的。开发上要好好考虑,会有性能问题。

这就所谓的类锁,其实就是所有静态方法使用的同一个锁。

三:同步代码块

synchronized (锁){
  代码

到这儿其实都不用试了,这里面的代码同步关键还是这个锁。同锁(同一对象)互斥。

package test;

public class Test {
public void fun() { synchronized (this) {
System.out.println("fun start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun end");
}
} public static void main(String[] args) { Test test = new Test();
// Test test1 = new Test();
Thread t = new Thread(new Runnable() { @Override
public void run() {
test.fun();
}
});
t.start();
Thread t2 = new Thread(new Runnable() { @Override
public void run() {
//同一对象共互斥
test.fun();
// test1.fun();
}
});
t2.start(); } } 结果:

fun start
fun end
fun start
fun end

 

不同对象,不起作用。看下面代码结果

package test;

public class Test {
public void fun() { synchronized (this) {
System.out.println("fun start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun end");
}
} public static void main(String[] args) { Test test = new Test();
Test test1 = new Test();
Thread t = new Thread(new Runnable() { @Override
public void run() {
test.fun();
}
});
t.start();
Thread t2 = new Thread(new Runnable() { @Override
public void run() {
// 同一对象共互斥
// test.fun();
test1.fun();
}
});
t2.start(); } } 结果:

fun start
fun start
fun end
fun end

 

或者修改代码如下:

传入一个对象锁

package test;

public class Test {
public void fun(Object object) { synchronized (object) {
System.out.println("fun start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun end");
}
} public static void main(String[] args) { Test test = new Test();
Test test1 = new Test();
Thread t = new Thread(new Runnable() { @Override
public void run() {
test.fun(test);
}
});
t.start();
Thread t2 = new Thread(new Runnable() { @Override
public void run() {
test1.fun(test);
}
});
t2.start(); } }

这样的代码块是在不同的对象,但是传的是同一个对象锁,结果照样还是同步的。

总结:synchronized 工作的关键就是这个对象锁。静态的方法所用锁就是类.class,所有人共用,因为只有一个,非静态的就是具体的对象。

同步只对使用的同一个锁的过程起作用。

Synchronized 原理

  如果对上面的执行结果还有疑问,也先不用急,我们先来了解Synchronized的原理,再回头上面的问题就一目了然了。我们先通过反编译下面的代码来看看Synchronized是如何实现对代码块进行同步的:

1 package com.paddx.test.concurrent;
2
3 public class SynchronizedDemo {
4 public void method() {
5 synchronized (this) {
6 System.out.println("Method 1 start");
7 }
8 }
9 }

反编译结果:

关于这两条指令的作用,我们直接参考JVM规范中描述:

monitorenter :

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

这段话的大概意思为:

每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

monitorexit: 

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

这段话的大概意思为:

执行monitorexit的线程必须是objectref所对应的monitor的所有者。

指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

  通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

  我们再来看一下同步方法的反编译结果:

源代码:

1 package com.paddx.test.concurrent;
2
3 public class SynchronizedMethod {
4 public synchronized void method() {
5 System.out.println("Hello World!");
6 }
7 }

反编译结果:

  从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成

线程同步synchronized理解的更多相关文章

  1. Java中线程同步的理解 - 其实应该叫做Java线程排队

    Java中线程同步的理解 我们可以在计算机上运行各种计算机软件程序.每一个运行的程序可能包括多个独立运行的线程(Thread). 线程(Thread)是一份独立运行的程序,有自己专用的运行栈.线程有可 ...

  2. java中线程同步的理解(非常通俗易懂)

    转载至:https://blog.csdn.net/u012179540/article/details/40685207 Java中线程同步的理解 我们可以在计算机上运行各种计算机软件程序.每一个运 ...

  3. Java线程同步synchronized的理解

    JVM中(留神:马上讲到的这两个存储区只在JVM内部与物理存储区无关)存在一个主内存(Main Memory),Java中所有的变量存储在主内存中,所有实例和实例的字段都在此区域,对于所有的线程是共享 ...

  4. Java线程(二):线程同步synchronized和volatile

    上篇通过一个简单的例子说明了线程安全与不安全,在例子中不安全的情况下输出的结果恰好是逐个递增的(其实是巧合,多运行几次,会产生不同的输出结果),为什么会产生这样的结果呢,因为建立的Count对象是线程 ...

  5. Java中线程同步的理解

    我们可以在计算机上运行各种计算机软件程序.每一个运行的程序可能包括多个独立运行的线程(Thread). 线程(Thread)是一份独立运行的程序,有自己专用的运行栈.线程有可能和其他线程共享一些资源, ...

  6. java多线程:线程同步synchronized(不同步的问题、队列与锁),死锁的产生和解决

    0.不同步的问题 并发的线程不安全问题: 多个线程同时操作同一个对象,如果控制不好,就会产生问题,叫做线程不安全. 我们来看三个比较经典的案例来说明线程不安全的问题. 0.1 订票问题 例如前面说过的 ...

  7. 线程同步 synchronized 同步代码块 同步方法 同步锁

    一 同步代码块 1.为了解决并发操作可能造成的异常,java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块.其语法如下: synchronized(obj){ // ...

  8. [03] 线程同步 synchronized

    1.线程同步概述 线程之间有可能共享一些资源,比如内存.文件.数据库等.多个线程同时读写同一份共享资源时,就可能引起冲突,所以引入了线程的"同步"机制. 所谓同步,就是说线程要有先 ...

  9. 线程同步synchronized,wait,notifyAll 测试示例

    https://www.cnblogs.com/LipeiNet/p/6475851.html 一  synchronized synchronized中文解释是同步,那么什么是同步呢,解释就是程序中 ...

随机推荐

  1. colspan和rowspan

    colspan和rowspan这两个属性用于创建特殊的表格. colspan用来指定单元格横向跨越的列数:colspan就是合并列的,colspan=2的话就是合并两列. rowspan用来指定单元格 ...

  2. 多线程之实现Runnable接口及其优点

    多线程之实现Runnable接口: 1.创建一个Runnable接口的实现类 2.在实现类中重写Runnable接口的run方法 3.创建一个Runnable接口实现类的对象 4.创建Thread类对 ...

  3. ps aux详解(进程状态说明)

    linux上进程有5种状态: 1. 运行(正在运行或在运行队列中等待) 2. 中断(休眠中, 受阻, 在等待某个条件的形成或接受到信号) 3. 不可中断(收到信号不唤醒和不可运行, 进程必须等待直到有 ...

  4. 微信公众号开发者中心配置 Token验证失败 终极解决方案

    请您检查这几项: 1. 在您的URL(服务器地址)页面里,直接Get获取echostr参数打印到页面上. 在火狐浏览器里Firebug里面看到echostr前面多了几个乱码. 把您开发者设置的URL页 ...

  5. PAT Basic 1006 换个格式输出整数 (15 分)

    让我们用字母 B 来表示“百”.字母 S 表示“十”,用 12...n 来表示不为零的个位数字 n(<),换个格式来输出任一个不超过 3 位的正整数.例如 234 应该被输出为 BBSSS123 ...

  6. new一个有父类的对象时各代码块的执行顺序问题

    public class QQ { public static void main(String[] args) { new B(); } } class A { static { System.ou ...

  7. uestc summer training #4 牛客第一场

    A dp[i][j][k]可以n3地做 但是正解是找把问题转化为一个两点不相交路径 最终答案为C(n+m, n)2-C(n+m, m-1)C(n+m,n-1) B 把题目的矩阵看成无向图的邻接矩阵 这 ...

  8. java读写大文件

    java读写2G以上的大文件(推荐使用以下方法) static String sourceFilePath = "H:\\DataSource-ready\\question.json&qu ...

  9. DevExpress ASP.NET Core v19.1版本亮点:数据网格和树列表

    行业领先的.NET界面控件DevExpress 发布了v19.1版本,本文将以系列文章的方式为大家介绍DevExpress ASP.NET Core Controls v19.1中新增的一些控件及增强 ...

  10. jquery 自定义右键菜单

    如果要自定义右键菜单,那么就需要禁止原本的右键菜单,代码如下 document.oncontextmenu = new Function("return false;");//禁止 ...