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