Java之线程同步、synchronized用法及原理

线程的同步

场景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,此时线程二执行,但在线程一的ILOADIADD之间读取到变量值为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)获取步骤

      1. 执行monitor指令,判断当前monitor监视器的线程进入数量:如果为0,则该线程直接进入监视器owner,代表该线程拥有监视器,同时进入数设置为1;
      2. 如果线程已经拥有该monitor监视器,重新进入,则进入数+1;如果其它线程尝试获取监视器,则进入阻塞区,线程进入阻塞状态,直到监视器的进入数为0;
      3. 执行monitorexit指令,进入数-1

    在这里插入图片描述

  • 锁升级(锁膨胀)

    • 偏向(斜)锁
      • 只有一个线程执行的场景,使用偏向锁;
    • 轻量级锁
      • 发现多个线程执行的场景,偏向锁升级为轻量级锁;
      • 轻量级锁不能处理并发;
    • 重量级锁
      • 发现多个线程并发执行的场景,轻量级锁升级为重量级锁;
      • 重量级锁通过操作系统的“互斥锁”实现。互斥锁实现线程之间的切换,需要从“用户态”切换到“内核态”,付出高昂的代价,会导致性能下降。

线程同步时的通信

  1. wait():必须通过notify() / notifyAll()唤醒线程
  2. **wait(等待时间):**达到计时时间后,自动唤醒
  • 21
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeMonkey-D

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值