【线程安全问题现象】
例:
class Demo implements Runnable{
private int num=100;
public void run(){
while(true){
if(num>0){
try{
Thread.sleep(10);
}catch(InterrupterException e){
}
System.out.println("num="+num--);
}
}
}
}
class TicketDemo{
public static void main(String []str){
Demo d=new Demo();
Thread t1=new Thread(d);
Thread t2=new Thread(d);
Thread t3=new Thread(d);
Thread t4=new Thread(d);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
这个时候控制台有可能会出现 num=-1..,这就是所谓的线程安全问题。
【线程安全问题产生的原因】
1.多个线程在操作共享的数据
2.操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算。
就会导致线程安全问题的产生。
如上述例子:
num=1时,
假设当线程t1执行到if(num>0)时这时,cpu切换到了其它线程,并操作了num,那么当cpu再切换到t1时,
执行下一句输出语句就会输出num=-1;
【解决思路】
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,
其它线程时不可以参与运算的。
必须
要当前线程把这些代码都执行完毕后,其它线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。
【同步代码块】
同步代码块的格式:
synchronized(对象)
{
需要被同步的代码;
}
相当于锁。这就是同步锁
class Demo implements Runnable{
private int num=100;
Object object=new Object();
public void run(){
while(true){
synchronized(object){
if(num>0){
try{
Thread.sleep(10);
}catch(InterrupterException e){
}
System.out.println("num="+num--);
}
}
}
}
}
使用同步的前提:必须有多个线程并使用同一个锁。
同步代码块是同步的第一种表现形式,同步的第二种表现形式是同步函数。
【同步函数】
class Bank{
private int sum;
private synchronized void add(int num){
sum+=sum;
System.out.println("sum="+sum);
}
}
class Cus implements Runable{
public void run(){
Bank b=new Bank();
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();
}
}
【静态同步锁】
静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前 类名.class
表示
【单例模式的多线程问题】
回顾一下单例模式:
饿汉式:
class Single{
private static Single s=new Single();
private Single(){}
public static Single getInstance(){
return s;
}
}
懒汉式:
class Single{
private static Single s=null;
private Single(){}
public static Single getInstance(){
if(s==null){
s=new Single();
}
return s;
}
}
饿汉式在多线程中是没什么问题的,但是懒汉式就会有线程安全问题。
class Single{
private static Single s=null;
private Single(){}
public static Single getInstance(){
if(s==null){
-->0-->1
s=new Single();
}
return s;
}
}
当线程0执行到if(s==null)时,cpu切换到线程1,这时切换回0线程就new了两个Single。
方法一:同步函数
public static synchronized Single getInstance(){
if(s==null){
-->0-->1
s=new Single();
}
return s;
}
弊端:效率低,因为要判断同步锁,又要判断空
方法二:同步代码块
public static Single getInstance(){
if(s==null){
synchronized(Single.class){
if(s==null){
-->0-->1
s=new Single();
}
}
}
return s;
}
第一个判断提高效率,加锁解决线程安全问题。
【线程死锁】
例子;
class Single{
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(){
}
}
}
}
}