线程安全问题
线程安全问题产生的原因:
1,多个线程在操作共享的数据;
2,操作共享数据的线程代码有多条。
即当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
例一:实际开发中,以火车票售票为例,假设共有10张票,有四个窗口(四个线程)同时在卖票:
class Ticket implements Runnable//extends Thread
{
private int num=10;
Object obj=new Object();
public void run()
{
while(true)
{
if(num>0)
{
try//run方法是接口的方法,没有抛出异常,所以覆盖的时候也不能抛出异常,只能catch
{
Thread.sleep(10);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"...tickets ..."+num--);
}
}
}
}
class ThreadDemo
{
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();
}
}
输出结果
Thread-2...tickets ...10
Thread-1...tickets ...9
Thread-0...tickets ...8
Thread-3...tickets ...7
Thread-2...tickets ...6
Thread-1...tickets ...5
Thread-0...tickets ...4
Thread-3...tickets ...3
Thread-2...tickets ...2
Thread-1...tickets ...1
Thread-0...tickets ...0
Thread-3...tickets ...-1
Thread-2...tickets ...-2
出现了负的票数,显然这是不被允许的。
线程安全问题来源于两个线程同时存取单一对象的数据。
解决思路:
将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算,必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象)//对象可以简单理解为标志位,是对象锁,同步锁
{
需要被同步的代码;
}
同步的好处:解决了线程的安全问题;
同步的弊端:相对地降低了效率,因为同步外的线程都会判断同步锁;
同步的前提:同步中必须有多个线程并使用同一个锁。
例二:加同步代码后:
class Ticket implements Runnable//extends Thread
{
private int num=10;
Object obj=new Object();
public void run()
{
while(true)
{
synchronized(obj)
{
if(num>0)
{
try//run方法是接口的方法,没有抛出异常,所以覆盖的时候也不能抛出异常,只能catch
{
Thread.sleep(20);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"...sale ..."+num--);
}
}
}
}
}
class ThreadDemo
{
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();
}
}
这样的结果就不会出现负数的票数了。
同步方法:
例三:假设有两个储户(两个线程)到银行去存钱,一次存100,各存三次。
class Bank
{
private int sum;
//private Object obj=new Object();
public synchronized void add(int num)//同步函数
{
// synchronized(obj)
// {
sum=sum+num;
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 ThreadDemo
{
public static void main(String[] args)
{
Cus c=new Cus();
Thread t1=new Thread(c);
Thread t2=new Thread(c);
t1.start();
t2.start();
}
}
输出结果:
sum=100
sum=200
sum=300
sum=400
sum=500
sum=600
同步函数使用的锁是this。
同步函数和同步代码块的区别:同步函数的锁是固定的this,同步代码块的锁是任意的对象。
建议使用同步代码块。
静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前 类名.class 表示。
例四:静态的同步函数
class Ticket implements Runnable//extends Thread
{
private static int num=100;
//Object obj=new Object();
boolean flag=true;
public void run()
{
// System.out.println("this:"+this);
if(flag)
while(true)
{
synchronized(this.getClass())//(Ticket.class -》类名.class这个是静态方法里的属性)
{
if(num>0)
{
try//run方法是接口的方法,没有抛出异常,所以覆盖的时候也不能抛出异常,只能catch
{
Thread.sleep(10);
}
catch(InterruptedException e)
{
//应该有处理方法
}
System.out.println(Thread.currentThread().getName()+"...obj ..."+num--);
}
}
}
else
while(true)
this.show();
}
public static synchronized void show()//静态方法当中没有this对象,静态同步方法的对象锁是字节码文件对象(this.getClass())
{
if(num>0)
{
try//run方法是接口的方法,没有抛出异常,所以覆盖的时候也不能抛出异常,只能catch
{
Thread.sleep(10);
}
catch(InterruptedException e)
{
//应该有处理方法
}
System.out.println(Thread.currentThread().getName()+"...function ..."+num--);
}
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Ticket t=new Ticket();//创建一个线程对象
//class clazz=t.getClass();
//class clazz=Ticket.class();
//System.out.println("this:"+this);
Thread t1=new Thread(t);
Thread t2=new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(InterruptedException e){}
t.flag=false;
t2.start();
}
}
单例模式中的多线程问题
先来复习一下单例模式,单例模式有两种形式,一种是饿汉模式:
class Single
{
private static final Single s=new Single();
private Single(){}
public static Single getInstance()
{
return s;
}
}
这种方式的getInstance()方法中由于函数中只有一句语句,所以在多线程使用时不会出现线程安全问题。
懒汉模式:
class Single
{
private static Single s=null;
private Single(){}
public static Single getInstance()
{
if(s==null)
s=new Single();
return s;
}
}
这种方式的getInstance()方法中由于函数中有多句语句,所以在多线程使用时会出现线程安全问题。必须加入同步。
class Single
{
private static Single s=null;
private Single(){}
public static Single getInstance()//如果用同步函数的话每一次进程进来都要判断同步锁,效率降低
{
if(s==null)//多加一次判断就可以解决效率问题
{
synchronized(Single.class)
{
if(s==null)
s=new Single();
return s;
}
}
}
}
死锁
死锁的常见形式之一就是同步的嵌套。
还有一种情况发生在wait()的使用时将所有的线程都冻结了,而没有线程来唤醒。
例五:死锁
class Ticket implements Runnable
{
private int num=100;
Object obj=new Object();
boolean flag=true;
public void run()
{
if(flag)
while(true)
{
synchronized(obj)
{
show();
}
}
else
while(true)
this.show();
}
public synchronized void show()
{
synchronized(obj)
{
if(num>0)
{
try
{
Thread.sleep(10);
}
catch(InterruptedException e)
{
//应该有处理方法
}
System.out.println(Thread.currentThread().getName()+"...function ..."+num--);
}
}
}
}
class DeadLockDemo
{
public static void main(String[] args)
{
Ticket t=new Ticket();//创建一个线程对象
Thread t1=new Thread(t);
Thread t2=new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(InterruptedException e){}
t.flag=false;
t2.start();
}
}
在这个例子中,在同步代码块中,外层的锁是obj,进入同步代码块后执行的show()方法是同步方法,持有锁this。而在同步方法show()中,外层持有this锁,里面的同步代码块持有obj锁。这两个锁互相嵌套。当同步代码块中持有obj锁,进入代码想拿到this锁,而此时show方法刚好持有this锁,进入代码想拿到obj锁时,两方会相持不下,就会发生死锁。
例六:死锁二
class Test implements Runnable
{
private boolean flag;
Test(boolean flag)
{
this.flag=flag;
}
public void run()
{
if(flag)
{
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+"...if locka");
synchronized(MyLock.lockb)
{
System.out.println(Thread.currentThread().getName()+"...if lockb");
}
}
}
else
{
synchronized(MyLock.lockb)
{
System.out.println(Thread.currentThread().getName()+"...else lockb");
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+"...else locka");
}
}
}
}
}
class MyLock
{
public static final Object locka=new Object();
public static final Object lockb=new Object();
}
class DeadLockTest
{
public static void main(String[] args)
{
Test a=new Test(true);
Test b=new Test(false);
Thread t1=new Thread(a);
Thread t2=new Thread(b);
t1.start();
t2.start();
}
}