Java线程 synchronized加锁(不能忽视的锁对象)

Java线程 synchronized加锁

锁的常见问题及原因分析

直接上代码例子:

package cn.HYQ.WSQ.XY;
/**
* 类说明:错误的加锁和原因分析
*/

public class HYQ{
	public static void main(String[] args) throws InterruptedException{
		Worker work = new Worker(1);	//new一个worker对象,并传入i的值
		//Thread.sleep(50);
		for(int i = 0; i < 5; i++){		//每循环new 一个新的线程,把Worker交给5个线程去执行
			Thread thread = new Thread(worker);		//创建线程
			thread.start();		//start线程,注意是start方法之后才算是线程的开始
		}
	}
}

//创建静态Worker类
private static class Worker implements Runnable{

	private Integer i;		//定义一个 i 成员变量
	publiv Worker(Integer i){
		this.i = i;
	}

	@Override
	public void run(){
		synchronized(i){
			Thread thread = Thread.currentThread();
			i++;
			System.out.println(thread.getName()+"-------"+ i+ "-@"+System.identityHashCode(i));
			//这里解释一下上面的identityHashCodeHashCode
			//identityHashCodeHashCode()方法是拿出这个类的哈希值,返回原生的HashCode,
            //出来的值是用每个对象在内存中的地址计算出来的
            //所以可以近视认为是内存地址,因为内存地址改变 identityHashCode的值也会改变

			try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
		}
	}
}

正常来说经过循环 i++ 完之后输出i的序列应该为代23456,但是直接跑起来的话结果如下:
在这里插入图片描述
在这里插入图片描述

会发现这样跑出来的i是不对的,33456没有2,34356没有2,意味着锁的内部代码块执行有问题

不妨在i++前后再输出一次 identityHashCode 值来看看 i 的变化,多试试总没错的:

        public void run() {
            synchronized (i) {
                Thread thread=Thread.currentThread();
                System.out.println(thread.getName()		//i++前加多一个哈希值
                        +"--@"
                        +System.identityHashCode(i));

                i++; 
                System.out.println(thread.getName()+"-------"
                					+ i
                					+ + "-@"+System.identityHashCode(i));

                try {Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();}
                
				//i++后面再加一个
                System.out.println(thread.getName()+"-------"
                					+ i
                					+ + "-@"+System.identityHashCode(i));
            }

再看看结果:
在这里插入图片描述

很明显的内存地址变化发生在了 i 身上!

比如第一个线程:i++ 前输出 identityHashCode 值为1368942806
i++ 过后的两次输出值都变成了1600097429,既然内存地址发生了变化,说明对象 i 变了

说明是 i++ 过程出现问题,要找到问题想到的比较好的办法就是查看反编译:
找到编译过后的 .class 文件直接反编译,结果如下:
在这里插入图片描述

i++ 的过程中出现了 Integer.valueOf() ! ! !
在这里插入图片描述
找到 Integer.valueOf()
在这里插入图片描述
可以看到调用 valueOf() 函数最终返回的是 new了一个新的 Integer(i),得证:对象 i 产生了变化

但是synchronized锁强调的便是锁的对象绝对不能变

而代码里的 i 包在了 synchronized代码块里面 ,在执行i++ 前就已经加了锁
尽管给i加了锁,但是i++ 执行后编译会执行return new Integer(i),也就是返回一个new integer(i),内存地址变了,所以System.identityHashCode(i) 才会变化
最终就是synchronized加锁对象 i 就变了,所以对这些线程实际加锁的是不同的对象,所以是并行了,不能保证线程的安全

总结:synchronized锁的对象不能变
解决办法:更换不会发生变化的对象:

public static calss Worker implements Runnable{
	private Integer i;
	
	//在类中new一个对象,这个对象不发生改变,synchronized锁这个不变的对象
	private Object o = new Object();
	...
	@Override
	public void run(){
		synchronized(o){	//重要的是这里的 o 对象
			Thread thread=Thread.currentThread();
			...
			...
			...
			System.out.println(thread.getName()+"-------"
                					+ i
                					+ + "-@"+System.identityHashCode(i));
		}
	}
	
}

总的来说,无论是类锁还是对象锁,都要记得保证锁的对象一定不能变,所以锁之前就要看好内部或者编译过程中对象是否会发生变化

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值