Java学习笔记之
多线程(二)——同步函数
如何找多线程安全问题:
1.明确哪些代码是多线程运行代码
2.明确共现数据
3.明确多线程运行代码中哪些语句是操作共享数据的
下面是一个银行储钱程序:
/*
* 银行有一个金库
* 两个储户分别存300元,存3次
* 找安全问题,解决办法
*/
import java.lang.Thread;
class Bank
{
private int sum;
Object obj = new Object();
public void add(int n)
{
synchronized(obj)
{
try{sum = sum + n;}catch(Exception e){}
//这里有等待的话,sum就会输出错误
System.out.println("sum="+sum);
}
}
}
class Cus implements Runnable
{
private Bank b = new Bank();
public void run()
{
for(int x=0; x<3; x++)
{
b.add(100);//如果在这里同步,程序就会进入顺序执行,一个储户存完三次,下一个储户才可以存。
}
}
}
class BankDemo {
public static void main(String[] args)
{
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
其中Bank类中的add()方法(红色部分)才是多线程运行代码。
sum是共享数据。
add()方法中的“sum=sum+n; System.out.println("sum="+sum);”语句才是操作共享数据的,所以将其作为同步代码。
我们也可以用同步函数:
private int sum;
//Object obj = new Object();
public synchronized void add(int n)
{
//synchronized(obj)
//{
try{sum = sum + n;}catch(Exception e){}
//这里有等待的话,sum就会输出错误
System.out.println("sum="+sum);
//}
}
同步有两种表现形式:同步代码块, 同步函数
回到前面的卖票程序,改成同步函数形式:
如果这样写:
class Ticket implements Runnable
{
private int tick = 100;//总票数
Object obj = new Object();
public synchronized void run()
{
while(true)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+" sale:"+tick--);
}
}
}
}
则错误。因为这样的话,一个线程进去之后,会一直循环while执行,不出来。这说明同步代码快选择错误。
我们可以这样写:
import java.lang.Thread;
class Ticket implements Runnable
{
private int tick = 100;//总票数
public void run()
{
while(true)
{
show();
}
}
public synchronized void show()
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+" sale:"+tick--);
}
}
}
class ThisLockDemo {
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);//创建线程
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
同步函数使用的是哪个锁呢?
函数需要被对象调用。那么函数都有一个所属的对象引用。就是this。
所以同步函数使用的锁是this。
下面通过一个程序进行验证同步函数使用的锁是this。
使用两个线程来卖票,一个程序在同步代码中,一个线程在同步函数中,都在执行卖票动作。
import java.lang.Thread;
class Tickett implements Runnable
{
private int tick = 100;//总票数
Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(obj)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+" code:"+tick--);
}
}
}
}
else
while(true)
show();
}
public synchronized void show()
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+" show:"+tick--);
}
}
}
class ThisLockDemo
{
public static void main(String[] args)
{
Tickett t = new Tickett();
Thread t1 = new Thread(t);//创建线程
Thread t2 = new Thread(t);
t1.start();
t.flag = false;
t2.start();
}
}
输出结果:
发现只输出了“show”,没有输出“code”。说明只执行了同步函数没有执行同步代码块。
这是因为,作为主线程,主函数main已经执行完了
t1.start();
t.flag = false;
t2.start();
三段代码,t1,t2线程才开始执行,这时flag已经是false,所以只执行了show()函数。
接着我们加入一条语句:
t1.start();
try{Thread.sleep(10);}catch(Exception e){}
t.flag = false;
t2.start();
这时,t1就会去执行同步代码块,t2执行同步函数。交替执行。
结果如下:
发现最后出现了“0”,可见程序并不安全。明明加了同步,怎么就不安全了呢?
回头想想,同步的前提:
必须有两个或两个以上线程在运行,满足;
必须是多个线程使用同一个锁,不满足。
上述代码,线程0用的obj锁,线程1用的是this锁。
将上述代码中的obj锁换成this:
synchronized(this)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+" code:"+tick--);
}
}
程序顺利执行!!!
另外,如果同步函数被静态修饰后,使用的锁不在是this,因为静态方法中也不可以定义this。
静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。类名.class,该对象的类型是Class
class Ticket implements Runnable
{
private static int tick = 100;//总票数
//Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(Ticket.class)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+" code:"+tick--);
}
}
}
}
else
while(true)
show();
}
public static synchronized void show()
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+" show:"+tick--);
}
}
}
顺利执行!
静态的同步方法,使用的锁是该方法所在类的字节码文件对象,类名.class