每个对象或者类都对应一个同步锁,在进入使用synchronize方法或者synchronize代码块的线程会持有相应的锁,此时其他想要进入同步方法或同步代码块时必须等待(即同步阻塞,被放进lock pool);
锁的情况有一下几种
1 同步非静态方法:
此类方法为对象调用,使用的是对象级别的锁,而且该锁为线程中调用此方法的对象对应的锁(this),
来看一个例子
public class LockDemo {
public void show(){
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName()+"执行show方法 :"+i);
}
}
public static void main(String[] args) {
final LockDemo ld = new LockDemo();
new Thread(){
public void run(){
ld.show();
}
}.start();
new Thread(){
public void run(){
ld.show();
}
}.start();
}
}
首先LockDemo的show方法没有使用同步锁, 此时运行结果为:
Thread-0执行show方法 :0
Thread-1执行show方法 :0
Thread-0执行show方法 :1
Thread-1执行show方法 :1
说明两个线程是交叉执行的, (当然,结果也会有顺序执行的情况) 这说明show方法是异步的;
show方法加上同步锁后:
public synchronized void show(){
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName()+"执行show方法 :"+i);
}
}
执行结果为:
Thread-0执行show方法 :0
Thread-0执行show方法 :1
Thread-1执行show方法 :0
Thread-1执行show方法 :1
注意此时的结果一定为顺序执行, 这说明show方法是同步的, 两个线程使用的是同一个同步锁,即 ld 实例对应的同步锁;
现在给LockDemo类加一个非同步的log方法,并将其中一个线程调用的方法改为log()
public class LockDemo {
public synchronized void show(){
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName()+"执行show方法 :"+i);
}
}
public void log(){
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName()+"执行log方法 :"+i);
}
}
public static void main(String[] args) {
final LockDemo ld = new LockDemo();
new Thread(){
public void run(){
ld.show();
}
}.start();
new Thread(){
public void run(){
ld.log();
}
}.start();
}
}
此时执行的结果:
Thread-0执行show方法 :0
Thread-1执行log方法 :0
Thread-1执行log方法 :1
Thread-0执行show方法 :1
可以看到出现了线程交叉的情况,说明即使一个线程持有对象的同步锁,也不妨碍其他线程执行相同对象的非同步方法;
当log()设置成同步的,结果就不一样了;
public synchronized void log(){
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName()+"执行log方法 :"+i);
}
}
Thread-0执行show方法 :0
Thread-0执行show方法 :1
Thread-1执行log方法 :0
Thread-1执行log方法 :1
结果一定是顺序执行的,说明 Thread-0线程持有ld 实例的对象锁时,其他线程不能进入相同对象的同步方法;
简单概括一下规则,一个线程要进入同步方法必须要持有对象的锁,如果该对象的锁被占用,则其要等待锁释放(同步阻塞);若一个线程进入对象的同步方法,则该对象的所有同步方法都必须等待锁释放,才有机会被其他线程执行;
介绍一下锁释放的规则:
synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。根据虚拟机规范的要求,在执行monitorenter指令时,首先要尝试获取对象的锁,如果获得了锁,把锁的计数器加1,相应地,在执行monitorexit指令时会将锁计数器减1,当计数器为0时,锁便被释放了。由于synchronized同步块对同一个线程是可重入的,因此一个线程可以多次获得同一个对象的互斥锁,同样,要释放相应次数的该互斥锁,才能最终释放掉该锁。对于本例来讲,执行完同步方法即会释放锁;
现在再对Thread-0中执行的方法进行一下改造(在Thread-0的run方法中添加log()方法的调用):
public static void main(String[] args) {
final LockDemo ld = new LockDemo();
new Thread(){
public void run(){
ld.show();
ld.log();
}
}.start();
new Thread(){
public void run(){
ld.log();
}
}.start();
}
执行结果:
Thread-0执行show方法 :0
Thread-0执行show方法 :1
Thread-1执行log方法 :0
Thread-1执行log方法 :1
Thread-0执行log方法 :0
Thread-0执行log方法 :1
该结果说明持有锁的线程在释放锁后同样要进行锁的争抢,也再次证明,线程彻底执行完同步方法便是释放了同步锁;
我们再对例子进行下改造(Thread-1中创建匿名对象执行show()方法):
public class LockDemo {
public synchronized void show(){
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName()+"执行show方法 :"+i);
}
}
public synchronized void log(){
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName()+"执行log方法 :"+i);
}
}
public static void main(String[] args) {
final LockDemo ld = new LockDemo();
new Thread(){
public void run(){
ld.show();
}
}.start();
new Thread(){
public void run(){
new LockDemo().log();
}
}.start();
}
}
其结果:
Thread-0执行show方法 :0
Thread-1执行show方法 :0
Thread-0执行show方法 :1
Thread-1执行show方法 :1
可以看到两个线程交叉执行,为什么会出现这种情况呢?原因是两个线程持有的同步锁不是同一个锁,每个对象都对应一个同步锁,这一点要特别注意;
2同步代码块synchronize(this)
这种同步方式与第一中同步非静态方法的方式很相似,一般情况下同步方法比较消耗资源,也容易发生死锁,synchronize(this)效果会好一些;
public class LockDemo {
public void show(){
synchronized (this) {
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName()+"执行show方法 :"+i);
}
}
}
public void log(){
synchronized (this) {
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName()+"执行log方法 :"+i);
}
}
}
public static void main(String[] args) {
final LockDemo ld = new LockDemo();
new Thread(){
public void run(){
ld.show();
}
}.start();
new Thread(){
public void run(){
ld.show();
}
}.start();
}
}
不在做过多演示
3同步代码块synchronize(obj)
实例:注意show()和log()方法中同步代码块的锁是不一样的
public class LockDemo {
public void show(){
synchronized (this) {
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName()+"执行show方法 :"+i);
}
}
}
public void log(){
synchronized (new LockDemo()) {
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName()+"执行log方法 :"+i);
}
}
}
public static void main(String[] args) {
final LockDemo ld = new LockDemo();
new Thread(){
public void run(){
ld.show();
}
}.start();
new Thread(){
public void run(){
ld.log();
}
}.start();
}
}
结果:
Thread-0执行show方法 :0
Thread-1执行log方法 :0
Thread-0执行show方法 :1
Thread-1执行log方法 :1
可以看到交叉的情况,由于show()和log()两个方法中的同步代码块的锁不是同一个对象锁,所以多线程在分别执行两个方法时不具有排他性,即show()和log()是不同步的;
4同步静态方法:
synchronize关键字修饰静态方法,此时该方法对应的锁为类锁,该类的所有同步方法都具有同步性
看一个例子:
public class LockDemo {
public static synchronized void show(){
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName()+"执行show方法 :"+i);
}
}
public static synchronized void log(){
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName()+"执行log方法 :"+i);
}
}
public static void main(String[] args) {
new Thread(){
public void run(){
LockDemo.show();
}
}.start();
new Thread(){
public void run(){
LockDemo.log();
}
}.start();
}
}
运行的结果:
Thread-0执行show方法 :0
Thread-0执行show方法 :1
Thread-1执行log方法 :0
Thread-1执行log方法 :1
可以看到线程顺序执行,
其实类锁也是一种对象锁,其对应的对象时该类的 Class对象;再复杂的场景都可以带入上边的对象锁进行分析