前言

多个线程访问同一个类的synchronized方法时, 都是串行执行的 ! 就算有多个cpu也不例外 ! synchronized方法使用了类java的内置锁, 即锁住的是方法所属对象本身. 同一个锁某个时刻只能被一个执行线程所获取, 因此其他线程都得等待锁的释放. 因此就算你有多余的cpu可以执行, 但是你没有锁, 所以你还是不能进入synchronized方法执行, CPU因此而空闲. 如果某个线程长期持有一个竞争激烈的锁, 那么将导致其他线程都因等待所的释放而被挂起, 从而导致CPU无法得到利用, 系统吞吐量低下.甚至导致死锁的产生, 因此要尽量避免某个线程对锁的长期占有 !

一、修饰方法

方法声明时使用,即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候

用法

public synchronized void synMethod() {

    //方法体

}

demo

public class SyncMethod {
public synchronized void syncMethod2() {
try {
System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 已经获取内置锁`SyncMethod.this`)");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 即将释放内置锁`SyncMethod.this`)");
}
public synchronized void syncMethod1() {
System.out.println("######################## (syncMethod1, 已经获取内置锁`SyncMethod.this`, 并即将退出)");
}
static class Thread1 extends Thread {
SyncMethod syncMethod;
public Thread1(SyncMethod syncMethod) {
this.syncMethod = syncMethod;
}
@Override
public void run() {
syncMethod.syncMethod2();
}
}
static class Thread2 extends Thread {
SyncMethod syncMethod;
public Thread2(SyncMethod syncMethod) {
this.syncMethod = syncMethod;
}
@Override
public void run() {
System.out.println("Thread2 running ...");
syncMethod.syncMethod1();
}
}
public static void main(String[] args) throws InterruptedException {
SyncMethod syncMethod = new SyncMethod();
Thread1 thread1 = new Thread1(syncMethod);
Thread2 thread2 = new Thread2(syncMethod);
thread1.start(); //先执行, 以便抢占锁
Thread.sleep(500); //放弃cpu, 让thread1执行, 以便获的锁
thread2.start(); //在syncMethod1()方法获得锁时, 看看syncMethod2()方法能否执行
}
}

console打印:

@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 已经获取内置锁`SyncMethod.this`)
Thread2 running ...
@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 即将释放内置锁`SyncMethod.this`)
######################## (syncMethod1, 已经获取内置锁`SyncMethod.this`, 并即将退出)

上述代码synchronized修饰的方法,锁住的是类的实例化对象syncMethod,所以Thread1执行syncMethod2的方法将syncMethod对象锁住,使得Thread2受到阻塞必须在Thread1释放锁之后才能执行syncMethod1方法。

将上述代码中的Main方法修改如下:

SyncMethod syncMethod1 = new SyncMethod();
SyncMethod syncMethod2 = new SyncMethod();
Thread1 thread1 = new Thread1(syncMethod1);
Thread2 thread2 = new Thread2(syncMethod2);
thread1.start();
Thread.sleep(500);
thread2.start();

console打印:

@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 已经获取内置锁`SyncMethod.this`)
Thread2 running ...
######################## (syncMethod1, 已经获取内置锁`SyncMethod.this`, 并即将退出)
@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 即将释放内置锁`SyncMethod.this`)

上述代码中Thread1锁的是syncMethod1对象,而Thread2锁的是syncMethod2对象,所以Thread1线程执行syncMethod2并不会阻塞Thread2

当然还有第二种改进措施:

public class SyncObject {
private Object lock1 = new Object();
private Object lock2 = new Object();
public void syncMethod2() {
synchronized (lock1) {
try {
System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 已经获取内置锁`SyncMethod.this`)");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 即将释放内置锁`SyncMethod.this`)");
}
}
public void syncMethod1() {
synchronized (lock2) {
System.out.println("######################## (syncMethod1, 已经获取内置锁`SyncMethod.this`, 并即将退出)");
}
} static class Thread1 extends Thread {
SyncObject syncObject; public Thread1(SyncObject syncObject) {
this.syncObject = syncObject;
} @Override
public void run() {
syncObject.syncMethod2();
}
}
static class Thread2 extends Thread {
SyncObject syncObject; public Thread2(SyncObject syncObject) {
this.syncObject = syncObject;
} @Override
public void run() {
System.out.println("Thread2 running ...");
syncObject.syncMethod1();
}
}
public static void main(String[] args) throws InterruptedException {
SyncObject syncObject = new SyncObject(); Thread1 thread1 = new Thread1(syncObject);
Thread2 thread2 = new Thread2(syncObject); thread1.start(); //先执行, 以便抢占锁
Thread.sleep(500); //放弃cpu, 让thread1执行, 以便获的锁 thread2.start(); //在syncMethod1()方法获得锁时, 看看syncMethod2()方法能否执行 }
}

下面是一些关于使用锁的一些建议: 为了避免对锁的竞争, 你可以使用锁分解,锁分段以及减少线程持有锁的时间, 如果上诉程序中的syncMethod1和syncMethod2方法是两个不相干的方法(请求的资源不存在关系), 那么这两个方法可以分别使用两个不同的锁。

上面Thread1锁的是对象lock1,而Thread2锁的是对象lock2。

二、修饰静态方法

用法

public synchronized static void method() {
// todo
}

demo

我们知道静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象。我们对第一节的Demo进行一些修改如下:

class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public synchronized static void method() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void run() {
method();
}
} public static void main(String[] args) {
SyncStatic syncThread1 = new SyncStatic();
SyncStatic syncThread2 = new SyncStatic();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();
}

console打印:

SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9

印证了我们刚开始说的

synchronized修饰的静态方法锁定的是这个类的所有对象

三、修饰代码块

当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。

class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public void run() {
synchronized(this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
//this.wait(100);释放锁,其他线程可以执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public int getCount() {
return count;
}
}
//main函数调用
SyncThread的调用:
SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();

console打印:

SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9

值得注意的是:

当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。

给指定的对象加锁:

public void method(SomeObject obj)
{
//obj 锁定的对象
synchronized(obj)
{
// todo
}
}

四、修饰类

public class SyncClass {
public void methodA(){
try {
synchronized (SyncClass.class){
System.out.println("methodA begin 线程名称:"+Thread.currentThread().getName()+"times:"+System.currentTimeMillis());
Thread.sleep(3000);
System.out.println("methodA end 线程名称:"+Thread.currentThread().getName()+"times:"+System.currentTimeMillis()); }
}catch (InterruptedException e){
e.printStackTrace();
}
}
public void methodB(){
synchronized (SyncClass.class){
System.out.println("methodB begin 线程名称:"+Thread.currentThread().getName()+"times:"+System.currentTimeMillis());
System.out.println("methodB end 线程名称:"+Thread.currentThread().getName()+"times:"+System.currentTimeMillis());
}
} static class Thread1 extends Thread {
private SyncClass syncClass; public Thread1(SyncClass syncClass) {
super();
this.syncClass = syncClass;
} @Override
public void run() {
syncClass.methodA();
}
} static class Thread2 extends Thread {
private SyncClass syncClass; public Thread2(SyncClass syncClass) {
super();
this.syncClass = syncClass;
} @Override
public void run() {
syncClass.methodB();
}
} public static void main(String[] args) {
SyncClass syncClass1 = new SyncClass();
SyncClass syncClass2 = new SyncClass(); Thread1 thread1=new Thread1(syncClass1);
Thread2 thread2 = new Thread2(syncClass2); thread1.setName("A");
thread2.setName("B"); thread1.start();
thread2.start();
}
}

console打印:

methodA begin 线程名称:Atimes:1533208268430
methodA end 线程名称:Atimes:1533208271431
methodB begin 线程名称:Btimes:1533208271431
methodB end 线程名称:Btimes:1533208271431

由打印结果以及与第一节改进1结果相比可得结论:

synchronized作用于一个类T时,是给这个类T加锁,T的所有实例化对象用的是同一把锁。

所以才会出现methodB在等methodA执行完毕才执行,收到阻塞。

五、synchronized原理

修饰静态代码块

将一个synchronized静态代码块反编译会看到两个专有名词

monitorenter

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

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

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

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

monitorexit:

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

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

  

总结

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

修饰方法

将一个synchronized同步方法反编译:

ACC_SYNCHRONIZED

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

六、总结

  • 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
  • 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
  • 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

java并发之synchronized详解的更多相关文章

  1. Java并发之AQS详解

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  2. Java并发之AQS详解(转)

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronized(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  3. java中同步(synchronized)详解

    一.开山篇: 1.synchronized的使用 一个程序中,如果该类中的代码可能运行于多线程环境下,那么就要考虑同步的问题.在Java中内置了语言级的同步原语--synchronized,这也大大简 ...

  4. java并发之CAS详解

    前言 在高并发的应用当中,最关键的问题就是对共享变量的安全访问,通常我们都是通过加锁的方式,比如说synchronized.Lock来保证原子性,或者在某些应用当中,用voliate来保证变量的可见性 ...

  5. Java并发之ReentrantLock详解

    一.入题 ReentrantLock是Java并发包中互斥锁,它有公平锁和非公平锁两种实现方式,以lock()为例,其使用方式为: ReentrantLock takeLock = new Reent ...

  6. Java并发之CompletionService详解

    CompletionService是什么? 它是JUC包中的一个接口类,默认实现类只有一个ExecutorCompletionService. CompletionService干什么的? 它将异步任 ...

  7. 黑马------synchronized详解

    黑马程序员:Java培训.Android培训.iOS培训..Net培训 JAVA线程-synchronized详解 一.synchronized概述 1.线程间实现互斥,必须使用同一个监视器(一个对象 ...

  8. Java并发之Synchronized机制详解

    带着问题阅读 1.Synchronized如何使用,加锁的粒度分别是什么 2.Synchronized的实现机制是什么 3.Synchronized是公平锁吗 4.Java对Synchronized做 ...

  9. Java synchronized 详解

    Java synchronized 详解 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 1.当两个并发线程访问同一个对象object ...

随机推荐

  1. odoo12从零开始:三、2)odoo模型层

    前言 上一篇文章(创建你的第一个应用模块(module))已经大致描述了odoo的模型层(model)和视图层(view),这一篇文章,我们将系统地介绍有关于model的知识,其中包括: 1.模型的类 ...

  2. Swift从入门到精通第十一篇 - 初始化 初识

    初始化(学习笔记) 环境Xcode 11.0 beta4 swift 5.1 初始化 初始化是类.结构体.枚举生成实例的过程,为该类的每个存储属性设置初始值,有些在实例使用前的设置或初始化也可在此实现 ...

  3. FreeSql (十二)更新数据时指定列

    var connstr = "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;" + "Initia ...

  4. 阿里mysql规范

    (一)建表规约 1.[强制]表达是与否概念的字段,必须使用 is_xxx的方式命名,数据类型是 unsigned tinyint( 1表示是,0表示否),此规则同样适用于 odps建表. 说明:任何字 ...

  5. 强大的时间处理库 moment

    中文文档: http://momentjs.cn/docs/ 常用方法 1.当前时间对象  moment ()   /  指定时间对象  moment("2019-09-19 08:00:0 ...

  6. DOM盒子模型常用属性client,offset和scroll

    JS盒子模型属性 在JS中通过相关的属性可以获取(设置)元素的样式信息,这些属性就是盒子模型属性(基本上都是有关于样式的) 属性 值 client top/left/width/height offs ...

  7. JS实现数组去重的方法

    1.使用ES6的Set进行去重 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"&g ...

  8. cent OS 7 忘记 root 密码

    1. 在如下图, 选择系统的界面 按 e 2. 移动光标到文件底部, 修改如下两个地方(初始化 shell文件 并设置可读写), Ctrl x 退出并启动 shell 3. 如下界面 输入命令 mou ...

  9. JavaScript之深入函数(一)

    在任何编程语言中,函数的功能都是十分强大的,JavaScript也不例外.之前已经讲解了函数的一些基本知识,诸如函数定义,函数执行和函数返回值等,今天就带大家深入了解JavaScript中函数的原理及 ...

  10. hibernate保存中文mysql

    最近自己做测试的时候,发现中文入库成?号了,不由得想到是否某处编码设置问题. 1.检查数据库是否为utf8 2.检查配置文件,数据库链接地址是否指定编码,经检查本地配置文件 jdbc.url=jdbc ...