序
学完线程之后,我们可以知道在java中我们是可以通过线程来同时去操作运行的,其应用范围非常广,但是由此引发了一个问题:假如有两个人同时去银行取钱,余额有一万元,两人通过不同平台同时取一万元。试想一下,此时程序判定取钱余额都是满足取钱金额的,因此返回ture,两个人同时取出钱,这不就出现bug了吗?这样我们岂不是可以同时取好多的钱出来?银行不就倒闭了?因此在这基础上有了锁的概念,这里顺便练习一下锁的知识还有复习线程的知识
线程同步问题分析
首先我们需要创建一个账户对象,用来保存账户的余额,卡号信息,再定义一个线程类,创建两个线程,分别表示两个人同时取钱,再启动线程
Account类:
public class Account {
private String cardId;//卡号
private double money;//余额
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public Account() {
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
接着我们创建进程类来创建进程,这里涉及到一个问题,我们要让两个人同时取一个账户的钱,那么需要传入这个账户的参数进去,因此我们在进程类里创建一个有参构造器,用来传输账户类,到时候好表示同一个账户
package com.ljx.thread_safe;
public class ThreadTest {
public static void main(String[] args) {
//创建账户对象,代表两个人共享账户
Account acc = new Account("ICBC-110",10000);
//创建两个线程,分别代表两个人,再同时去取钱
//创建有参构造器,用来表示这个账户对象
new DrawThread(acc,"no1").start();//no1人
new DrawThread(acc,"no2").start();//no2人
}
}
public class DrawThread extends Thread{
//由于用有参构造器创建对象时,我们接收到账户对象需要去调用run函数,因此要创建一个成员变量接收
private Account acc;
public DrawThread(Account acc,String name){
super(name);//Thread提供有参构造器,将name传入进程中,可以得到名称,方便我们判断是谁取得钱
this.acc = acc;
}
@Override
public void run() {
acc.drawMoney(10000.0);
}
}
这里我们创建了drawthread方法,重新在账户类里创建:
public void drawMoney(double i) {
//判定谁先取钱----》进程对象中传入name参数,用no代表编号----》行程类中多传入一个参数,并用父类的构造器传入参数,通过threadcurrent获取当前线程的名称
String name = Thread.currentThread().getName();
//判断余额是否足够
if(money>=i){
System.out.println((name + "来取钱" + i + "成功"));
//计算取完钱的余额
money=money-i;
System.out.println(("余额还有:" + money));
}
else{
System.out.println((name + "来取钱,余额不足"));
}
}
最后结果出现冲突,两个人同时取钱,引发了线程同步问题

线程同步问题解决方法
从上面实现案例可以发现,两个人同时取钱,会引发线程同步问题,那有没有什么办法可以避免呢?当然有,我们要创建一个锁,只能允许一个人访问,当有一个人访问的时候,我们将进程锁住,不让别的人进来,因此规避了两个人同时访问的问题。这在操作系统中有提及——互斥锁
通俗的讲就是将异步转换为同步
同步代码块
同步代码块就是其中一个加锁的方法,其作用是把共享资源核心代码上锁,以此保证线程安全
synchronized(同步锁){
访问共享资源核心代码
}
注意:对于当前执行的线程来说,同步锁必须是同一个对象
public void drawMoney(double i) {
//判定谁先取钱----》进程对象中传入name参数,用no代表编号----》行程类中多传入一个参数,并用父类的构造器传入参数,通过threadcurrent获取当前线程的名称
String name = Thread.currentThread().getName();
//判断余额是否足够
synchronized ("suo") {
if (money >= i) {
System.out.println((name + "来取钱" + i + "成功"));
//计算取完钱的余额
money = money - i;
System.out.println(("余额还有:" + money));
} else {
System.out.println((name + "来取钱,余额不足"));
}
}
}
更改如上;对于两个人对象来说。suo是一个常量对象,当成锁对象。

最后结果发现两个进程不会冲突了
但但但是!!!我们发现,这个锁对象,不单单可以锁住这两个人,假如我们再创建几个对象,从不同的账户中取钱,就也会被锁住!!这是因为我们没有区分其他账户对象。试想一下,如果这样的话你在你的账户里取钱,别人去自己的账户取钱还要等你取完才能取,是不是很不符合逻辑?
因此我们设立锁对象还要让两个人共享资源作为锁
java中有推荐的方案:
public void drawMoney(double i) {
//判定谁先取钱----》进程对象中传入name参数,用no代表编号----》行程类中多传入一个参数,并用父类的构造器传入参数,通过threadcurrent获取当前线程的名称
String name = Thread.currentThread().getName();
//判断余额是否足够
synchronized (this) {
if (money >= i) {
System.out.println((name + "来取钱" + i + "成功"));
//计算取完钱的余额
money = money - i;
System.out.println(("余额还有:" + money));
} else {
System.out.println((name + "来取钱,余额不足"));
}
}
}
应将锁对象改为this,我们再创建多个对象去调用别的账户

发现已经成功了
扩充:如果是静态方法的话,同步锁改为类名.class,静态方法是通过类名调用,所以用类名.class锁住所有线程
public static void test(){
synchronized (Account.class){
}
}
同步方法
除了同步代码块之外,我们可以设立同步方法,在方法的修饰词里加上synchronized
public synchronized void drawMoney(double i) {
//判定谁先取钱----》进程对象中传入name参数,用no代表编号----》行程类中多传入一个参数,并用父类的构造器传入参数,通过threadcurrent获取当前线程的名称
String name = Thread.currentThread().getName();
//判断余额是否足够
if (money >= i) {
System.out.println((name + "来取钱" + i + "成功"));
//计算取完钱的余额
money = money - i;
System.out.println(("余额还有:" + money));
} else {
System.out.println((name + "来取钱,余额不足"));
}
}
总结:同步代码块和同步方法其实类似,但是(省略)代码块范围小,功能相对来说强一点,方法范围大,功能相对弱
Lock锁
创建一个Lock对象进行加锁和解锁,更灵活,更强大
//定义一个锁对象,reentrantlock为实现类,用final修饰,确保一个账户类只有一把锁
private final Lock lk = new ReentrantLock();
public void drawMoney(double i) {
//判定谁先取钱----》进程对象中传入name参数,用no代表编号----》行程类中多传入一个参数,并用父类的构造器传入参数,通过threadcurrent获取当前线程的名称
String name = Thread.currentThread().getName();
try {//如果中间出错,程序可能执行不到unlock,导致后续线程无法启动
//用try catch可以确保出错后任然解锁
lk.lock();//加锁
//判断余额是否足够
if (money >= i) {
System.out.println((name + "来取钱" + i + "成功"));
//计算取完钱的余额
money = money - i;
System.out.println(("余额还有:" + money));
} else {
System.out.println((name + "来取钱,余额不足"));
}
}catch(Exception e){
e.printStackTrace();
}finally {
lk.unlock();//解锁
}
}






