voliate Synchronized Lock

参考文章:https://blog.csdn.net/huyiju/article/details/97126274
一、voliate相关

1:java内存模型

1.1:计算机的内存模型
在计算机的内存模型中cpu和内存之间的速度存在数量级所以引入了高速缓存,告诉缓存会导致到底以哪个处理器的缓存为主,同步到主内存,这个时候有有了缓存一致性协议,来保证缓存一致性。

在这里插入图片描述
指令重排:例如一下五行代码,前四行的在计算机cpu的执行顺序不一定是12345,也可以是13245或者34125,但是第五步的顺序不会变,这种指令重排不会影响最后的计算结果。

          int a=1;

          a++;

          int b=5;

          b++;

         int c=a+b;

1.2:java内存模型

java的内存模型屏蔽掉了计算机硬件和操作系统的差异,但是没有限制处理器使用缓存和主内存进行交互,也没有限制编译器在执行过程中的指令重排这类优化措施。
在这里插入图片描述

2:Voliate关键字
voliate关键字有两个作用(可见性和禁止指令重排)

1:可见性:保证在多个线程的情况下,线程一把int a的值修改为5的时候,其他线程也能立即知道int a=5,实现的方法是线程1把int a=5,会把a=5的是立马通过缓存同步到主内存,然后其他线程使用a之前会从主内存刷新一次,得到被线程1修改的值为5.

2:有序性:有序性的意思的在本线程内观察,所有操作都是有序的(线程内是串行的),但是在另一个线程观察本线程,所有操作都是无序的(主要是指指令重排现象和工作内存与主内存同步)。voliate和synchronize两个关键字来保证线程操作之间的有序性。

2.1:voliate不能保证线程安全(可见性分析)
预期结果:10个线程同时执行,每一个此线程都把i累加到1000.最后的结果应该是10000.

实际结果:多次运行,大部分结果都不足10000

结论分析:i++,不是原子性操作,线程1把i的值赋值的10,这个时候后线程2根据可见性也得到了10,但是线程1接着执行累加把i的值赋值到250,这个时候线程2依然拿到的值是10是过期数据,入栈++后的值为11,把11同步到了主内存,触发了线程1从主内存同步的得到值为11,这个时候会导致线程一的结果错误。

public class VoliteTest {
	//voliate关键字保证了此变量在各个线程中是一致的
	public static volatile int id=1;
 
	public static void main(String[] args) throws InterruptedException {
		// TODO Auto-generated method stub
		
		Thread[] ths=new Thread[10];
		for(int i=0;i<10;i++) {
			//10个线程同时执行
			ths[i]=new Thread(new Runnable() {
				@Override
				public void run() {
					// TODO Auto-generated method stub
					add();
				}
			});
			ths[i].start();
			
		}
		
		//累加线程执行完毕
		while (Thread.activeCount()>1) {
			Thread.yield();
		}
		System.out.println("========================");
		System.out.println("ID的值是:"+id);
	}
	
	//该方法可以通过加锁实现线程安全
	public  static void add() {
		for(int i = 0;i<1000;i++) {
			//id++不是原子性的
			//第一步:假如线程1同步id=5,这个时候要++,其他线程切换了进来
			//第二部:其他比如线程2同步id=5,执行了很多操作,id=100了
			//第三部:线程1执行++,得到了id=6,这个时候的id为5已经是过期数据了,
			//第四部:线程1将结果6同步到内存共享,其他的线程得到id=6.会导致最后的值不等于10001
		    id++;
		}
	}
 
}

2.2:voliate禁止指令重排
voliate保证cpu能够保证在这个关键字之前和之后的代码不会指令重排,保证按照书写顺序执行代码。

之所以能实现指令重排是因为voliate的可见性决定的,这个关键字保证了从缓存到主内存之间的同步,就像内存之间的一道屏障,保证了之前和之后的代码无法越过这个 内存栅栏。

单例代码案例:保证线程安全和只有一个实例

public class Danli {
	
	private volatile static Danli danli;
	
	//私有构造,防止通过外部new创建对象
	private Danli() {
		
	}
	
	//懒汉模式  需要的时候get
	//(方法加锁导致多线程效率低)
	public static synchronized Danli getDanli() {
		if(danli==null) {
			//在此处判断加锁,防止线程1判断为空后,线程二创建了实例
			//在加锁方法之内再次判断一次
			synchronized (Danli.class) {
				if(danli==null) {//再次判断
					danli=new Danli();//内存屏障,保证写完之后其他线程读取
					//1:在工作内存创建,(store存储)到主内存
					//在此之间可能有其他线程插入
					//2:主内存的danli(write写入)
                           //这是一个内存栅栏
				}
			}
		}
		return danli;
	}
 
}

二、synchronize关键字

2.1:说一下对synchronize关键字的理解
synchronize关键字解决了多线程的资源同步性,该关键字保证了在多线程的条件下,同时是有一个线程能够获取到资源。

在jdk的早起版本中,这个关键字是重量级锁,这个关键字在编译之后,在同步块的前后会形成monitorenter和monitorexit(监视器进入和退出,通过获取对象锁计数器加一减一来实现锁机制)两个节码指令,其他线程在竞争的时候会挂起,效率低下,但是随着jdk的发展,通过对synchronize底层的发展,不必每次线程都挂起。来大大提升了效率

比如采用偏向锁、轻量级锁、锁粗化、锁自旋、锁自旋、锁消除等手段来提升效率

2.2:synchronize在用项目中用到了吗?
2.2.1:synchronize用法
1:用来修饰静态方法(给类加了锁,无论new多少个对象,都会产生竞争,线程1new的对象获取普通加锁方法和线程2获取类的静态加锁方法不会产生竞争)

2:用来修饰静态代码块(也是给类加了锁)

3:用来修饰普通方法(锁给了对象,只用new的同一对象才有竞争)

2.2.2:项目中的使用(懒汉单例模式)

package com.thit.connpool;
 
public class LazySimple {
	//voliate修饰,禁止指令重排
	private static volatile LazySimple lazySimple;
	//私有构造防止new创建对象
	private LazySimple() {
		
	}
	//静态方法外部得到实例
	public static LazySimple getDanli() {
		//首先判断是否为空
		if (lazySimple==null) {
			synchronized (LazySimple.class) {
				//再次判断是否为空
				if(lazySimple==null) {
					//new 对象非原子性操作
					//1:分配内存空间
					//2:初始化值(构造器)
					//3:将对象指向内存地址
					lazySimple=new LazySimple();
				}
			}
		}
		return lazySimple;
	}
}

代码分析:

1:其中 volatile修饰lazySimple;是为了防止指令重排,其中lazySimple=new LazySimple();这段代码不是原子性操作,实际分为3步

                //1:分配内存空间
                //2:初始化值
                //3:将引用指向内存地址
                lazySimple=new LazySimple();

由于jvm有指令重排的优化,所以代码执行顺序可能是1>3>2,当线程1执行1>3的时候没有执行到2初始化赋值的时候,线程2进入执行,这个时候判断到lazySimplenull,会在创造一个对象,就不是单例了。加了voliate关键字修饰,会因为可见性而防止指令重排,因为其他的线程想要判断lazySimplenull的时候,会在1>2>3执行完成,在线程1修改lazySimple的时候,没写入之前内存像是有屏障一般。这边形成了指令重排无法越过的内存屏障。

三、说说synchronize关键字和voliate关键字
1:voliate修饰变量多线程可见性但是没有原子性,synchronize修饰方法能保证可见性和原子性。

2:voliate的效率更高一点,线程不会阻塞,但是synchronize线程会阻塞

3:voliate用来解决变量的多线程可见性,但是synchronize用来解决多线程资源访问问题

四、lock接口
4.1:为什么需要Lock接口?

我们知道锁是来控制多线程并发访问资源的竞争,在Lock接口出现之前我们使用synchronize关键字来实现锁功能,但是synchronize获取锁是隐式的,我们无法控制锁的获取,释放中断的可操作性性,由于synchronize获取锁固话,所以创建了Lock接口来实现更领过的锁。

Lock和synchronize都是重入锁

1:尝试非中断的获取锁,

2:可以实现中断,与synchronize关键字不同,当获取锁的线程能够响应中断,中断的时候,异常内抛出,释放锁

3:能指定时间获取锁,在指定时间内无法获取锁的时候返回

4.2:代码实现

Lock lock=new ReentrantLock();
		lock.lock();//获取锁
		try {
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();//finally修饰,异常处理,一定要释放锁
		}

原文链接:https://blog.csdn.net/huyiju/article/details/97646152

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Java中,编写比较方法时需要遵守“通用契约”,这是为了保证排序算法的正确性。然而,有时候会出现“java时间排序comparison method violate lts general contract”的错误。这是因为在比较方法中违反了通用契约的规定。 通用契约中指定了三个规则: 1. 反对称性(Antisymmetry):如果两个对象进行比较,其中一个大于另一个,那么另一个一定小于前者。如果两个对象相等,它们之间的比较结果应为0。 2. 传递性(Transitivity):如果对象A大于对象B,B大于对象C,那么A一定大于C。同样地,如果A等于B,B等于C,那么A一定等于C。 3. 一致性(Consistency):在比较过程中,如果两个对象发生变化,它们之间的比较结果应该随之改变。 当我们在比较Java中的时间时,经常使用的是Date类。要满足通用契约,我们需要按照时间顺序进行比较。然而,有时候我们在编写比较方法时可能没有正确地实现这些规则,导致了“java时间排序comparison method violate lts general contract”错误。 要解决这个问题,我们需要回顾一下比较方法的实现,并确保满足通用契约的规定。首先,我们应该确保在比较中使用的属性是可比较的,并且没有为null的情况。然后,我们应该检查比较方法是否正确地比较了两个对象的时间顺序,并根据比较结果返回正确的值。 此外,还有一种可能导致此错误的情况是,排序算法中使用了不支持通用契约的比较方法。在这种情况下,我们应该更改排序算法或者使用其他可靠的比较方法。 总之,解决“java时间排序comparison method violate lts general contract”错误,首先我们需要检查并修复比较方法中的错误。确保按照通用契约的规定进行比较,并且没有使用不支持通用契约的比较方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值