黑马程序员——Java基础---多线程<二>

——- android培训java培训、期待与您交流! ——–

7.5、线程安全问题

线程安全 :一数据错乱、二死锁

问题演示 一:数据错乱
package XianChen;

public class LiZi {

    public static void main(String[] args) {
//      开启新线程  将要运行的 对象传入
        XianCh x = new XianCh();
        Thread t1 = new Thread(x);
        Thread t2 = new Thread(x);
        Thread t3 = new Thread(x);
//      启动新线程
        t1.start();
        t2.start();
        t3.start();

    }
}
class XianCh implements Runnable{
    private int num=100;
//  覆写 抽象方法 run()
    public void run() {

        for(int x=0;x<50;x++){
            if(num>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"……"+ num--);                   
            }
        }   
    }

}
class XianCh2 extends Thread{

//  覆写 抽象方法 run()
    public void run() {
        for(int x=10;x>0;x--)
            System.out.println(Thread.currentThread().getName()+"……"+x);

    }

}
/*
 * 输出结果截取:
    Thread-0……13
    Thread-2……12
    Thread-1……11
    Thread-0……10
    Thread-2……9
    Thread-1……8
    Thread-0……7
    Thread-2……6
    Thread-1……5
    Thread-0……4
    Thread-2……3
    Thread-1……2
    Thread-0……1
    Thread-2……0
    Thread-1……-1        //错误数据

*/
问题分析:

出现上图安全问题的原因在于Thread-0通过了if判断后,在执行到“num–”语句之前,num此时仍等于1。
CPU切换到Thread-1、Thread-2、Thread-3之后,这些线程依然可以通过if判断,从而执行“num–”的操作,因而出现了0、-1的情况。

也就是说线程安全问题产生的原因:

1. 多个线程在操作共享的数据。
2. 操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
解决方案

思路:
保证单一线程操作数据

具体方法

synchronized

在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象){
       需要被同步的代码;
} 

同步的好处:解决了线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
同步的前提:必须有多个线程并使用同一个锁。
例:

package XianChen;
public class LiZi {

    public static void main(String[] args) {
//      开启新线程  将要运行的 对象传入
        XianCh x = new XianCh();
        Thread t1 = new Thread(x);
        Thread t2 = new Thread(x);
        Thread t3 = new Thread(x);
//      启动新线程
        t1.start();
        t2.start();
        t3.start();

    }
}
class XianCh implements Runnable{
    private int num=100;
//  覆写 抽象方法 run()
// 使用 synchronized 同步函数         方式 一
    public synchronized void run() {

        for(int x=0;x<50;x++){
//          方式二 加入同步代码块
            synchronized(obj ){
                if(num>0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }           System.out.println(Thread.currentThread().getName()+"……"+ num--);                   
                }
            }
        }   
    }   
}
class XianCh2 extends Thread{   
//  覆写 抽象方法 run()
    public void run() {
        for(int x=10;x>0;x--)           System.out.println(Thread.currentThread().getName()+"……"+x);    
    }
}

注:
同步函数和同步代码块的区别:
1. 同步函数的锁是固定的this。
2. 同步代码块的锁是任意的对象。
建议使用同步代码块。

由于同步函数的锁是固定的this,同步代码块的锁是任意的对象,那么如果同步函数和同步代码块都使用this作为锁,就可以实现同步。

问题演示 二:死锁
概念、原因、条件

什么叫死锁?
所谓死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
那么为什么会产生死锁呢?
1.因为系统资源不足。
2.进程运行推进的顺序不合适。
3.资源分配不当。

产生死锁的条件有四个:
1.互斥条件:所谓互斥就是进程在某一时间内独占资源。
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

死锁 一:请求与保持条件
package XianChen;
/**
 * 
 *  
 */
public class LiZi {

    public static void main(String[] args) {
        Rec r = new Rec();
//      开启新线程  将要运行的 对象传入
        Sell s= new Sell(r);
        Get g = new Get(r);
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(g);
//      启动新线程
        t1.start();
        t2.start();
    }
}
class Rec {
    private Object objA=new Object();
    private Object objB=new Object();
    private boolean flag=true;
    private int A=100;
    private int B=100;

    public  void sell() {   
        for(int x=0;x<50;x++){
            synchronized(objA){
                System.out.println(Thread.currentThread().getName()+":A上锁我拿到 A"+ A--);
                synchronized(objB){
                    if(!flag)
                        System.out.println(Thread.currentThread().getName()+":B上锁我拿到 B"+ B--);
                }
            }
        }
    }
    public void get(){
        synchronized(objB){
            System.out.println(Thread.currentThread().getName()+":B上锁我拿到 B"+ B--);
            synchronized(objA){
                if(flag){
                    System.out.println(Thread.currentThread().getName()+":A上锁我拿到 A"+ A--);
                    flag=false;
                }
            }
        }
    }
}

class Sell implements Runnable{
    private Rec r=null;
    Sell(Rec r){
        this.r = r;
    }
    public void run() {
        while(true){
            r.sell();
        }
    }
}

class Get implements Runnable{
    private Rec r=null;
    Get(Rec r){
        this.r=r;
    }
    public void run() {
        while(true){        
            r.get();
        }       
    }
}
/*
        输出结果:
        Thread-0:A上锁我拿到 A100
        Thread-1:B上锁我拿到 B100
 */
问题分析

死锁大多是因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。

解决方案

避免锁的嵌套,用完之后立即解锁

具体案例
package XianChen;
/**
 * 
 *  
 */
public class LiZi {

    public static void main(String[] args) {
        Rec r = new Rec();
//      开启新线程  将要运行的 对象传入
        Sell s= new Sell(r);
        Get g = new Get(r);
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(g);
//      启动新线程
        t1.start();
        t2.start();
    }
}
class Rec {
    private Object objA=new Object();
    private Object objB=new Object();
    private boolean flagA=true,flagB=true;
    private int A=100;
    private int B=100;

    public  void sell() {
    // 当两个资源都为零 时 停止程序
        while(flagA||flagB){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized(objA){
                if(A>0)
                    System.out.println(Thread.currentThread().getName()+":A上锁我拿到 A"+ A--);
                else
                    flagA =false;
            }
            synchronized(objB){
                if(B>0)
                    System.out.println(Thread.currentThread().getName()+":A上锁我拿到 B"+ B--);
                else
                    flagB =false;
            }           
        }

    }
    public void get(){
        // 当两个资源都为零 时 停止程序
        while(flagA||flagB){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized(objB){
                if(B>0)
                    System.out.println(Thread.currentThread().getName()+":B上锁我拿到 B"+ B--);
                else
                    flagB =false;
            }
            synchronized(objA){
                if(A>0)
                    System.out.println(Thread.currentThread().getName()+":A上锁我拿到 A"+ A--);
                else
                    flagA =false;
            }
        }           
    }

}

class Sell implements Runnable{
    private Rec r=null;
    Sell(Rec r){
        this.r = r;
    }
    public void run() {
        r.sell();
    }
}

class Get implements Runnable{
    private Rec r=null;
    Get(Rec r){
        this.r=r;
    }
    public void run() {     
        r.get();    
    }
}
/*
        输出结果:
        Thread-0:A上锁我拿到 A7
        Thread-0:A上锁我拿到 B7
        Thread-1:B上锁我拿到 B6
        Thread-1:A上锁我拿到 A6
        Thread-0:A上锁我拿到 A5
        Thread-0:A上锁我拿到 B5
        Thread-1:B上锁我拿到 B4
        Thread-1:A上锁我拿到 A4
        Thread-0:A上锁我拿到 A3
        Thread-0:A上锁我拿到 B3
        Thread-1:B上锁我拿到 B2
        Thread-1:A上锁我拿到 A2
        Thread-0:A上锁我拿到 A1
        Thread-0:A上锁我拿到 B1
 */
死锁 二 循环等待条件
问题演示:
package XianChen;
/**
 * 
 *  
 */
public class LiZi {

    public static void main(String[] args) {
        Rec r = new Rec();
//      开启新线程  将要运行的 对象传入
        Sell s= new Sell(r);
        Get g = new Get(r);
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(g);
//      启动新线程
        t1.start();
        t2.start();
    }
}
class Rec {
    private Object objA=new Object();
    private Object objB=new Object();
//  private boolean flagA=true,flagB=true;*/
    private int A=100;
    private int B=100;

    public  void sell() {
        synchronized(objA){
            if(A>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":A上锁我拿到 A"+ A--);              
            }
            get();
        }       

    }
    public synchronized void get(){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized(objA){
            if(B>0){
                System.out.println(Thread.currentThread().getName()+":A上锁我拿到 B"+ B--);
            }
        }
    }           

}

class Sell implements Runnable{
    private Rec r=null;
    Sell(Rec r){
        this.r = r;
    }
    public void run() {
        while(true){
            r.sell();               
        }
    }
}

class Get implements Runnable{
    private Rec r=null;
    Get(Rec r){
        this.r=r;
    }
    public void run() { 
        while(true){
            r.get();                
        }

    }
}
/*
 */
问题分析

这其实还是两个锁的嵌套
执行get 方法则必须获取this对象锁,然后才能执行其中的同步代码块。
当线程t1获取到objA对象锁执行同步代码块,线程t2获取到this对象锁执行get方法。同步代码块中的get方法因无法获取到this对象锁无法执行,sell方法中的同步代码块因无法获取到objA对象锁无法执行,就会产生死锁。

解决方案

同问题一,去掉锁的嵌套

具体方案
package XianChen;
/**
 * 
 *  
 */
public class LiZi {

    public static void main(String[] args) {
        Rec r = new Rec();
//      开启新线程  将要运行的 对象传入
        Sell s= new Sell(r);
        Get g = new Get(r);
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(g);
//      启动新线程
        t1.start();
        t2.start();
    }
}
class Rec {
    private Object objA=new Object();
    private Object objB=new Object();
//  private boolean flagA=true,flagB=true;*/
    private int A=100;
    private int B=100;

    public  void sell() {
        synchronized(objA){
            if(A>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":A上锁我拿到 A"+ A--);              
            }
            get();
        }       

    }
    public void get(){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized(objA){
            if(B>0){
                System.out.println(Thread.currentThread().getName()+":A上锁我拿到 B"+ B--);
            }
        }
    }           

}

class Sell implements Runnable{
    private Rec r=null;
    Sell(Rec r){
        this.r = r;
    }
    public void run() {
        while(true){
            r.sell();               
        }
    }
}

class Get implements Runnable{
    private Rec r=null;
    Get(Rec r){
        this.r=r;
    }
    public void run() { 
        while(true){
            r.get();                
        }

    }
}
/*
        输出结果:
        Thread-0:A上锁我拿到 B89
        Thread-1:A上锁我拿到 B88
        Thread-0:A上锁我拿到 A89
        Thread-0:A上锁我拿到 B87
        Thread-0:A上锁我拿到 A88
        Thread-0:A上锁我拿到 B86
        Thread-0:A上锁我拿到 A87
        Thread-0:A上锁我拿到 B85
        Thread-0:A上锁我拿到 A86
        Thread-0:A上锁我拿到 B84
        Thread-0:A上锁我拿到 A85
        Thread-0:A上锁我拿到 B83
        Thread-0:A上锁我拿到 A84
        Thread-0:A上锁我拿到 B82
        Thread-1:A上锁我拿到 B81
 */
死锁处理总结

不难看出,产生死锁主要是四个条件,因此只要不让四个发生即可

互斥:
要打破这个条件,就是要让多个线程能共享资源,就相当于A和B能同时举起ball1一样,当然在这个例子里我们可以这样修改规则,但是在其它程序中就不一定能了,比如说一个“读”线程,一个“写”线程,它们都能操作同一文件。在这种情况下,我们就不能“又读又写”文件,否则有可能会读到脏数据!因此我们很少从这方面考虑。

占有等待
打破占有等待,只要当检测到自己所需的资源仍被别的线程占用,即释放自己已占有的资源(毫不利己,专门利人,呵呵~),或者在经过一段时间的等待后,还未得到所需资源,才释放,这都能打破占有等待。

不剥夺,循环等待
这两个线程也是如此,经过一段时间的等待后,还未得到所需资源,
就释放资源,即可打破占有等待;
另外其实打破非剥夺,只要给线程制定一个优先级即可

——– android培训java培训、期待与您交流! ———

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值