需求:银行有一个金库,有两个储户分别存300元,存3次。
目的:该程序是否有安全问题,若有,如何解决?
public class Bank {
private int sum;
public void add(int n) {
sum=sum+n;
System.out.println("sum="+sum);
}
}
public class Cus implements Runnable {
private Bank b=new Bank();
public void run() {
for(int i=0;i<3;i++) {
b.add(100);
}
}
}
public class BankDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
Cus c=new Cus();
Thread t1=new Thread(c);
Thread t2=new Thread(c);
t1.start();
t2.start();
}
}
一、如何找到问题呢?
1.明确哪些代码是多线程运行代码
2明确共享数据
3明确多线程运行代码中哪些语句是操作共享数据
二、同步实现的两种方式
①同步代码块(run方法内)
②同步函数(run方法上)
如何找到问题举例:
有两个共享数据:Bank类中sum和Cus类中的Bank。
若把Cus中的Bank设为同步,则成了单线程。因为同步中有个for,此同步结束,相当于一个客户结束,即单线程。
可以让多个客户去存,即多线程。但是sum这个必须得单线程,这个共享区得同步,否则会有问题。
【这个问题举例:第一个用户函数调用到add,执行完sum=sum+n;后,sum=100,但没有打印,CPU夺走,第二个用户调用add,执行sum=sum+n;,sum=200,也没打印,CPU夺走,假如此时第一个用户得到cpu,则打印了200,第一个用户的第一笔钱结束调用,数据错误。第二个用户此时获得cpu,打印200。】
所以必须给sum同步。
同步之后,执行是什么样的呢?
第一个用户调用到了add,现在锁是开的“1”,执行sum=sum+n;睡觉,释放cpu.比如此时第二个用户调用了add,此时锁是关的,所以得等第一个用户睡醒输出sum后释放锁,就实现了同步,不会存储错误数据。
修改1:
public class Bank {
private int sum;
Object obj=new Object();
public void add(int n) {
synchronized(obj) {
sum=sum+n;
try {
Thread.sleep(10);
}
catch(Exception e) {}
System.out.println("sum="+sum);
}
}
}
也可以对其写成同步函数public synchronized void add(int n) {},就不用再定义对象了。
但是呢,并不是所有的都可以直接就写成同步函数。
这个能够写成同步函数的原因是:执行add立刻就判断锁了。
例如之前(46)中的卖票,就不能直接写成同步函数。
比如直接写成同步函数,对其分析错误:
public class Demo implements Runnable{
private int tick=10;
public synchronized void run() {
while(true) {
if(tick>0) {
try {
Thread.sleep(10);
} catch (Exception e) {}
System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
}
}
}
}
不能在此run方法上写同步函数,
原因是第一个线程将锁置为0,一直true,一直打印,无法出同步。
想写同步函数,则将同步代码块封装成一个函数,然后调用
public class Demo implements Runnable{
private int tick=10;
Object obj=new Object();
public void run() {
while(true) {
show();
}
}
public synchronized void show() //这样就不会发生只有一个线程执行,因为执行完这个函数,打印一个数,就释放锁,其他的线程可以打印
{
if(tick>0) {
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO Auto-generated catch block
}
System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
}
}
}