java同步块(Synchronized block)用来标记方法或者代码块是同步的。java同步块用来避免竞争。
Java同步关键字(synchronized)
java中的同步块用synchronized标记。同步块在java中是同步在某个对象上。所有同步在一个对象上的同步块在同时只能被一个线程进入并执行操作。所有其他等待进入该同步块的线程将被阻塞,之道执行该同步块中的线程退出。
有四种不同的同步块:
- 实例方法
- 静态方法
- 实例方法中的同步块
- 静态方法中的同步块
上述同步块都同步在不同对象上。实际需要哪种同步块视具体情况而定。
实例方法同步
public synchronized void add(int value){
this.count+=value;
}
注意在方法中声明同步关键字(synchronized)。这告诉java该方法是同步的。
java实例方法同步是同步在拥有该方法的对象上。这样,每个实例其方法同步都同步在不同对象上,即该方法所属的实例。只有一个线程在实例方法同步块中运行。如果有多个实例存在,那么一个线程一次可以在一个实例同步块中执行操作。一个实例一个线程。
静态方法同步
静态方法同步和实例方法同步一样,也是用synchronized关键字。
public synchronized void add(int value){
this.count+=value;
}
静态方法的同步是指同步在该方法所在的类对象上。因为在java虚拟机中一个类对应一个类对象,所以同时只允许一个线程执行同一个类中的静态同步方法。
对于不同类的静态同步方法,一个线程可以执行每个类中的静态同步方法而无需等待。不管类中的哪个静态同步方法被调用。一个类只能由一个线程同步执行。
实例方法中的同步块
有时不需要同步整个方法,而是同步方法中的一部分。Java可以对方法的一部分进行同步。
public void add(int value){
synchronized(this){
this.count+=value;
}
}
注意java同步块构造器用括号将对象括起来。在上面代码中,使用了“this”,即为调用add方法的实例本身。在同步构造器中用括号括起来的对象叫做监听器对象。上面代码使用监听器对象同步,同步实例方法使用调用方法本身的实例作为监听器对象。
一次只有一个线程能够在同步于同一个监听器对象的java方法内执行。
下面两个方法都同步在他们所调用的实例对象上,因此他们在同步的执行效果上是等效的。
public class MyClass{
public synchronized void log(String s1,String s2){
log.writeln(s1);
log.writeln(s2);
}
public void log2(String s1,String s2){
synchronized(this){
log.writeln(s1);
log.writeln(s2);
}
}
}
在上例中,每次只有一个线程能够在两个同步块中任意一个方法内执行。
如果第二个同步块不是同步在this实例对象上,那么两个方法可以被线程同时执行。
静态方法中的同步块
public class MyClass{
public static synchronized void log(String s1,String s2){
log.writeln(s1);
log.writeln(s2);
}
public static void log2(String s1,String s2){
synchronized(this){
log.writeln(s1);
log.writeln(s2);
}
}
}
同样,这两个方法不能同时被线程访问。
实例
public class Count {
public long count=0;
public synchronized void add(long value) {
this.count+=value;
}
}
public class CounterThread extends Thread {
protected Count counter=null;
public CounterThread(Count counter) {
this.counter=counter;
}
public void run() {
for(int i=0;i<10;i++) {
counter.add(i);
System.out.println(this.getName()+":"+counter.count);
}
}
}
public static void main(String[] args) {
Count counter=new Count();
Thread thread1=new CounterThread(counter);
Thread thread2=new CounterThread(counter);
thread1.start();
thread2.start();
}
运行结果:
Thread-1:0
Thread-1:1
Thread-1:3
Thread-0:0
Thread-0:7
Thread-0:9
Thread-0:12
Thread-0:16
Thread-0:21
Thread-0:27
Thread-0:34
Thread-0:42
Thread-0:51
Thread-1:6
Thread-1:55
Thread-1:60
Thread-1:66
Thread-1:73
Thread-1:81
Thread-1:90
创建了两个线程。他们的构造器引用了同一个Count实例。Count.add方法是同步在实例上,是因为add方法是实例方法并且被标记上synchronized关键字。因此每次只允许一个线程调用该方法。另外一个线程必须要等到第一个线程退出add方法时,才能继续执行方法。
如果两个线程引用了两个不同的Count实例,那么他们可以同时调用add方法。这些方法调用了不同的对象,因此这些方法也就同步在不同的对象上。这些方法的调用将不会被阻塞。
public static void main(String[] args) {
Thread thread1=new CounterThread(new Count());
Thread thread2=new CounterThread(new Count());
thread1.start();
thread2.start();
}
运行结果:
Thread-0:0
Thread-1:0
Thread-1:1
Thread-0:1
Thread-0:3
Thread-0:6
Thread-0:10
Thread-0:15
Thread-0:21
Thread-0:28
Thread-0:36
Thread-0:45
Thread-1:3
Thread-1:6
Thread-1:10
Thread-1:15
Thread-1:21
Thread-1:28
Thread-1:36
Thread-1:45