并发编程之同步锁

每个对象或者类都对应一个同步锁,在进入使用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对象;再复杂的场景都可以带入上边的对象锁进行分析






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值