——- android培训、java培训、期待与您交流! ———-
我们知道,多线程带来了方便,但同时也出现数据安全问题。
看如下买票的例子:
class Test implements Runnable
{
private int ticket = 500;
public void run()
{
while(true)
{
if(ticket>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println("ticket......."+ticket--);
}
}
}
}
public class ThreadDemo
{
public static void main(String args[])
{
Test t = new Test();
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();
}
}
运行结果如下:
最后票数出现了0,甚至-1。当多线程操作同一共享资源时,由于线程执行延迟,可能会出现数据错乱问题。对于这种问题我们该如何解决?
线 程 同 步
线程同步的作用:解决多线程数据安全问题
将涉及到共享数据的多行代码封装到同步代码中,相当于对这部分代码上锁,在某一时刻只有一个线程对其进行操作,避免了数据错乱问题。
线程同步的方法两种
第一种:同步代码块
第二种:同步函数
(一)同步代码块
格式为: synchronized(对象){ 需要同步的代码 },
说明:对象,一般为Object obj = new Object();直接传入到synchronized(obj)
更改以上代码,使用同步代码块
class Test implements Runnable
{
private int ticket = 500;
Object obj = new Object();
public void run()
{
while(true)
{ synchronized(obj)
{
if(ticket>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println("ticket......."+ticket--);
}
}
}
}
}
public class ThreadDemo
{
public static void main(String args[])
{
Test t = new Test();
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();
}
}
程序运行结果如下:
此时没有出现数据错误,解决了上述问题。
synchronized(obj)参数可以是任意一个Object类或者他的子类。
注解:object相当于一个同步锁,进入之前需要判断,当一个线程在执行同步代码块时,同步锁是关闭的,即时其他线程获得了执行权也不能进入,而当一个线程执行完跳出同步代码块时,同步锁是开启的,其他获得执行权的线程才可以操作,故在同一时间点,只有一个线程在执行同步代码块的所有代码,保证了共享数据的安全性。
判断哪些代码块需要同步遵循以下三个步骤:
1)明确哪些代码是多线程运行代码;
2)明确共享数据;
3)明确多线程运行代码中哪些是操作共享数据的。
看一个例子: 银行一个金库,两个人一人存300元,每个人一次存100元,分3次存完。
class Bank implements Runnable
{
private int money = 0;
Object obj = new Object();
public void run()
{
synchronized(obj)
{ for(int i=0;i<3;i++)
{
money = money +100;
System.out.println("money......."+money);
}
}
}
}
public class ThreadDemo
{
public static void main(String args[])
{
Bank t = new Bank();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}
运行结果如下:
判断同步的两个关键点:
第一点:是否有两个及两个以上的线程;
第二点:这些线程是否使用同一个同步锁。
(二)使用同步函数
1)直接在函数上加入关键字synchronized,同步锁为this。
2)静态同步函数的同步锁为该方法所在类的字节码文件对象,即类名.class
举例说明同步函数的同步锁为this。
用两个线程来买票,一个处于同步代码块中,一个处于同步函数中,都在执行买票动作。
class Ticket implements Runnable
{
private int ticket = 100;
Object obj = new Object();
static boolean flag = true;
public void run()
{
if(flag)
{ while(true)
{
synchronized(obj)
{
if(ticket>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...code..."+ticket--);
}
}
}
}else
{
while(true)
{
show();
}
}
}
private synchronized void show(){
if(ticket>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...show..."+ticket--);
}
}
}
public class ThreadDemo
{
public static void main(String args[])
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{t1.sleep(10);}catch(Exception e){}
Ticket.flag = false;
t2.start();
}
}
此时运行结果如下:
出现了数据异常,说明没有同步,说明同步代码块和同步函数不是同一个锁,而此时同步代码块的锁为obj,如果把这个锁改为this,看下结果如何?
class Ticket implements Runnable
{
private int ticket = 100;
static boolean flag = true;
public void run()
{
if(flag)
{ while(true)
{
synchronized(this)
{
if(ticket>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...code..."+ticket--);
}
}
}
}else
{
while(true)
{
show();
}
}
}
结果如下:
此时无论运行多少次,结果都不会出现0,数据异常现象了,反面说明了同步函数的同步锁为this。
同样方法,可以验证静态同步函数的同步锁为类名.class。
到此为止,把多个线程操控同一共享资源的数据安全问题利用同步代码块或同步函数得到了解决,有什么问题和意见欢迎提出。
下集讲述多个线程对同一资源具有不同操作行为时需要解决的问题—-等待唤醒机制。