Java中的锁
Thread API
Java不推荐直接停止一个线程,推荐让线程代码执行完以后优雅的停止,如果直接暴力停止一个线程,可能会导致一些资源无法被释放或一些标记状态无法
被改变/还原.而如果一定要让线程执行完,代码存在BUG或着运行异常时可能会导致两个问题的出现
1.线程阻塞 2.线程中有while()条件判断且一直为true
JVM推荐解决以上两个问题的思想是:
遇到阻塞就去解阻塞,解阻塞以后继续执行代码,最后停止线程。
如果遇到使用线程之外的Boolean类型标记变量做while()循环的判断条件,尝试将boolean值改为fasle解除死循环后继续执行代码。最后停止线程。
JVM如何实现以上的两个问题的解决思想?
参考Thread类中的API interrput(),isInterrputed();
如果线程阻塞了,调用interrput()方法,会抛出一个InterrputedException从而进入到catch代码块中,catch代码块中补充异常处理逻辑,
异常处理完后代码会继续向下执行,最后解除阻塞。
如果是遇到while()循环,判断一个线程之外的Boolean标记,JVM推荐使用thread.isInterrputed()方法代替Boolean标记去做判断,代码运行时
出现BUG导致死循环后,调用interrput()方法,这个方法会改变isInterrputed()的返回值,从而跳出死循环,代码继续向下执行,直到线程结束。
为什么要用isInterrputed()方法替代Boolean标记?
1.如果使用线程之外的Boolean标记变量会有不可见问题
2.JVM优化导致指令重排的问题
什么是指令重排,Java为什么要使用指令重排进行优化?
最终的代码会被翻译成为CPU指令,例如 int i= 1;int k = 0; i++,CPU寄存器中永远只有一个变量,如果不重排,先要在内存中找变量i地址,再找k,
然后再i++,这样麻烦且效率低,Java内存模型,成员变量在堆中,寻找堆中变量的地址是十分的麻烦的,直接在方法栈中定义临时变量,使用指令重排
的时候,会先执行int i = 1;然后执行i++,最后执行k = 0;这样可以提高效率,指令重排有很多中模式。
指令重排发生在编译阶段还是执行阶段?
都有
什么时候会发生指令重排?
例如当一个线程执行的时候,其中有一个while(flag)判断,同时flag为线程之外的变量。while(flag){}中没有代码(i++也可以),JVM会进行激进的
优化方式指令重排,会在线程中定义一个临时变量,将线程之外的Boolean flag赋值给临时变量。这时线程就不会停止,while(){}代码将一直执行,
如果while(){}方法体中有其他的方法调用,JVM会认为他可能存在方法溢出,就不会发生指令重排
如何理解java的interrupt()方法
此方法是让程序员去优雅的停止一个线程的函数,JVM不推荐开发人员直接停止一个线程, 直接停止一个线程会导致资源无法及时释放,
所以JVM推荐让线程执行完后优雅的停止,interrupt()方法可以解阻塞,让线程继续向下执行,或者可以让while(true)这种BUG代码条件
标识被改变,从而继续执行下去
synchronized关键字 //https://www.cnblogs.com/jyroy/p/11365935.html
synchronized是操作系统锁的原理,创建pthread_mutex_t mt 变量,可以将mt变量理解为一个整形,声明时为0,初始化调用pthread_mutex_init(mt)函数
传入mt变量,通过pthread_mutex_lock(mt)函数加锁,mt变量改为1,当代码块中的代码执行完毕后,通过pthread_mutex_unlock(mt)函数解锁,mt变为0
其他线程进来的时候,如果mt为0其他线程可以获得锁,如果为1则无法获取锁。
synchroized实现一把锁分三种
偏向锁 我们的方法一定要保证线程安全,但在实际情况下不一定有互斥(只有一个线程访问,那么该线程会自动获取锁,降低获取锁的代价),偏向锁是
在synchronized锁的对象没有资源竞争下存在的,偏向锁只会调用一次系统函数获取一次锁,之后将一直持有锁。只能有一个线程访问。
轻量级锁 当一个偏向锁被其他线程访问的时候,偏向锁升级为轻量级锁,其他线程尝试通过自旋的形式获取锁,不会阻塞,从而提升性能,
轻量级锁一般发生在多个线程交替执行,没有竞争的情况下。
重量级锁 通过操作系统函数实现的锁都是重量级锁,每次访问代码块的时候都会调用OS函数,JDK1.5之前synchroized实现的锁都是重量级锁。
1.6以后做了优化,偏向锁就是优化的一个结果。
为什么要优化synchronized关键字?
因为是重量级锁,重量级锁是存在在互斥场景当中的,当前方法只有一个单线程去访问,就不存在线程之间资源竞争(互斥)的问题。
这时重量级锁就显得很笨重,消耗资源,耗费时间,然后如果不存在互斥的场景为什么还要使用锁?原因是,当前情况可能是只有一个线程
去执行,但并不能确定以后也只会有一个线程去访问,可能会变为有多个线程去访问,这时候就会产生线程竞争的问题,所以优化是考虑了
线程安全的问题,防止改方法在以后的场景调用下出现并发问题,这样我们可以说这个方法是一个线程安全的方法。这被称为偏向锁
如何证明偏向锁存在?
修改Linux源码,Java代码调用操作系统加锁操作时,Linux控制台都会打印一遍线程ID,synchronized关键字修饰的方法,在执行时,会调用操作系统的
加锁函数,这时将会打印出线程ID。如果是偏向锁,只会调用一次系统函数加锁,控制台只会打印一次线程ID,但是JVM本身在运行时会很多的锁操作
如何区分打印出来的线程ID是我们启动的线程ID?我们可以在Java代码中调用一个本地方法,该方法返回本线程的ID,再输出,输出时可以加上一些
标识,比如使用"java thread print:" + thread id,Linux控制台上一定会打印synchronized代码块被执行的时候的thread id,还有Java代码调用本地
方法输出的线程"java thread print:" + thread id,如果是偏向锁,上述两条打印语句,第一句只会出现一次,第二句则会一直输出。重量级锁,会出现
上述两条语句在一起输出多次
-+