线程同步机制 ★★★★
线程同步机制★★★★
1、synchronized-线程同步
线程同步机制的语法是:
synchronized(){
// 线程同步代码块。
}
synchronized()小括号内容是至关重要的,它必须得是要同步的多个线程所共享的数据
举个例子:
假设目前程序内共有t1到t5五个线程,我只想让t1,t2,t3排队,其余线程不排队,要怎么办呢?
一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。
在java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记。100个对象,100把锁。1个对象1把锁。
2、eg.理解线程同步
/**
* 通过代码来理解线程同步synchronized
* 多线程共享同一银行账户,取钱
*/
public class Test {
public static void main(String[] args) {
// 创建账户对象(只创建1个)
Account act = new Account("act-001", 10000);
// 创建两个线程,共享同一个对象
Thread t1 = new Account.AccountThread(act);
Thread t2 = new Account.AccountThread(act);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
static class Account {
private String actno;//账户编号
private double balance;//账户余额
//对象
Object o = new Object(); // 实例变量。(Account对象是多线程共享的,Account对象中的实例变量obj也是共享的。)
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款方法
public void withdraw(double money) {
/**
* 以下可以共享,金额不会出错
* 以下这几行代码必须是线程排队的,不能并发。
* 一个线程把这里的代码全部执行结束之后,另一个线程才能进来。
*/
synchronized (this) {
//synchronized(actno) {
//synchronized(o) {
/**
* 以下不共享,金额会出错
*/
//Object obj2 = new Object();
//synchronized(obj2) { // 这样编写就不安全了。因为obj2不是共享对象。
//String s = null;
//synchronized(null) {//编译不通过
//synchronized(s) {//java.lang.NullPointerException
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
//}
}
}
static class AccountThread extends Thread {
// 两个线程必须共享同一个账户对象。
private Account act;
// 通过构造方法传递过来账户对象
public AccountThread(Account act) {
this.act = act;
}
public void run() {
double money = 5000;
act.withdraw(money);
System.out.println(Thread.currentThread().getName() + "对" + act.getActno() + "取款" + money + "成功,余额" + act.getBalance());
}
}
}
}
如何判断是否共享?
- 如果某个对象是多个线程所共享的,那么该对象中的实例变量自然也是这几个线程所共享的,如代码中的下图部分
/**
* 以下可以共享,金额不会出错
* 以下这几行代码必须是线程排队的,不能并发。
* 一个线程把这里的代码全部执行结束之后,另一个线程才能进来。
*/
synchronized (this)
synchronized(actno)
synchronized(o)
- 在新方法中创建的实例变量不属于在线程方法中定义的对象Account的实例变量,这些实例变量自然就不是多个线程所共享的
2、运行结果
观察以下几张图片的运行结果,证明对象或实例变量是否共享
以上代码锁this、实例变量actno、实例变量o都可以!因为这三个被多个线程共享
共享
查看结果可知,t1,t2两个线程排队对act-001账户对象进行取款操作,说明this为多线程共享
查看结果可知,t1,t2两个线程排队对act-001账户对象进行取款操作,说明实例变量actno为多线程共享
查看结果可知,t1,t2两个线程排队对act-001账户对象进行取款操作,说明实例变量o为多线程共享
不共享
用同样的方式测试后发现,不属于线程方法中定义的对象Account的实例变量都是不共享的
obj2,null,s都不共享
3、线程同步原理
-
假设t1和t2线程并发,开始执行代码的时候,肯定有一个先一个后。
-
假设t1先执行了,遇到了synchronized,这个时候自动找“共享对象”的对象锁,
找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是
占有这把锁的。直到同步代码块代码结束,这把锁才会释放。 -
假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有共享对象的这把锁
结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,
直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后
t2占有这把锁之后,进入同步代码块执行程序。这样就实现了线程排队执行。
4、在实例方法中可以使用synchronized(不推荐)
-
举个例子,在上边的代码中,synchronized是可以在实例方法中使用的,但是,此时的锁只能是this!没得挑,只能是this,不能是其他的对象。所以这种方式不灵活。
-
synchronized出现在实例方法上,表示整个方法体都需要同步,可能会无故扩大同步的范围,导致程序的执行效率降低。所以这种方式不常用。但这种方式可以精简代码。
-
如果共享的对象就是this,并且需要同步的代码块是整个方法体,建议使用这种方式。
1、eg.
在实例方法中使用synchronized
public synchronized void withdraw(double money){
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
在方法调用处使用synchronized
public void run(){
double money = 5000;
// 取款
// 多线程并发执行这个方法。
//synchronized (this) { //这里的this是AccountThread对象,这个对象不共享!
synchronized (act) { // 这种方式也可以,只不过扩大了同步的范围,效率更低了。
act.withdraw(money);
}
System.out.println(Thread.currentThread().getName() + "对"+act.getActno()+"取款"+money+"成功,余额" + act.getBalance());
}
2、疑问(待解决)
为什么this不共享?运行发现也没有问题啊?待解决
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律