一星期全栈速成-----线程安全问题 以及解决方案

线程安全问题

买票问题:

package myRunable_1;

public class MyRunnable implements Runnable{
    private int num = 100;

    @Override
    public void run() {
        while (true){

     System.out.println(Thread.currentThread().getName()+": 卖出第:" +num--+"张票");
                
            }
        }

    }
}

测试类

public class MyTest {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread1 = new Thread(myRunnable,"窗口一:");
        Thread thread2 = new Thread(myRunnable,"窗口二:");
        Thread thread3 = new Thread(myRunnable,"窗口三:");
//好处,可以实现多个接口
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

发现问题:同票问题

一星期全栈速成-----线程安全问题 以及解决方案_第1张图片

问题2:卖出不存在的票

一星期全栈速成-----线程安全问题 以及解决方案_第2张图片

原因:

出现同票原因

// 线程抢夺cpu 执行权时,执行的代码具有原子性
// 原子性是指最基本的代码(最小的语句)

出现负票的原因

线程的运行是抢占式的,出现负票首先是,线程1,2,3都在等于1的时候进去了while循环,导致多次输出

解决


public class My_Thread_Optimal extends Thread{
    private static int ticket = 100;

    private String str = "";

    public My_Thread_Optimal(String name) {
        super(name);
    }

    @Override
    public void run() {
        try {
            //睡眠100毫秒
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //只允许一个程序同步,解决重复票的问题
        synchronized (str){
        while (ticket>0){
            
                //解决最后卖出不存在票的问题
                if (ticket==0){
                    break;
                }
                System.out.println(getName()+": 卖出第:" +ticket+"张票");
                ticket--;
            }
        }

    }
}

同步锁:

当线程出现不安全情况,我们就可以用同步锁来将其锁住,只让一个线程去使用公共的资源。

 synchronized ( obj ){}

参数可以是任意的对象,他的作用就是提供一个钥匙,谁有钥匙就可以打开锁。这样就解决了卖出同票的问题。

 将输出的语句

 和数据改变的语句分开,在判断是否有不存在的票。解决负票的问题。

 一星期全栈速成-----线程安全问题 以及解决方案_第3张图片

 

 

Lock锁:

public void run() {
        while (true){
            lock.lock(); //上锁
            if(ticket>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName()+"第"+ticket+"张票");
                ticket--;// 0
            }
            lock.unlock(); //释放锁
        }
    }

 lock锁使用灵活,不需要参数。

synchronized与lock的区别

synchronized jvm级别的锁,jvm自动上锁和解锁

Lock锁java代码写的锁,需要手动的加锁和释放锁

死锁

多线程产生死锁的四个必要条件:

互斥条件:一个资源每次只能被一个进程使用。

保持和请求条件:一个进程因请求资源而阻塞时,对已获得资源保持不放。

不可剥夺调用:进程已获得资源,在未使用完成前,不能被剥夺。

循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

 

线程的生命周期图

 新建:创建线程对象

就绪:有执行资格,但是没有拿到执行权

运行:有执行资格,且有执行权

阻塞: 没有执行资格,没有执行权; 恢复阻塞回到就绪状态

死亡: 线程变成垃圾,等待回收

一星期全栈速成-----线程安全问题 以及解决方案_第4张图片

线程间的通信

正常思路

生产者:

先看有没有数据,有就等待,没有就生产,生产后,通过消费者来消费数据

消费者:

先看有没有数据,有就消费,没有就等待,且通知生产都生产数据

为了实现上述的问题,Java提供一种机制,等待唤醒机制

案例

取钱案例

分析:

Account对象 (money)

SetMoney对象 (生产者)

GetMoney对象(消费者)

Test(测试类)

Account:


public class Account {
    private int money;
   

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }
}

SetMoney:


import java.util.Scanner;

public class SetMoney implements Runnable{
    Account a = new Account();

    public SetMoney(Account a) {
        this.a = a;
    }

    public SetMoney() {
    }

    @Override
    public void run() {

            while (true){
                synchronized (a){
                    if (a.getMoney()>0){//有钱,不设置
                        try {
                            a.wait();//等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //没钱,设置钱
                    int money=0;
                    Scanner scanner = new Scanner(System.in);

                    System.out.println("请输入要存多少钱:");
                    money=scanner.nextInt();
                    if (a.getMoney()<=0){
                        a.notify();
                        a.setMoney(money);
                        System.out.println(Thread.currentThread().getName() + "存入" + a.getMoney());

                    }

                    a.notify();


                }
            }




    }
}

 GetMoney:


import java.util.Scanner;

public class GetMoney implements Runnable{
    private Account a = new Account();

    public GetMoney(Account a) {
        this.a = a;
    }

    public GetMoney() {
    }

    @Override
    public void run() {

//            Scanner scanner = new Scanner(System.in);
//            int getMoney = scanner.nextInt();
//            System.out.println("请输入要取多少:");

            while (true) {
                synchronized (a) {
                if(a.getMoney()<=0){//没钱,等待
                    try {
                        a.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }


                if ((a.getMoney() - 100 < 0)) {
                    System.out.println("余额不足,请管理先存入");
                    a.notify();
                    break;
                }

                    System.out.println(Thread.currentThread().getName() + "取走:" + a.getMoney()+ "元!");


                    a.setMoney(a.getMoney() - 100);
                    System.out.println("剩余:" + a.getMoney() + "元");

                    a.notify();//唤醒线程
                } //运行到这里,证明原来有钱,flag是true,下面取走,要变false;

            }







    }
}

 Test:


public class Test {
    public static void main(String[] args) {

        Account a = new Account();

        SetMoney setMoney = new SetMoney(a);

        GetMoney getMoney = new GetMoney(a);
        //设置钱
        Thread setThread = new Thread(setMoney,"管理");
        //取钱
        Thread getThread1 = new Thread(getMoney,"张三");
        Thread getThread2 = new Thread(getMoney,"李四");
        Thread getThread3 = new Thread(getMoney,"王五");


        setThread.start();

        getThread1.start();
        getThread2.start();
        getThread3.start();
    }
}

 

线程间通信的内存图

常见情况:

  1. 新建 -- 就绪 -- 运行 -- 死亡

  2. 新建 -- 就绪 -- 运行 -- 就绪 -- 运行 -- 死亡

  3. 新建 -- 就绪 -- 运行 -- 等待阻塞 -- 同步阻塞 -- 就绪 -- 运行--死亡

  4. 新建 -- 就绪 -- 运行 -- 其它阻塞 -- 就绪 -- 运行--死亡

  5. 新建 -- 就绪 -- 运行 -- 同步阻塞 -- 就绪 -- 运行--死亡

一星期全栈速成-----线程安全问题 以及解决方案_第5张图片

你可能感兴趣的:(java,前端,开发语言)