synchronized实现线程安全的原理分析

synchronized

例子:我们模拟两个线程取钱的操作,代码如下:

class Account {
	 String accountNo;//账户名
	 double balance;//账户余额
	public Account(String accountNo, double balance) {
	this.accountNo = accountNo;
		this.balance = balance;
	}

	public void draw(double drawAmount) {
	//如果账户余额>=所取得钱数则取钱成功,否则失败
		if (balance >= drawAmount) {
			balance -= drawAmount;
			System.out.println(Thread.currentThread().getName() + "取钱成功, 余额为:" + balance);
		} else {
			System.out.println(Thread.currentThread().getName() + "取钱失败,余额不足");
		}
	}
}

class DrawThread extends Thread{
	Account account;
	public DrawThread(Account account) {
		this.account=account;
	}
	@Override
	public void run() {
		while (account.balance>=100) {
			account.draw(100);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}
}
public class SynchronizeTest {
public static void main(String[] args) {
	Account account=new Account("dandan", 1000);
	DrawThread drawThread1= new DrawThread(account);
	DrawThread drawThread2= new DrawThread(account);
	drawThread1.start();
	drawThread2.start();
 }
}

运行结果:
在这里插入图片描述
然而运行结果,却出现两个800元,即就是线程安全问题,下面我们分析为什么会出现这种状况,可能是下面的这种情况(但是这种情况并不唯一,因为余额的变动并不是原子操作):
在这里插入图片描述
那么出现线程安全的原因是什么?

  1. 存在两个或者两个以上的线程对象,而且线程之间共享着一个资源。
  2. 有多个语句操作了共享资源。
    为了解决这个问题,java的多线程引入synchronized同步代码块和同步方法来解决这个问题,下面我们来看一下这两种方法:

synchronized有两种使用方式:
方式一:同步代码块
格式:
synchronized(锁对象){
需要被同步的代码…
}
注意:多线程操作的锁对象必须是唯一共享的。否则无效。也就是说锁对象是static的/常量,其实最简单的就是使用一个常量作为锁对象
这种方式比较简单,我们就不写代码的例子了。。。
方式二:同步函数
同步函数:同步函数就是使用synchronized修饰一个函数。
注意:

  1. 如果是一个非静态的同步函数的锁对象是this对象,如果是静态的同步函数的锁对象是当前函数所属的类的字节码文件(class对象)。因此,同步方法只能保证多个线程同时执行同一个对象的同步代码段
  2. 同步函数的锁对象是固定的,不能自己来指定的。
    下面我们用Synchronized修饰这个取钱的方法,代码如下:
class Account {
	 String accountNo;
	 double balance;

	public Account(String accountNo, double balance) {
		// TODO Auto-generated constructor stub
		this.accountNo = accountNo;
		this.balance = balance;
	}

	public synchronized void draw(double drawAmount) {
		if (balance >= drawAmount) {
			balance -= drawAmount;
			System.out.println(Thread.currentThread().getName() + "取钱成功, 余额为:" + balance);
		} else {
			System.out.println(Thread.currentThread().getName() + "取钱失败,余额不足");
		}
	}
}

class DrawThread extends Thread{
	Account account;
	public DrawThread(Account account) {
		// TODO Auto-generated constructor stub
		this.account=account;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while (account.balance>=100) {
			account.draw(100);
		}
		
	}
}
public class SynchronizeTest {
public static void main(String[] args) {
	Account account=new Account("dandan", 50000);
	DrawThread drawThread1= new DrawThread(account);
	DrawThread drawThread2= new DrawThread(account);
	drawThread1.start();
	drawThread2.start();
 }
}

执行结果:
在这里插入图片描述
那么synchronized的原理是什么?
原来synchronized关键字经过编译之后, 会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令,这两个字节码都需要一个reference类型的参数(即我们之前说的锁对象)来指明要锁定和解锁的对象。
在执行monitorenter指令时, 首先要尝试获取对象的锁。 如果这个对象没被锁定, 或者当前线程已经拥有了那个对象的锁, 把锁的计数器加1, 相应的, 在执行monitorexit指令时会将锁计数器减1,当计数器为0时, 锁就被释放。如果获取对象锁失败, 那当前线程就要阻塞等待, 直到对象锁被另外一个线程释放为止。
为什么要用计数器来实现锁对象?
其实为了保证不会出现自己把自己锁死的问题:
对于同步代码块来说,即在synchronized块中,再定义一个synchronized块
对于同步方法来说,为了防止自己再调用自己的时候(递归调用)时,自己把自己锁死

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
synchronized 是 Java 中用于实现线程同步的关键字。它主要用于控制对共享资源的访问,确保在同一时间只有一个线程可以执行被 synchronized 修饰的代码块或方法。 synchronized 采用的是互斥锁(也称为监视器锁)的机制来实现线程的同步。每个对象都有一个关联的互斥锁,当一个线程访问 synchronized 代码块或方法时,它会尝试获取对象的互斥锁。如果锁已经被其他线程获取,那么当前线程将被阻塞,直到获得锁为止。 具体来说,synchronized 可以分为两种使用方式:synchronized 代码块和 synchronized 方法。 1. synchronized 代码块:可以用来对指定的对象或类进行加锁。当一个线程进入 synchronized 代码块时,它会尝试获取指定对象的锁。如果获取成功,则执行代码块中的代码,执行完毕后释放锁。如果获取失败,则进入阻塞状态,等待锁释放。 2. synchronized 方法:可以用来对整个方法进行加锁。当一个线程调用 synchronized 方法时,它会尝试获取该方法所属对象的锁。如果获取成功,则执行方法体中的代码,执行完毕后释放锁。如果获取失败,则进入阻塞状态,等待锁释放。 需要注意的是,synchronized 锁的是对象而非代码,也就是说,如果多个线程访问的是同一个对象的 synchronized 代码块或方法,那么它们将会相互排斥,只能有一个线程执行。但如果是多个线程分别访问不同对象的 synchronized 代码块或方法,则它们之间不会相互影响。 总结来说,synchronized 通过互斥锁机制确保了多线程对共享资源的安全访问。它的使用可以有效避免竞态条件和数据不一致的问题。然而,需要注意合理使用 synchronized,避免过多的同步操作导致性能下降。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值