多线程系列:java同步块
2016-04-11 10:45
411 查看
Java同步关键字(synchronized)
Java中的同步块用synchronized标记。同步块在Java中是同步在某个对象上。所有同步在一个对象上的同步块在同时只能被一个线程进入并执行操作。所有其他等待进入该同步块的线程将被阻塞,直到执行该同步块中的线程退出。有四种不同的同步块:实例方法
静态方法
实例方法中的同步块
静态方法中的同步块
上述同步块都同步在不同对象上。实际需要那种同步块视具体情况而定。
实例方法同步
下面是一个同步的实例方法:1 | public synchronized void add( int value){ |
2 | this .count +=value; |
3 | } |
Java实例方法同步是同步在拥有该方法的对象上。这样,每个实例其方法同步都同步在不同的对象上,即该方法所属的实例。只有一个线程能够在实例方法同步块中运行。如果有多个实例存在,那么一个线程一次可以在一个实例同步块中执行操作。一个实例一个线程。
静态方法同步
静态方法同步和实例方法同步方法一样,也使用synchronized关键字。Java静态方法同步如下示例:1 | public static synchronized void add( int value){ |
2 | count |
3 | } |
静态方法的同步是指同步在该方法所在的类对象上。因为在Java虚拟机中一个类只能对应一个类对象,所以同时只允许一个线程执行同一个类中的静态同步方法。
对于不同类中的静态同步方法,一个线程可以执行每个类中的静态同步方法而无需等待。不管类中的那个静态同步方法被调用,一个类只能由一个线程同时执行。
实例方法中的同步块
有时你不需要同步整个方法,而是同步方法中的一部分。Java可以对方法的一部分进行同步。在非同步的Java方法中的同步块的例子如下所示:
1 | public void add( int value){ |
2 |
3 | synchronized ( this ){ |
4 | this .count +=value; |
5 | } |
6 | } |
注意Java同步块构造器用括号将对象括起来。在上例中,使用了“this”,即为调用add方法的实例本身。在同步构造器中用括号括起来的对象叫做监视器对象。上述代码使用监视器对象同步,同步实例方法使用调用方法本身的实例作为监视器对象。
一次只有一个线程能够在同步于同一个监视器对象的Java方法内执行。
下面两个例子都同步他们所调用的实例对象上,因此他们在同步的执行效果上是等效的。
01 | public class MyClass { |
02 |
03 | public synchronized void log1(String |
04 | log.writeln(msg1); |
05 | log.writeln(msg2); |
06 | } |
07 |
08 | public void log2(String |
09 | synchronized ( this ){ |
10 | log.writeln(msg1); |
11 | log.writeln(msg2); |
12 | } |
13 | } |
14 | } |
如果第二个同步块不是同步在this实例对象上,那么两个方法可以被线程同时执行。
静态方法中的同步块
和上面类似,下面是两个静态方法同步的例子。这些方法同步在该方法所属的类对象上。
01 | public class MyClass { |
02 | public static synchronized void log1(String |
03 | log.writeln(msg1); |
04 | log.writeln(msg2); |
05 | } |
06 |
07 | public static void log2(String |
08 | synchronized (MyClass. class ){ |
09 | log.writeln(msg1); |
10 | log.writeln(msg2); |
11 | } |
12 | } |
13 | } |
如果第二个同步块不是同步在MyClass.class这个对象上。那么这两个方法可以同时被线程访问。
Java同步实例
在下面例子中,启动了两个线程,都调用Counter类同一个实例的add方法。因为同步在该方法所属的实例上,所以同时只能有一个线程访问该方法。
publicclassTest{
publicstaticvoidmain(String[]args){
//TODOAuto-generatedmethodstub
Testtest=newTest();
Countercounter=test.newCounter();
ThreadthreadA=test.newCounterThread(counter);
ThreadthreadB=test.newCounterThread(counter);
threadA.start();
threadB.start();
}
publicclassCounter{
longcount=0;
publicsynchronizedvoidadd(longvalue){
this.count+=value;
System.out.println(Thread.currentThread().getName()+"count:===="+count);
}
}
publicclassCounterThreadextendsThread{
protectedCountercounter=null;
publicCounterThread(Countercounter){
this.counter=counter;
}
publicvoidrun(){
for(inti=0;i<10;i++){
counter.add(i);
}
}
}
}
创建了两个线程。他们的构造器引用同一个Counter实例。Counter.add方法是同步在实例上,是因为add方法是实例方法并且被标记上synchronized关键字。因此每次只允许一个线程调用该方法。另外一个线程必须要等到第一个线程退出add()方法时,才能继续执行方法。
运行结果如下:
Thread-0count:====1
Thread-0count:====3
Thread-0count:====6
Thread-0count:====10
Thread-0count:====15
Thread-0count:====21
Thread-0count:====28
Thread-0count:====36
Thread-0count:====45
Thread-1count:====45
Thread-1count:====46
Thread-1count:====48
Thread-1count:====51
Thread-1count:====55
Thread-1count:====60
Thread-1count:====66
Thread-1count:====73
Thread-1count:====81
Thread-1count:====90</span></span>
说明一下:多执行几次,可能每次过程的值有可能不一样,但是最终结果会是一样,都是90。原因在于CounterThread类中的run方法中的for循环,这里synchronizedadd方法能保证counter类中add方法对于同一个实例对象是同步的,但for循环不是同步的。可以跑一下看看,如果效果不明显,可以在for循环里面,sleep一会。
如果两个线程引用了两个不同的Counter实例,那么他们可以同时调用add()方法。这些方法调用了不同的对象,因此这些方法也就同步在不同的对象上。这些方法调用将不会被阻塞。如下面这个例子所示:
//TODOAuto-generatedmethodstub
Testtest=newTest();
Countercountera=test.newCounter();
Countercounterb=test.newCounter();
ThreadthreadA=test.newCounterThread(countera);
ThreadthreadB=test.newCounterThread(counterb);
threadA.start();
threadB.start();
}
注意这两个线程,threadA和threadB,不再引用同一个counter实例。CounterA和counterB的add方法同步在他们所属的对象上。调用counterA的add方法将不会阻塞调用counterB的add方法。
输出:
下面结合例子来分析:
静态同步方法问题
如下代码是两个静态同步方法01 | Class A{ |
02 |
03 | public static synchronized void write( boolean b){ |
04 | isTrue |
05 | } |
06 |
07 | public static synchronized boolean read(){ |
08 | return isTrue; |
09 | } |
10 | } |
线程1访问A.write(true)方法时,线程2能访问A.read()方法吗?
线程1访问newA().write(false)方法时,线程2能访问newA().read()方法吗?
线程1访问A.write(false)方法时,线程2能访问newA().read()方法吗?
实例同步方法问题
如下代码是两个实例同步方法1 | public synchronized void write( boolean b){ |
2 | isTrue |
3 | } |
4 |
5 | public synchronized boolean read(){ |
6 | return isTrue; |
7 | } |
Aa=newA();线程1访问a.write(false)方法,线程2能访问a.read()方法吗?
Aa=newA();Ab=newA();线程1访问a.write(false)方法,线程2能访问b.read()方法吗?
回答问题之前,先想一下当前方法使用的锁是哪一个?当前线程是否有拿到这把锁?拿到锁了就能访问当前方法了。
答案
我们先回顾基础知识,Java中的每一个对象都可以作为锁,而不同的场景锁是不一样的。对于实例同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前对象的Class对象。
对于同步方法块,锁是Synchonized括号里配置的对象。
线程1访问A.write()方法时,线程2能访问A.read()方法吗?不能,因为静态方法的锁都是A.Class对象,线程1拿到锁之后,线程2就拿不到锁了。
线程1访问newA().write()方法时,线程2能访问newA().read()方法吗?不能,原因同上。
线程1访问A.write()方法时,线程2能访问newA().read()方法吗?不能,原因同上
Aa=newA();线程1访问a.write()方法,线程2能访问a.read()方法吗?不能,因为这两个方法的锁都是对象a,线程1拿到了锁,线程2就不能访问了。
Aa=newA();Ab=newA();线程1访问a.write()方法,线程2能访问b.read()方法吗?可以,因为线程1拿到的是锁是a,而线程2访问b.read()需要的是锁是b。
现在你应该明白了这句话,对于实例同步方法,锁是当前实例对象。对于静态同步方法,锁是当前对象的Class对象。
************评论区选摘如下*********
synchronized是针对内存对象加锁,对象头部会标记是否被加锁,在偏向里面还会和线程ID记录下来;
知道几个级别都离不开对象和锁的关系,也就是锁始终在对象上;
常见的无非是:class(class也是一个java对象,只是他相对自己的对象很特殊就是了)、其次是this(也就是new出来的对象)、再其次是自己顶一个Object,当然自己的object如果是static的那也是全局的;
对非静态方法加锁,就是对this加锁,所有相关的this加锁以及非静态方法加锁,都会相互会产生排他;对静态方法加锁,其实就是对class本身进行加锁,所有相关静态方法加锁的以及对class加相互隔离;object就是自定义;
对于方法,如果有方法使用了加锁,未进行加锁的方法不会被隔离;静态方法也是如此;
原文地址:http://ifeve.com/synchronized-blocks/
相关文章推荐
- java程序在内存中的分布
- java程序在内存中的分布
- mujava的配置和运行Demo
- baseDao 使用spring3+hibernate4方式
- 码农小汪-spring框架学习之3-spring Method injection(方法注入 ) Lookup method injection
- java 自增操作是原子操作
- Java设计模式--工厂模式的自述
- Java学习之正则表达式入门
- Commons Collections Java反序列化漏洞利用
- eclipse项目有时不能启动原因
- Java单链表反转 详细过程
- springMVC两种实现多文件上传及效率比较
- java下载、安装与配置
- 关于Eclipse的git功能(egit插件)执行replace 依然显示差异问题的处理
- eclipse
- Spring JDBC 编译出错
- 【转载】Java垃圾回收机制
- Java ActiveMQ连接池使用
- java反射原理
- PropertyAccessException 1: org.springframework.beans.MethodInvocationException