多线程(三) synchronized

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011377803/article/details/73477338

说到synchronized可以说是java 多线程第一节课了。每java程序员都知道这个关键字。这里就这个关键字的原理和使用在介绍一次,一方面巩固理解,一方面和大家讨论一下。


这个关键词使用起来很简单,就是将代码段,或者方法,加锁实现串行执行,可是锁是什么?这个就是一个初学者常见的坑。

锁有3种:1.成员属性锁。2,对象锁(也就是this关键字;或者方法加上synchronized,静态方法除外)3.类锁(Test.class;静态方法加上synchronize也是)。

什么时候用什么锁,只要保持一个原则,可见原则,解释一下,就是你同步的代码中是否看到的都是一个锁。

比如说:静态方法,就要用类锁。因为静态方法所属于类。对象还不存在,不能用对象锁。

普通方法,可以用对象锁。如果是成员属性锁,那么这个必须在方法调用前初始化,同时不能改变。

这是时候就有一个疑问,我都用类锁,所有人都可以看见都是一个,不就行了吗。理论上使可行的,但实际行不通。因为锁的方法,串行的,其他人都要等待,这会让你的程序只有一个用户可以访问,其他人等着。(在实际中通常行不通,除非你就是想玩用户)

有了多用户的需求,我们就尽量减少同步时间,就产生了以下原则:

1.尽量使用低阶别的锁,成员属性锁<对象锁<类锁。

2.尽量使用同步块代替同步方法,减少同步代码。

3 耗时的代码进行不要在同步块中。(这里强调一下,不要把sql放到同步块中,你可能想这样就不会发生数据不一致问题,但是如果是集群呢,数据库还要靠事务保证,不是程序同步块


下面用代码说一下使用:

package com.ldh.syn;

public class SynTest1 {
	private int i = 0;
	public synchronized void run(){
		System.out.println(++i);
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		final SynTest1 t = new SynTest1();
		for(int i=0;i<20;i++){
			new Thread(()->{
				t.run();
			}).start();;
		}
	}
}

这段代码实现一次计数,计数后休息3秒,模仿慢代码执行。20次计数就要花费60s时间。所以尽量减少同步块大小。


package com.ldh.syn;

import java.util.Date;

public class SynTest extends Thread {
	private static int i = 0;
	
	private Date startDate ;
	
	public SynTest(Date startDate) {
		this.startDate = startDate;
	}
	@Override
	public synchronized void run() {
		System.out.println(++i);
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		Date date = new Date();
		System.out.println(date.getTime()-startDate.getTime());
	}
	public static void main(String[] args) {
		final Date startDate = new Date();
		for(int i=0;i<20;i++){
			new SynTest(startDate).start();
		}
		
	}
}
这段代码看上去没什么问题,其实是错的并不能实现同步功能,你运行的时候可会出现打印1-20 。那是因为start(),需要时间,先启动的一定先执行。

错误的原因是,每次都new 一个  synTest的对象。普通同步方法是对象锁,观察的不是一个对象(锁),所以不能实现同步效果。


简单说一下底层实现:

synchronize 同步后,会发生阻塞,阻塞的线程进入阻塞池(相当于一个队列),线程执行完成后,队列中的线程进行抢夺,抢到时间片的进行执行代码。

jdk1.8中,jvm开发人员发现其实大部分时候不会发生阻塞强占,都是一个线程在自己抢夺自己(你可能问自己强自己为什么,因为分时操作系统,一个时间片,你不一定能执行完同步块中的代码。)这时候设计了3中所结构: 偏向锁,轻量级锁,重量级锁。

偏向锁,具有自己的引用,如果多个线程冲突,那么就自动让自己通过。

轻量级锁,如果多个线程同时进行强占,那么将偏向锁升级为轻量级锁。这时候会有自旋,就是不断的抢夺。

重量级锁,如果有更多线程同时进行强占,将轻量级锁升级为重量级锁。大家进行等待,可以减少自旋。


总结一下,以上3种锁为了更快的让一个线程执行代码,减少阻塞所带来的等待时间。如果是单线程无冲突的情况下,加锁比不加锁效率已经差距不大了。



参考文章: http://ifeve.com/java-synchronized/ 





猜你喜欢

转载自blog.csdn.net/u011377803/article/details/73477338