synchronized
或许你见过很多synchronized的用法,如它修饰了方法,或者只是仅仅锁住了代码块,那么来对synchronized的用法进行分类。总的来说,synchronized用法主要分两类,一类是修饰代码块,
一类是修饰方法,并细分为修饰实例方法和静态方法,如:
静态方法:public static synchronized void increase();
实例方法:public synchronized void increase();
但synchronized关键字不能修饰类和变量。synchronized的主要作用是获得对象锁,获得该对象锁的线程对该共享对象操作时,其它线程不能访问,直到该线程释放了对象锁其它线程才可能操作该对象,
那我们现在来看一些更加具体的方法和例子。
首先是synchronized修饰代码块,synchronized用于修饰代码块的语句是:
synchronized (object){
//对object dosomething();
}
举个例子,
我们开了100个线程,每个线程对共享变量自增到1000,我们预计最后最后输出的值应该是100*1000 = 100000,那么如果没有synchronized语句块,结果会是怎么样?
public class Test {
public int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<100;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
你会发现最后的结果值是不稳定的,有时并不都是100000,那为什么会这样呢?主要是因为工作内存与主内存之间的变量还没及时被线程写入或者读出就被另外一个线程使用了,不能保持原子性,可见性和有序性。详细可以参考我之前写的文章:
http://blog.youkuaiyun.com/scau_rich/article/details/43851999
那么使用synchronized就可以保证每次执行都可以得出的结果是100000,因为它保证了原子性、可见性和有序性。修改后的代码为:
public class Test {
public int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<100;i++){
new Thread(){
public void run() {
synchronized (test) { //加入了同步原语,保证每次进入object test的线程只有一个
for(int j=0;j<1000;j++)
test.increase();
}
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
有时,我们也会看到这种代码:
synchronized (this) {
dosomethind();
}
它绑定了一个this关键字,那这个this代表的是什么呢?其实它就是该代码块所在的方法的类的实例本身,那么synchronized(this)就是获得了当前类的对象锁了,同时因为静态方法中是没有实例化方法所在的类的,所以也就没有this关键字使用synchronized(this)会编译报错。我们可以继续看例子来加深理解:
我们可以看到,使用synchronized修饰静态方法,其实与synchronized获得当前方法的类的class对象是一致的,可以使得每次进入Test类的class对象的静态变量inc的线程只有一个,保证结果。
public class SynchronizedTest {
public static void main(String[] args) {
Test test = new Test();
Thread threadA = new Thread(test, "线程1");
Thread threadB = new Thread(test, "线程2");
threadA.start();
threadB.start();
}
static class Test implements Runnable{
public int inc;
@Override
public void run() {
synchronized (this) { //this指的是绑定了该Test的实例test
for(int i = 0; i < 5; i++)
System.out.println("Thread: " +
Thread.currentThread().getName() + " 运行了第" + i + "次");
}
}
}
}
运行结果是:
Thread: 线程1 运行了第0次
Thread: 线程1 运行了第1次
Thread: 线程1 运行了第2次
Thread: 线程1 运行了第3次
Thread: 线程1 运行了第4次
Thread: 线程2 运行了第0次
Thread: 线程2 运行了第1次
Thread: 线程2 运行了第2次
Thread: 线程2 运行了第3次
Thread: 线程2 运行了第4次
我们看到this关键字是所代表的就是test实例,在线程1运行完后,线程2才获得test实例的对象锁并进行操作。
但我们会有疑问,既然静态类方法中不能使用synchronized(this)获得当前方法类的实例的对象锁,那么在静态方法前使用synchronized修饰方法又是什么意思呢?那现在我们来看一下使用synchronized修饰方法是什么意思。
1)synchronized修饰实例方法:
public class Test {
public int inc = 0;
//修饰实例方法,可以保证每次进入该类实例的线程只有一个
public synchronized void increase() {
//与在方法前使用synchronized方法效果一致
// synchronized(this) {
inc++;
// }
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<100;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
使用了synchronized修饰了increase方法后,最终的结果就可以保证是100*1000 = 100000了,这是因为其实synchronized修饰实例方法是获得了当前方法所在的实例的对象锁,我们知道,实例方法需要先实例化才能使用,所以会有一个隐藏变量this传入,而synchronized其实是获得this这个对象锁。所以与synchronized(this)效果一致了。
那么使用synchronized修饰静态方法又是获取谁的对象锁呢?
2)synchronized修饰静态方法:
public class SynchronizedTest {
public static void main(String[] args) {
Test test = new Test();
Thread threadA = new Thread(test, "线程1");
Thread threadB = new Thread(test, "线程2");
Thread threadC = new Thread(test, "线程3");
Thread threadD = new Thread(test, "线程4");
Thread threadE = new Thread(test, "线程5");
threadA.start();
threadB.start();
threadC.start();
threadD.start();
threadE.start();
}
static class Test implements Runnable{
private static int inc = 0;
//静态方法使用synchronized修饰相当于全局锁
public static synchronized void increase() {
//锁住的是Test.class对象
// synchronized(Test.class) {
inc++;
System.out.println("Thread: " + Thread.currentThread().getName() + "使用的inc值为 " + inc );
// }
}
@Override
public void run() {
for(int i = 0; i < 5000; i++) {
increase();
}
}
}
}
我们可以看到,使用synchronized修饰静态方法,其实与synchronized获得当前方法的类的class对象是一致的,可以使得每次进入Test类的class对象的静态变量inc的线程只有一个,保证结果。
总结一下使用场景,一般如果我们使用synchronized修饰实例方法,是因为有多个线程共同访问一个共享变量实例,并操作对应的方法;使用synchronized修饰静态方法,是因为需要操作该类的静态变量,即操作class对象。