JavaSE-多线程(2)- 锁
synchronized
举例说明:在 SynChronizedTest 类中有一个 count 变量,有两个线程同时对count进行 ++ 操作,希望结果应该为线程每访问一次count +1 ,最终count 值为 2
package com.hs.example.base.multithread.day01;
public class SynChronizedTest implements Runnable{
private int count = 0;
public /*synchronized*/ void countNum(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println(count);
}
@Override
public void run() {
countNum();
}
public static void main(String[] args) {
SynChronizedTest synChronizedTest = new SynChronizedTest();
new Thread(synChronizedTest).start();
new Thread(synChronizedTest).start();
}
}
但是却出现如下状况,原因在于,两个线程同时访问count,count初始值都为0,经过睡眠后,count同时加1,导致最后结果为1(测试结果不一定为1)
1
1
Process finished with exit code 0
如何避免以上状况出现,这时候就需要用到锁,如下:
public synchronized void countNum(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println(count);
}
以上 synchronized 锁定的是当前this对象(即synChronizedTest对象)代码添加 synchronized 后的意思是访问countNum方法需要获得synChronizedTest对象这把锁,以上例子如果改成下面这样,则synchronized锁定的是o这个对象
package com.hs.example.base.multithread.day01;
public class SynChronizedTest2 implements Runnable {
private int count = 0;
private final Object o = new Object();
public void countNum() {
synchronized (o) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println(count);
}
}
@Override
public void run() {
countNum();
}
public static void main(String[] args) {
SynChronizedTest2 synChronizedTest = new SynChronizedTest2();
new Thread(synChronizedTest).start();
new Thread(synChronizedTest).start();
}
}
synchronized 如果加在 static方法上则锁定的是 SynChronizedTest.class对象
public static synchronized void countNum() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println(count);
}
重入锁
简单理解为同一个线程可以对同一把锁进行重复加锁,例如:调用m1方法需要获得reentrantLock 锁对象,在m1方法内部调用的m2方法同样需要获得reentrantLock 锁对象,线程在执行时可以重复获得该锁,即重入
public class ReentrantLock implements Runnable{
public synchronized void m1(){
System.out.println("m1");
m2();
}
public synchronized void m2(){
System.out.println("m2");
}
@Override
public void run() {
System.out.println("ReentrantLock test");
m1();
}
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock();
new Thread(reentrantLock).start();
}
}
异常释放锁
以下程序中,有t1,t2两个线程,从执行结果可以看出,当某个线程执行后,另一个(或多个)线程会等待其执行结束后再执行
public class ExceptionLockTest implements Runnable {
public synchronized void m() {
int i = 0;
while (i < 10) {
System.out.println(Thread.currentThread().getName() + " i: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
}
}
@Override
public void run() {
m();
}
public static void main(String[] args) {
ExceptionLockTest exceptionLockTest = new ExceptionLockTest();
new Thread(exceptionLockTest, "t1").start();
new Thread(exceptionLockTest, "t2").start();
}
}
t2 i: 0
t2 i: 1
t2 i: 2
t2 i: 3
t2 i: 4
t2 i: 5
t2 i: 6
t2 i: 7
t2 i: 8
t2 i: 9
t1 i: 0
t1 i: 1
t1 i: 2
t1 i: 3
t1 i: 4
t1 i: 5
t1 i: 6
t1 i: 7
t1 i: 8
t1 i: 9
假如上述程序出现异常,那么刚开始执行的线程会释放锁,另外的线程就可以继续执行了,如下:
public class ExceptionLockTest implements Runnable {
public synchronized void m() {
int i = 0;
while (i < 10) {
System.out.println(Thread.currentThread().getName() + " i: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i==5){
int a = i/0;
}
i++;
}
}
@Override
public void run() {
m();
}
public static void main(String[] args) {
ExceptionLockTest exceptionLockTest = new ExceptionLockTest();
new Thread(exceptionLockTest, "t1").start();
new Thread(exceptionLockTest, "t2").start();
}
}
t2 i: 0
t2 i: 1
t2 i: 2
t2 i: 3
t2 i: 4
t2 i: 5
t1 i: 0
Exception in thread "t2" java.lang.ArithmeticException: / by zero
at com.hs.example.base.multithread.day01.ExceptionLockTest.m(ExceptionLockTest.java:15)
at com.hs.example.base.multithread.day01.ExceptionLockTest.run(ExceptionLockTest.java:23)
at java.lang.Thread.run(Thread.java:745)
t1 i: 1
t1 i: 2
t1 i: 3
t1 i: 4
t1 i: 5
Exception in thread "t1" java.lang.ArithmeticException: / by zero
at com.hs.example.base.multithread.day01.ExceptionLockTest.m(ExceptionLockTest.java:15)
at com.hs.example.base.multithread.day01.ExceptionLockTest.run(ExceptionLockTest.java:23)
at java.lang.Thread.run(Thread.java:745)
锁升级
偏向锁(在对象头MarkWord位置记录当前线程id) -> 自旋锁(如果有其他线程竞争则升级,默认自旋10次) -> 重量级锁(如果升级为自旋锁后线程还未获得锁或自旋时间过长则升级为重量级锁)
自旋锁与重量级锁的区别
- 自旋锁只需要访问用户态不需要访问内核态,但是会消耗CPU资源,如果需要获得锁才能执行的方法执行时间较短或者线程较少时适合采用自旋锁
- 重量级锁访问内核态,需要向操作系统申请锁资源,比较消耗时间,重量级锁的使用不消耗CPU资源,它会让没有获得锁的线程阻塞进入等待队列,所以对于线程多,方法耗时长的情况更适合使用重量级锁
内核态用户态是什么
操作系统对程序的执行权限进行分级,分别为用户态和内核态。用户态相比内核态有较低的执行权限,很多操作是不被操作系统允许的,简单来说就是用户态只能访问内存,防止程序错误影响到其他程序,而内核态则是可以操作系统的程序和普通用户程序
内核态: cpu可以访问计算机所有的软硬件资源
用户态: cpu权限受限,只能访问到自己内存中的数据,无法访问其他资源
参考:https://blog.csdn.net/weixin_43776652/article/details/116400370