在Java开发的时候经常会用到关键字synchronized来对代码进行同步,在使用的过程中,对于synchronized确不是很熟悉,最近在看Spring源码时,发现有不少地方都用到同步,因此,趁此机会,研究一下。

1. synchronized锁的对象

      Java中的每一个对象都可以作为锁。

1)对于同步方法,锁是当前实例对象。

2)对于静态同步方法,锁是当前对象的Class对象。因为在Java 虚拟机中一个类只能对应一个类对象,所以同时只允许一个线程执行同一个类中的静态同步方法。

3)对于同步方法块,锁是Synchonized括号里配置的对象。

下面我们将一一根据代码来介绍这几种锁。

2. 同步实例方法

对于同步方法,锁是当前实例对象。即在同一时刻只能有一个线程可以访问该实例的同步方法。但是请注意,如果有多个实例对象,那么不同示例之间不受影响,线程可以同时访问不同示例的同步方法。看下面这段代码:

package com.bj.chenfeic.concurrency;

/**
* 同步测试,提供了用于同步的实例方法
*
* @author chenfei0801
*
*/
public class Sync { public synchronized void testSync() {
long id = Thread.currentThread().getId();
System.out.println("线程:" + id + "进入同步块");
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + id + "退出同步块");
}
}
package com.bj.chenfeic.concurrency;

/**
*
* @author chenfei0801
*
*/
public class SyncTread implements Runnable { @Override
public void run() {
Sync sync = new Sync();
sync.testSync();
} }
package com.bj.chenfeic.concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
*
* @author chenfei0801
*
*/
public class SyncTest { /**
* @param args
*/
public static void main(String[] args) {
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool();
for(int i=;i<;i++) {
newFixedThreadPool.execute(new SyncTread());
}
newFixedThreadPool.shutdown(); } }

执行结果:

线程:11进入同步块
线程:13进入同步块
线程:12进入同步块
线程:13退出同步块
线程:11退出同步块
线程:12退出同步块

从执行结果中,我们可以看到testSync并没有同步,synchronized没有生效。这是因为在SyncTherd中每次都是new Sync();这样synchronized其实是作用到不同的对象上去了,所以各个线程之间其实并没有同步。

按照如下修改SynsThread、SyncTest代码,即可达到同步作用

package com.bj.chenfeic.concurrency;

/**
*
* @author chenfei0801
*
*/
public class SyncTread implements Runnable {
private Sync sync;
public SyncTread(Sync sync) {
this.sync = sync;
} @Override
public void run() {
this.sync.testSync();
} }
package com.bj.chenfeic.concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
*
* @author chenfei0801
*
*/
public class SyncTest { /**
* @param args
*/
public static void main(String[] args) {
Sync sync = new Sync();//只实例化一次
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool();
for (int i = ; i < ; i++) {
newFixedThreadPool.execute(new SyncTread(sync));
}
newFixedThreadPool.shutdown(); } }

执行结果

线程:11进入同步块
线程:11退出同步块
线程:12进入同步块
线程:12退出同步块
线程:13进入同步块
线程:13退出同步块

从上面结果中可以看出,SyncThread中使用的是同一个对象,此时线程是互斥访问testSync()方法的,即达到了同步的作用。

3、同步静态方法

        对于静态方法,锁的对象不再是类的某个实例,而是类对象(Class)。因为在Java 虚拟机中一个类只能对应一个类对象,所以在同一时刻只允许一个线程执行该类中的静态同步方法。因此它的同步可以认为是全局的。如下面的例子所示:

package com.bj.chenfeic.concurrency;

/**
* 同步测试,提供了用于同步的实例方法
*
* @author chenfei0801
*
*/
public class Sync { //同步静态方法块
public synchronized static void testSync() {
long id = Thread.currentThread().getId();
System.out.println("线程:" + id + "进入同步块");
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + id + "退出同步块");
}
}
package com.bj.chenfeic.concurrency;

/**
*
* @author chenfei0801
*
*/
public class SyncTread implements Runnable { @Override
public void run() {
// Sync.testSync();
new Sync().testSync();//为了说明同步机制,我们此处先实例化对象。正常调用应该还是按照上行那样处理。
} }

  

package com.bj.chenfeic.concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
*
* @author chenfei0801
*
*/
public class SyncTest { /**
* @param args
*/
public static void main(String[] args) {
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool();
for (int i = ; i < ; i++) {
newFixedThreadPool.execute(new SyncTread());
}
newFixedThreadPool.shutdown(); } }

执行结果:

线程:12进入同步块
线程:12退出同步块
线程:13进入同步块
线程:13退出同步块
线程:11进入同步块
线程:11退出同步块

从执行结果上,可以看到线程是同步访问代码块的。为了说明我们在SyncTread中每次都通过new的方式,实例化了新的对象。虽然在每个线程中,每个Sync对象都不相同,但是由于同步的是静态方法,因此所锁作用在所有Sync对象上。

     
4、同步方法块

除了将对整个方法加锁进行同步外,Java还支持对方法内部的方法快进行同步,这样做个人觉得有两个好处

1)一个方法可能还含有不需要同步的部分,而且有可能这个不需要同步的代码执行过程很耗时,这样就会影响其他线程执行,严重影响效率。

package com.bj.chenfeic.concurrency;

/**
* 同步测试,提供了用于同步的实例方法
*
* @author chenfei0801
*
*/
public class Sync { public void testSync() {
long id = Thread.currentThread().getId();
synchronized (this) {
System.out.println("线程:" + id + "进入同步块");
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + id + "退出同步块");
}
try {
Thread.sleep();//耗时操作,未同步
System.out.println("线程 "+ id +" 未同步部分");
} catch (InterruptedException e) {
e.printStackTrace();
} }
}
package com.bj.chenfeic.concurrency;

/**
*
* @author chenfei0801
*
*/
public class SyncTread implements Runnable {
private Sync sync; public SyncTread(Sync sync) {
this.sync = sync;
} public SyncTread() {
this.sync = new Sync();//不传参数时每次实例化一个对象
} @Override
public void run() {
this.sync.testSync();
} }
package com.bj.chenfeic.concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
*
* @author chenfei0801
*
*/
public class SyncTest { /**
* @param args
*/
public static void main(String[] args) {
Sync sync = new Sync();
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool();
for (int i = ; i < ; i++) {
newFixedThreadPool.execute(new SyncTread(sync));//同一个sync对象
}
newFixedThreadPool.shutdown(); } }

执行结果:

线程:11进入同步块
线程:11退出同步块
线程:13进入同步块
线程:13退出同步块
线程:12进入同步块
线程:12退出同步块
线程 未同步部分
线程 未同步部分
线程 未同步部分

从执行结果中可以看出,synchronized同步的代码块都是互斥访问的,对于耗时的操作则可以异步执行。提高了效率。注意的是,例子中synchronized后面括号中为this。说明锁是加在当前Sync实例对象上的。如果SyncTread中Sync对象不是同一个,则各个对象之间的代码不会同步。

上面的例子中,锁对象为当前的对象实例(this)。跟我们在“同步实例方法”中遇到一个同样的问题,如果每个线程都是重新实例化了一个对象,那么这些锁都会加在不同的实例对象上,这样此时每个对象之间都是互相独立的,这样就达不到同步的目的了。

修改SyncTest类的代码:

package com.bj.chenfeic.concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
*
* @author chenfei0801
*
*/
public class SyncTest { /**
* @param args
*/
public static void main(String[] args) {
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool();
for (int i = ; i < ; i++) {
newFixedThreadPool.execute(new SyncTread());//没有传递任何参数
}
newFixedThreadPool.shutdown(); } }
SyncTread在实例化时如果没有传递参数,则每次都会实例化一个Sync对象。

运行结果如下:

线程:11进入同步块
线程:12进入同步块
线程:13进入同步块
线程:11退出同步块
线程:13退出同步块
线程:12退出同步块
线程 未同步部分
线程 未同步部分
线程 未同步部分

此时该如何处理?我们在“同步静态方法”中提到,静态方法之所以是全局的,是因为锁的对象不再是类的某个实例,而是整个类对象(Class)。因此我们在对方法块处理时,也可以同样的处理。

在上述代码的基础上修改Sync类

package com.bj.chenfeic.concurrency;

/**
* 同步测试,提供了用于同步的实例方法
*
* @author chenfei0801
*
*/
public class Sync { public void testSync() {
long id = Thread.currentThread().getId();
synchronized (Sync.class) {//全局锁
System.out.println("线程:" + id + "进入同步块");
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + id + "退出同步块");
}
try {
Thread.sleep();//耗时操作
System.out.println("线程 "+ id +" 未同步部分");
} catch (InterruptedException e) {
e.printStackTrace();
} }
}

执行结果为

线程:11进入同步块
线程:11退出同步块
线程:12进入同步块
线程:12退出同步块
线程:13进入同步块
线程:13退出同步块
线程 未同步部分
线程 未同步部分
线程 未同步部分

从执行结果上可以看出,各个线程虽然都实例化了各自的Sync对象,但是却是在互斥访问Sync中的同步块。

2)同步方法块,除了对可以减少同步区域,还可以减小锁的范围,在对方法进行同步时,同步的对象都是对象的实例或者类对象。如果一个类有多个同步方法,如果当前线程进入了其中一个方法里,这个类的其他方法此时也被锁住了,其他线程无法进入。一般情况下,可能没有问题,但是如果如果这些方法或者部分方法之间其实并没有什么关系,那么互斥访问显然会影响效率,看下面的例子:

package com.bj.chenfeic.concurrency;

/**
* 同步测试,提供了用于同步的实例方法
*
* @author chenfei0801
*
*/
public class Sync { /** Synchronization monitor for the "active" flag 。仅仅用于同步*/
private final Object activeMonitor = new Object(); /** Synchronization monitor for the "refresh" and "destroy"仅仅用于同步 */
private final Object startupShutdownMonitor = new Object(); public void refresh() {
long id = Thread.currentThread().getId();
synchronized (this.startupShutdownMonitor) {
System.out.println("线程:" + id + "进入同步块--refresh()");
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + id + "退出同步块-----refresh()");
} } public void destroy() {
long id = Thread.currentThread().getId();
synchronized (this.startupShutdownMonitor) {
System.out.println("线程:" + id + "进入同步块--destroy()");
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + id + "退出同步块-----destroy()");
} } public void active() {
long id = Thread.currentThread().getId();
synchronized (this.activeMonitor) {
System.out.println("线程:" + id + "进入同步块--active()");
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + id + "退出同步块-----active()");
} } }

Sync中有三个同步的方法,分别是refresh,destory,active。其中前两者是用的是同一个对象锁(Sync的startupShutdownMonitor对象),后者是另外的对象锁,现在我们看下测试的类。

package com.bj.chenfeic.concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
*
* @author chenfei0801
*
*/
public class SyncTest { /**
* @param args
*/
public static void main(String[] args) {
Sync sync = new Sync();
SyncTest syncTest = new SyncTest();
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool();
newFixedThreadPool.execute(syncTest.new ActiveThread(sync));
newFixedThreadPool.execute(syncTest.new RefreshThread(sync));
newFixedThreadPool.execute(syncTest.new DestoryThread(sync));
newFixedThreadPool.execute(syncTest.new ActiveThread(sync));
newFixedThreadPool.shutdown();
} class RefreshThread implements Runnable { private Sync sync; public RefreshThread(Sync sync) {
this.sync = sync;
} @Override
public void run() {
sync.refresh();
} } class DestoryThread implements Runnable { private Sync sync; public DestoryThread(Sync sync) {
this.sync = sync;
} @Override
public void run() {
sync.destroy();
} } class ActiveThread implements Runnable { private Sync sync; public ActiveThread(Sync sync) {
this.sync = sync;
} @Override
public void run() {
sync.active();
} } }

在测试类中,有3类线程,分布式调用Sync中的refreash,destory,active。在测试类中,启动了4个线程,我们看一下执行结果

线程:11进入同步块--active()
线程:13进入同步块--destroy()
线程:11退出同步块-----active()
线程:14进入同步块--active()
线程:13退出同步块-----destroy()
线程:12进入同步块--refresh()
线程:14退出同步块-----active()
线程:12退出同步块-----refresh()

从执行结果中,可以看出active之间是同步的,但是与destory、refresh之间却没有同步。线程11在在active方法时,线程13可以执行destory方法。

destory方法和refresh是同步的。

由此可见,通过对方法内部的方法快进行同步,可以灵活的决定同步块的范围和相应的锁对象。

最后,一定要记住synchronized锁住的是括号里的对象,而不是代码(方法或者方法快)

Java synchronized 总结的更多相关文章

  1. Java Synchronized Blocks

    From http://tutorials.jenkov.com/java-concurrency/synchronized.html By Jakob Jenkov   A Java synchro ...

  2. java synchronized(一)

    java synchronized主要用于控制线程同步,中间有很多小的细节,知识,这里我简单的整理一下,做个记录.主要用于方法和代码块的控制 先说说方法控制 模拟银行存款和取款,创建一个Account ...

  3. java synchronized使用

    java synchronized 基本上,所有并发的模式在解决线程冲突问题的时候,都是采用序列化共享资源的方案.这意味着在给定时刻只允许一个任务访问该资源.这个一般通过在代码上加一条锁语句实现,因为 ...

  4. Java synchronized 详解

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

  5. Java Synchronized 关键字

    本文内容 Synchronized 关键字 示例 Synchronized 方法 内部锁(Intrinsic Locks)和 Synchronization 参考资料 下载 Demo Synchron ...

  6. Java synchronized 关键字详解

    Java synchronized 关键字详解 前置技能点 进程和线程的概念 线程创建方式 线程的状态状态转换 线程安全的概念 synchronized 关键字的几种用法 修饰非静态成员方法 sync ...

  7. Java synchronized对象级别与类级别的同步锁

    Java synchronized 关键字 可以将一个代码块或一个方法标记为同步代码块.同步代码块是指同一时间只能有一个线程执行的代码,并且执行该代码的线程持有同步锁.synchronized关键字可 ...

  8. java synchronized详解

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

  9. Java synchronized

    1. 将synchronized加在方法上, 即可实现对此方法的同步 public synchronized void deposit(float amt) { float tmp = amount; ...

  10. Java synchronized指南

    在多线程程序中,同步修饰符用来控制对临界区代码的访问.其中一种方式是用synchronized关键字来保证代码的线程安全性.在Java中,synchronized修饰的代码块或方法不会被多个线程并发访 ...

随机推荐

  1. Android开发视频学习(2)

    S02E05_Android当中的线程 Worker Thread不允许操作UI,只能在Main Thread操作UI S02E06_Handler(一) Handler,Looper,Message ...

  2. Alexander Grothendieck去世了

    Alexander Grothendieck (German: [ˈɡroːtn̩diːk]; French: [ɡʁɔtɛndik]; 28 March 1928 – 13 November 201 ...

  3. HDU-1700 Points on Cycle

    这题的俩种方法都是看别人的代码,方法可以学习学习,要多看看.. 几何题用到向量.. Points on Cycle Time Limit: 1000/1000 MS (Java/Others)     ...

  4. win7下的vxworks总结

    在visualbox下运行vxworks 先来看一张效果图: 在tornado端 成功运行第一个程序,输出了visualbox can run the vxworks ! 在vmware下的速度快多了 ...

  5. 2015美团网 哈工大 第k个排列

    leetcode 上的Permutation Sequence 下面是可执行代码 1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1 以1 开头 123,132,共2!个数 2 开 ...

  6. public staic void main 总结

    jvm 就是java的操作系统.深入了解jvm很必要. public:该函数的修饰符,表示该函数是公有的,无需多言. static 对于函数的修饰,表明该方法为静态方法,可以通过类名直接调用,事项对于 ...

  7.  VS2012 C#调用C++ dll

    VS2012 C#调用C++ dll 调试方法:[dll工程和调用dll的exe工程在同一个解决方案中]dll工程,属性-配置属性-调试-把 命令 为 调用该dll的exe工程的bin\Debug\* ...

  8. JUnit学习总结

    Junit简介: Junit最初是由Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework),为单元测试(Unit Test) ...

  9. linux重定向总结:如何将shell命令的输出信息自动输出到文件中保存

    在做批量实验室,例如跑批量MR的作业,我们会写好shell脚本,然后启动脚本,等所有作业执行完再去看结果,但是这些执行时的信息如何保存下来到文件中呢?下面这个命令可以完成这个任务. sh batchj ...

  10. LeetCode:Restore IP Address

    93. Restore IP Addresses Given a string containing only digits, restore it by returning all possible ...