线程的同步
场景1:两个线程同时访问一个变量,一个线程自增,一个线程自减
public class thread11 {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new AddThread();
Thread thread2 = new DecThread();
// 启动线程
thread1.start();
thread2.start();
// 线程插队
thread1.join();
thread2.join();
System.out.println(Counter.count);
}
}
class Counter {
public static int count = 0;
}
class AddThread extends Thread {
public void run() {
for (int i = 0; i < 10000; i++) {
Counter.count = Counter.count + 1;
}
}
}
class DecThread extends Thread {
public void run() {
for (int i = 0; i < 10000; i++) {
Counter.count = Counter.count - 1;
}
}
}
自增线程和自减线程都执行了一万次,结果本应该为0(不变),但是因为线程不安全,结果并不是0;原因如下:
线程一和线程二并发执行,线程一执行一次数字本应变为101,此时线程二执行,但在线程一的ILOAD
和IADD
之间读取到变量值为100,造成了不同步的问题;保证此过程的原子性,即可解决该问题,如下:
每次线程执行之前加锁,线程执行之后释放锁;线程一拿到锁,线程二想要执行就必须要等线程一释放锁
public class thread11 {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new AddThread();
Thread thread2 = new DecThread();
// 启动线程
thread1.start();
thread2.start();
// 线程插队
thread1.join();
thread2.join();
System.out.println(Counter.count);
}
}
class Counter {
public static int count = 0;
// 创建锁
public final static Object LOCK = new Object();
}
class AddThread extends Thread {
public void run() {
for (int i = 0; i < 10000; i++) {
synchronized (Counter.LOCK) {
Counter.count = Counter.count + 1;
}
}
}
}
class DecThread extends Thread {
public void run() {
for (int i = 0; i < 10000; i++) {
synchronized (Counter.LOCK) {
Counter.count = Counter.count - 1;
}
}
}
}
使用synchronized关键字修饰代码块,使其线程安全
线程同步锁
-
synchronized用法
-
修饰代码块
synchronized
:修饰代码块,则其用的锁是某个指定的Java
对象public static void dosth1() { // 使用当前类Class对象,作为锁对象 synchronized (Example2.class) { System.out.println("xxx"); } }
-
修饰实例方法
synchronized
修饰实例方法,则其用的锁默认 为this
当前方法调用对象// 方法声明中直接使用synchronized关键字(作用等同于使用this作为锁) public synchronized void dosht2() { System.out.println("..."); }
-
修饰静态方法
synchronized
修饰实例方法,则其用的锁默认 为Class
对象// 静态方法:方法声明中直接使用synchronized关键字(作用等同于使用this作为锁) public synchronized static void dosth2() { System.out.println("xxx"); }
-
-
synchronized原理
-
通过monitorenter/monitorexit指令实现监视器机制。
-
监视器(monitor)获取步骤:
- 执行monitor指令,判断当前monitor监视器的线程进入数量:如果为0,则该线程直接进入监视器owner,代表该线程拥有监视器,同时进入数设置为1;
- 如果线程已经拥有该monitor监视器,重新进入,则进入数+1;如果其它线程尝试获取监视器,则进入阻塞区,线程进入阻塞状态,直到监视器的进入数为0;
- 执行monitorexit指令,进入数-1
-
-
锁升级(锁膨胀)
- 偏向(斜)锁:
- 只有一个线程执行的场景,使用偏向锁;
- 轻量级锁:
- 发现多个线程执行的场景,偏向锁升级为轻量级锁;
- 轻量级锁不能处理并发;
- 重量级锁:
- 发现多个线程并发执行的场景,轻量级锁升级为重量级锁;
- 重量级锁通过操作系统的“互斥锁”实现。互斥锁实现线程之间的切换,需要从“用户态”切换到“内核态”,付出高昂的代价,会导致性能下降。
- 偏向(斜)锁:
线程同步时的通信
- wait():必须通过notify() / notifyAll()唤醒线程
- **wait(等待时间):**达到计时时间后,自动唤醒