在进行多线程编程时,Synchronized 基本上都会涉及到,但是一直都是停留在使用的层面,没有系统的了解过,更没有深入的研究过,现在做个简单的整理,有不足的地方,忘大神们多多指教:
- 类上非静态 Synchronized 方法,锁为 this 对象,是对象锁
在多个线程同时执行同一个实例的 Synchronized 的两个非静态方法时,后面的线程会被阻塞,进入锁池中
在多个线程同时执行不同实例的 Synchronized 的两个非静态方法时,线程直接没有影响
- 类上的静态 Synchronized 方法,锁为 类.clss 对象,其实质也是一个对象(类Class 对象的实例),是类锁
在多个线程同时执行同一个实例或不同实例的 Synchronized 的两个静态方法时,后面的线程会被阻塞
- 当存在一个线程竞争对象锁,另一个线程竞争类锁时,这两个线程时不存在竞争关系的,即对象锁与类锁不是同一把锁
- jvm 中,每个对象和类在逻辑上都和一个监视器相关联,即 对象锁与类锁
- 一个线程可以多次对同一个对象上锁,对于每一个对象,java虚拟机维护一个加锁计数器,线程每获得
一次对对象,计数器就加 1,每释放一次,计数器就减 1,当计数器值为 0 时,锁就被释放了
- 在用 String 类型作为锁时,一定要注意因为 String 类型有 常量池,两个String类型可能 == 判断是true
- 在用对象实例做同步锁时,判断是否为同一个锁的依据是 对象没存地址是否相同
内存地址是否相同,可以用 == 来判断,不等价于 hashCode
如果两个对象 == 为 true时,则该对象锁为同一个锁
如果两个对象 == 为 false时,则该对象锁不为同一个锁
在多个线程同时执行同一个实例的 Synchronized 的两个非静态方法时,后面的线程会被阻塞,进入锁池中
在多个线程同时执行不同实例的 Synchronized 的两个非静态方法时,线程直接没有影响
在多个线程同时执行同一个实例或不同实例的 Synchronized 的两个静态方法时,后面的线程会被阻塞
一次对对象,计数器就加 1,每释放一次,计数器就减 1,当计数器值为 0 时,锁就被释放了
内存地址是否相同,可以用 == 来判断,不等价于 hashCode
如果两个对象 == 为 true时,则该对象锁为同一个锁
如果两个对象 == 为 false时,则该对象锁不为同一个锁
下面贴几个实例来具体验证下:
默认情况下 synchronized 修饰的非静态方法,其锁为 this,与 synchronized(this) 效果一样:
public class Synchronized {
public static void main(String[] args) {
Synchronized sync1 = new Synchronized();
Synchronized sync2 = new Synchronized();
/**
当两个线程运行的不是同一个类实例时,结果是
sync2 is running
sync1 is running
sync1-0
sync2-0
sync2-1
sync1-1
sync2-2
sync1-2
sync2 is end
sync1 is end
可见两个线程执行的顺序没有影响
所以可以说明:同一个类中 synchronized 声明的非静态方法,其锁不为 Synchronized.class
同一个类的两个实例在访问 synchronized 声明的非静态方法时,互不影响
*/
syncTest(sync1, sync2);
/*
当两个线程运行的 是同一个类实例时,结果是
sync1 is running
sync1-0
sync1-1
sync1-2
sync1 is end
sync2 is running
sync2-0
sync2-1
sync2-2
sync2 is end
可见两个线程执行的顺序有明显的先后顺序
所以可以说明:同一个类中,同一个实例在访问 synchronized 修饰的不同方法时,是需要等待 synchronized 锁的
默认的 synchronized 修改的方法的锁为 this
*/
syncTest(sync1, sync1);
}
public static void syncTest(final Synchronized sync1, final Synchronized sync2) {
Thread th1 = new Thread(new Runnable() {
@Override
public void run() {
sync1.sync1();
}
}, "th-1");
th1.start();
Thread th2 = new Thread(new Runnable() {
@Override
public void run() {
sync2.sync2();
}
}, "th-2");
th2.start();
}
/**
* 同步方法一
*/
public synchronized void sync1() {
System.out.println("sync1 is running");
try {
for(int i = 0; i < 3; i++) {
Thread.sleep(2000);
System.out.println("sync1-" + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sync1 is end");
}
/**
* 同步方法二
*/
public synchronized void sync2() {
System.out.println("sync2 is running");
try {
for(int i = 0; i < 3; i++) {
Thread.sleep(2000);
System.out.println("sync2-" + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sync2 is end");
}
}
上面是对非静态 synchronized 的方法。
下面是静态的 synchronized 运行结果:
默认情况下,synchronized 修饰的静态方法,其锁为 类.class
public class Synchronized {
public static void main(String[] args) {
Synchronized sync1 = new Synchronized();
Synchronized sync2 = new Synchronized();
/**
当两个线程运行的不是同一个类实例时,结果是
sync1 is running
sync1-0
sync1-1
sync1-2
sync1 is end
sync2 is running
sync2-0
sync2-1
sync2-2
sync2 is end
可见对于静态的 synchronized 修饰的方法,在同时调用不同实例的 同步 方法时,也是会阻塞的
*/
syncTest(sync1, sync2);
/*
当两个线程运行的 是同一个类实例时,结果是
sync1 is running
sync1-0
sync1-1
sync1-2
sync1 is end
sync2 is running
sync2-0
sync2-1
sync2-2
sync2 is end
可见对于静态的 synchronized 修饰的方法,在同时调用相同实例的 同步 方法时,同样会阻塞
*/
syncTest(sync1, sync1);
}
public static void syncTest(final Synchronized sync1, final Synchronized sync2) {
Thread th1 = new Thread(new Runnable() {
@Override
public void run() {
sync1.sync1();
}
}, "th-1");
th1.start();
Thread th2 = new Thread(new Runnable() {
@Override
public void run() {
sync2.sync2();
}
}, "th-2");
th2.start();
}
/**
* 同步方法一
*/
public static synchronized void sync1() {
System.out.println("sync1 is running");
try {
for(int i = 0; i < 3; i++) {
Thread.sleep(2000);
System.out.println("sync1-" + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sync1 is end");
}
/**
* 同步方法二
*/
public static synchronized void sync2() {
System.out.println("sync2 is running");
try {
for(int i = 0; i < 3; i++) {
Thread.sleep(2000);
System.out.println("sync2-" + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sync2 is end");
}
}
对于 synchronized 修饰的静态方法,因为其锁默认为 类.class,所以在多个线程同时运行时,会存在锁竞争的情况,所以会阻塞
下面是对 synchronized(实例对象) 的使用:
public class Synchronized2 {
String ob1 = new String("sync");
String ob2 = ob1;
public static void main(String[] args) {
final Synchronized2 sy1 = new Synchronized2();
System.out.println("ob1 == ob2: " + (sy1.ob1 == sy1.ob2));
System.out.println("ob1.hashCode() == ob2.hashCode(): " + (sy1.ob1.hashCode() == sy1.ob2.hashCode()));
Thread th1 = new Thread(new Runnable() {
@Override
public void run() {
sy1.syncTest(sy1.ob1);
}
}, "th-1");
Thread th2 = new Thread(new Runnable() {
@Override
public void run() {
sy1.syncTest(sy1.ob2);
}
}, "th-2");
th1.start();
th2.start();
}
public void syncTest(Object sync) {
synchronized (sync) {
System.out.println("thread name : " + Thread.currentThread().getName());
try {
for(int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
上面代码运行结果为:
ob1 == ob2: true
ob1.hashCode() == ob2.hashCode(): true
thread name : th-1
th-1 : 0
th-1 : 1
th-1 : 2
thread name : th-2
th-2 : 0
th-2 : 1
th-2 : 2
原因是:两个线程竞争的锁是同一个锁,因为 ob1==ob2 结果为true。
下面看,如果把上面的
String ob1 = new String("sync");
String ob2 = ob1;
改为:
String ob1 = new String("sync");
String ob2 = new String("sync");
结果就为:
ob1 == ob2: false ob1.hashCode() == ob2.hashCode(): true thread name : th-2 th-2 : 0 thread name : th-1 th-1 : 0 th-1 : 1 th-2 : 1 th-2 : 2 th-1 : 2
因为此时两个线程竞争的锁不为同一个锁,所以不存在锁竞争。
下面的实例来自网上,原文出自(抱歉,没有保存到)
运行结果为:public class Run { public static void main(String[] args) { Service service1 = new Service("xiaobaoge"); ThreadA a = new ThreadA(service1); a.setName("A"); a.start(); ThreadB b = new ThreadB(service1); b.setName("B"); b.start(); } } class Service { String anyString = new String(); public Service(String anyString){ this.anyString = anyString; } public void setUsernamePassword(String username, String password) { try { synchronized (anyString) { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入同步块"); Thread.sleep(1000); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开同步块"); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class ThreadA extends Thread { private Service service; public ThreadA(Service service) { super(); this.service = service; } @Override public void run() { service.setUsernamePassword("a", "aa"); } } class ThreadB extends Thread { private Service service; public ThreadB(Service service) { super(); this.service = service; } @Override public void run() { service.setUsernamePassword("b", "bb"); } }
线程名称为:A在1482044312482进入同步块 线程名称为:A在1482044313483离开同步块 线程名称为:B在1482044313483进入同步块 线程名称为:B在1482044314483离开同步块
因为作为锁的对象为同一个对象 service1 的 anyString 对象,所以存在锁竞争,所以两个进程会按顺序输出
如果我将 main 方法改为如下,结果会是什么呢:
public static void main(String[] args) { Service service1 = new Service("xiaobaoge"); Service service2 = new Service("xiaobaoge"); System.out.println(service1.anyString == service2.anyString); ThreadA a = new ThreadA(service1); a.setName("A"); a.start(); ThreadB b = new ThreadB(service2); b.setName("B"); b.start(); }
这次我传的对象为两个不同的对象,那么输出结果是什么呢?答案是不变的:
true 线程名称为:A在1482044611516进入同步块 线程名称为:A在1482044612517离开同步块 线程名称为:B在1482044612517进入同步块 线程名称为:B在1482044613518离开同步块
因为这个例子中,作为锁的不是 两个 service1 与 service2,而是其中的 anyString 字符串对象。写到这,可能细心的读者会发现,在用 String 类型作为锁时,一定要注意,因为在jvm 中存在一个 字符串常量池 的内存区域,这就涉及到有关 String 的内存空间分配的问题了,如:
String str1 = "12345";
String str2 = new String("12345");
String str3 = "12345".intern();
他们直接先后顺序不一样,变量的内存地址也是不一样的