网站推广策略的控制和效果评价,济宁做网站的电话,那些网站可以做兼职,百度排行榜风云1、线程安全问题 多个线程#xff0c;同时操作同一个共享资源的时候#xff0c;可能会出现业务安全问题。
2、实例#xff1a;取钱的线程安全问题
2.1、场景 小明和小红是夫妻#xff0c;他们有个共同账户#xff0c;余额是十万元#xff0c;如果两人同时取钱并且各自取…1、线程安全问题 多个线程同时操作同一个共享资源的时候可能会出现业务安全问题。
2、实例取钱的线程安全问题
2.1、场景 小明和小红是夫妻他们有个共同账户余额是十万元如果两人同时取钱并且各自取十万元可能会出现什么问题 2.2、模拟取钱案例 分析 1、需要提供一个账户类接着创建一个账户对象代表2个人的共享账户。 2、需要定义一个线程类用于创建两个线程分别代表小明和小红。 3、创建2个线程传入同一个账户对象给2个线程处理。 4、启动两个线程同时去同一个账户对象中取钱10万元。
public class ThreadTest {public static void main(String[] args) {//1、创建一个账户对象代表两个人的共享账户Account acc new Account(ICBC-110,1000000);//2、创建两个线程分别代表小明、小红再去同一个账户下取出10万元。new DrawThread(acc,小明).start();//小明new DrawThread(acc,小红).start();//小红}
}public class Account {private String cargId;//卡号private double money;//余额public Account() {}public Account(String cargId, double money) {this.cargId cargId;this.money money;}public void drawMoney(double i){//判断谁来取钱String name Thread.currentThread().getName();//判断余额是否足够if (this.moneyi){System.out.println(name来取钱:i成功);this.money - i;System.out.println(name来取钱后余额剩余this.money);}else{System.out.println(name来取钱:余额不足);}}public String getCargId() {return cargId;}public void setCargId(String cargId) {this.cargId cargId;}public double getMoney() {return money;}public void setMoney(double money) {this.money money;}
}public class DrawThread extends Thread{private Account account;public DrawThread(Account a,String name) {super(name);this.account a;}Overridepublic void run() {//super.run();//取钱account.drawMoney(1000000);}
}//执行结果
小红来取钱:1000000.0成功
小明来取钱:1000000.0成功
小红来取钱后余额剩余0.0
小明来取钱后余额剩余-1000000.0
2.3、解决办法线程同步
2.3.1、线程同步的思想
小明和小红都使用了取钱线程两个线程同时执行了就出现了都取成功余额出现了负数。
2.3.2、线程同步的常见方案
加锁每次只允许一个线程加锁加锁后才能进入访问访问完毕后自动解锁然后其他线程才能再加锁进来。 加锁的三种方式1、同步代码块2、同步方法3、Lock锁 2.3.3、同步代码块
作用把访问共享资源的核心代码给上锁以此保证线程安全。 synchronized(同步锁){ 访问共享资源的核心代码 } 原理每次只允许一个线程加锁后进入执行完毕后自动解锁其他线程才可以进来执行执行过程还是加锁再解锁
ps:对于当前同时执行的线程来说同步锁必须是同一把同一个对象否则会出现bug。 //将主业务设置为同步代码块synchronized (同步锁) {if (this.moneyi){System.out.println(name来取钱:i成功);this.money - i;System.out.println(name来取钱后余额剩余this.money);}else{System.out.println(name来取钱:余额不足);}}
//执行结果
小明来取钱:1000000.0成功
小明来取钱后余额剩余0.0
小红来取钱:余额不足 如果有其他人来取钱
Account acc1 new Account(ICBC-112,1000000);
new DrawThread(acc1,小黑).start();//小黑
new DrawThread(acc1,小白).start();//小白
此时就会出现这情况
小白来取钱:1000000.0成功
小白来取钱后余额剩余0.0
小红来取钱:1000000.0成功
小红来取钱后余额剩余0.0
小黑来取钱:余额不足
小明来取钱:余额不足
解决办法就是将synchronized里面的参数改为this //this代表共享资源synchronized (this) {if (this.moneyi){System.out.println(name来取钱:i成功);this.money - i;System.out.println(name来取钱后余额剩余this.money);}else{System.out.println(name来取钱:余额不足);}}
执行结果
小黑来取钱:1000000.0成功
小黑来取钱后余额剩余0.0
小明来取钱:1000000.0成功
小白来取钱:余额不足
小明来取钱后余额剩余0.0
小红来取钱:余额不足
扩展静态方法
当静态方法使用同步锁的时候可以直接使用类名来作为参数
public static void test(){synchronized (Account.class){}}
PS使用规范
建议使用共享资源做为锁对象对于实力方法建议使用this作为锁对象。对于静态方法建议使用字节码类名.class对象作为锁对象。 2.3.4、同步方法 作用把访问共享资源的核心方法给上锁以此来保证线程安全 修饰符 synchronized 返回值类型 方法名称形参列表 操作共享资源的代码 原理每次只能一个线程进入执行完毕以后自动解锁其他线程才可以进来执行。 //同步方法内部包含thispublic synchronized void drawMoney(double i){//判断谁来取钱String name Thread.currentThread().getName();//判断余额是否足够if (this.moneyi){System.out.println(name来取钱:i成功);this.money - i;System.out.println(name来取钱后余额剩余this.money);}else{System.out.println(name来取钱:余额不足);}}
//执行结果
小红来取钱:1000000.0成功
小红来取钱后余额剩余0.0
小明来取钱:余额不足
同步方法底层原理
同步方法其实底层也是有隐士锁对象的只是锁的范围是整个方法代码。如果方法是实例方法同步方法默认用this作为的锁对象。如果方法是静态方法同步方法默认用类名.class作为的锁对象。 2.3.5、同步代码块与同步方法比较
范围上同步代码块锁的范围更小同步方法锁的范围更大锁的范围越小性能越好。可读性同步方法更好 2.3.6、Lock锁
是JDK5开始提供的一个新的锁定操作通过它可以创建出锁对象进行加锁和解锁更灵活、更方便、更强大。是接口不能直接实例化可以采用他的实现类ReentrantLock来构建Lock锁对象。 //创建一个锁对象
private Lock lock new ReentrantLock();//加锁lock.lock();//判断余额是否足够if (this.moneyi){System.out.println(name来取钱:i成功);this.money - i;System.out.println(name来取钱后余额剩余this.money);}else{System.out.println(name来取钱:余额不足);}//解锁lock.unlock();
//执行结果
小明来取钱:1000000.0成功
小明来取钱后余额剩余0.0
小红来取钱:余额不足
ps:注意
创建的时候增加final,保证不可修改private final Lock lock new ReentrantLock();解锁最好放在finally中 //加锁lock.lock();//判断余额是否足够try {if (this.moneyi){System.out.println(name来取钱:i成功);this.money - i;System.out.println(name来取钱后余额剩余this.money);}else{System.out.println(name来取钱:余额不足);}} catch (Exception e) {throw new RuntimeException(e);} finally {//解锁lock.unlock();} 这样的好处就是如果出现错误还能继续执行
构造器说明public ReentrantLock()获得Lock锁的实现类对象常用方法方法名称说明void lock()获得锁void unlocak()释放锁
2.4、线程通信 当多个线程共同操作共享资源时线程间通过某种方式互相告知自己的状态以相互协调并避免无效的资源争夺。
2.4.1、常见模型
生产者负责生产数据。消费者线程负责消费生产者生产的数据。注意生产者生产完数据应该等待自己通知消费者消费消费者消费完数据也应该等待自己通知生产者生产。
2.4.2、 案例 Object类的等待和唤醒方法
方法名称说明void wait()让当前线程等待并释放所占锁直到令一个线程调用notify()方法或notifyAll()方法void notify()唤醒正在等待的单个线程void notifyAll()唤醒正在等待的所有线程
注意上述方法应该使用当前同步锁对象进行调用
public class ThreadTest {public static void main(String[] args) {Desk desk new Desk();//创建三个生产者线程new Thread(() -{desk.put();},生产者1).start();new Thread(() -{desk.put();},生产者2).start();new Thread(() -{desk.put();},生产者3).start();//创建两个消费者线程new Thread(() -{while (true) {desk.get();}},消费者1).start();new Thread(() -{while (true) {desk.get();}},消费者2).start();}
}public class Desk {private ListString list new ArrayList();//放1个包子的方法//三个生产者public synchronized void put() {String name Thread.currentThread().getName();//判断是否有包子try {if (list.size() 0){list.add(name 做的肉包子);System.out.println(name 做了一个肉包子);Thread.sleep(2000);//唤醒别人等待自己//先唤醒再等待this.notifyAll();this.wait();}else{//其他生产者进来发现有包子不做了//唤醒别人等待自己//先唤醒再等待this.notifyAll();this.wait();}} catch (Exception e) {e.printStackTrace();}}//取一个包子的方法//两个消费者public synchronized void get() {String name Thread.currentThread().getName();try {if (list.size() 1){//有包子吃掉System.out.println(name 吃了:list.get(0));//清空listlist.clear();Thread.sleep(1000);//唤醒别人等待自己//先唤醒再等待this.notifyAll();this.wait();}else{//没有包子//唤醒别人等待自己//先唤醒再等待this.notifyAll();this.wait();}} catch (Exception e) {e.printStackTrace();}}
}
执行结果
生产者1做了一个肉包子
消费者2吃了:生产者1做的肉包子
生产者3做了一个肉包子
消费者1吃了:生产者3做的肉包子