导致线程安全问题的原因:
1、多个线程访问出现延迟
2、线程随机性
注:线程的安全问题在理想状态下,不容易出现,但是一旦出现了对应用系统的影响是非常大的。
二、下面我们可以通过Thread.sleep(long time)方法来简单的模拟一下延迟导致的线程安全的问题
如下是一个卖票的多线程代码:
package com.cn.xianChen;
public class SellTicketDemo implements Runnable{
private int num=50; //定义票的数量
@Override
public void run() {
for(int i=0;i<100;i++){
if(num>0){
System.out.println(Thread.currentThread().getName()+"卖出第"+num--+"张票了");
}
}
}
}
测试:
package com.cn.xianChen;
public class TestSellTicket {
public static void main(String[] args) {
SellTicketDemo st=new SellTicketDemo();
new Thread(st,"A").start();
new Thread(st,"B").start();
new Thread(st,"C").start();
}
}
结果:
此时一切都是正常的售票。如果因为网络等其它原因导致延迟,下面看看会出现什么问题?
在售票前加入Thread.sleep(1000)每个线程延时1秒,修改后代码如下:
package com.cn.xianChen;
public class SellTicketDemo implements Runnable{
private int num=25; //定义票的数量
@Override
public void run() {
for(int i=0;i<100;i++){
if(num>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖出第"+num--+"张票了");
}
}
}
}
执行结果如下:
结论:出现卖了0张票,而且还会出现B和C同时卖第22张票的情况,即2个线程抢夺同一资源的问题。即当线程出现延迟时,会导致线程安全问题。
三、如何解决线程的安全问题?
有如下3种方法
方法1、同步代码块
格式如下:
synchronized (锁对象){
需要被同步的代码
}
注:1、锁对象可以是任意的一个对象;2、如果不存在线程安全的问题,千万不要使用同步代码块,因为会降低效率;3、锁对象必须是多线程共享的一个资源,否则锁不住;4、一个线程在同步代码块中sleep了,并不会释放锁对象。
上述代码使用同步代码块解决如下:
public void run() {
for(int i=0;i<100;i++){
synchronized (this){
if (num > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出第" + num-- + "张票了");
}
}
}
}
方法2、同步方法
同步方法就是用Synchronized修饰一个方法
格式如下:
Synchronized 返回值类型 方法名称(参数列表){
....
}
同步方法的同步监听器其实就是this
package com.cn.xianChen;
public class SellTicketDemo implements Runnable{
private int num=50; //定义票的数量
@Override
public void run() {
//调用同步方法
sellTicket();
}
/**
* 同步方法
* */
public synchronized void sellTicket(){
for(int i=0;i<100;i++){
if (num > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出第" + num-- + "张票了");
}
}
}
}
注意事项:1、同步方法的锁对象是固定的,不能由你来指定;
2、如果是一个非静态的同步函数的锁,对象是this对象
3、如果是静态的同步函数的锁,对象是当前函数所属类的字节码文件(即class对象)
方法3、静态方法的同步
注:推荐使用同步代码块.原因如下:
1、同步代码块的锁对象可以由我们随意指定,方便控制。而同步方法的锁对象不是固定的,不能由我们控制
2、同步代码块可以很方便的控制需要被同步代码的范围,同步方法必须是整个方法里的所有代码都被同步了。