Java-多线程2

线程安全问题

先看一段代码
public class TicketWindow3 implements Runnable{
    //由于这个类的对象只创建一次,也就只有一个对象,就只有一个tickets变量
    private int tickets = 100; // 1

    @Override
    public void run() {

        while (true) {
            //窗口1,窗口2
            if (tickets > 0) {
                //窗口1,窗口2,窗口3
                //为了模拟更加符合现实生活卖票的场景,我们加入延迟操作
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //窗口1,窗口2
                System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票。。。");
                //窗口1正在出售第100张票。。。
                //窗口2正在出售第100张票。。。
                //...
                //窗口1正在出售第1张票
                //窗口2正在出售第0张票
                //窗口3正在出售第-1张票
            }
        }
    }
}
这是创建线程的代码
/*
        某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
        两种方式实现
            2、实现Runnable接口

        为了模拟更加现实的售票场景,我们加入延迟操作
        但是呢,加入延迟之后,产生了两个问题:
            1、相同的票我们卖了很多次
                CPU的执行操作是原子性,小小的CPU时间片足矣运行很多次
            2、出了卖第0张票和负数的票。
                这个是由于线程的执行具有随机性和延迟性导致的,因为我们加入了sleep休眠方法,让线程变成阻塞状态,让其他线程执行

 */
public class SellTicketDemo3 {
    public static void main(String[] args) {
        //创建自定义类对象
        TicketWindow3 ticketWindow3 = new TicketWindow3();

        //创建3个线程对象,模拟3个窗口进行卖票
        Thread window1 = new Thread(ticketWindow3, "窗口1");
        Thread window2 = new Thread(ticketWindow3, "窗口2");
        Thread window3 = new Thread(ticketWindow3, "窗口3");

        //启动线程
        window1.start();
        window2.start();
        window3.start();
    }
}

出现问题
++为了模拟更加现实的售票场景,我们加入延迟操作但是呢,加入延迟之后,产生了两个问题:++

  • 1、相同的票我们卖了很多次
    CPU的执行操作是原子性,小小的CPU时间片足矣运行很多次
  • 2、出了卖第0张票和负数的票。
    这个是由于线程的执行具有随机性和延迟性导致的,因为我们加入了sleep休眠方法,让线程变成阻塞状态,让其他线程执行

++上一个案例中加入了延迟操作,出现了问题,其实这个问题现象就叫做:线程安全问题要想解决这个问题,就得先搞清楚哪些原因导致的这个问题出现:(这三个条件同时满足的情况下,就会出现线程安全问题)++

  • 1、是否存在多线程环境
  • 2、是否存在共享变量(共享数据)
  • 3、是否有多条语句操作着共享变量(共享数据)
解决办法
  • 条件1,2都是我们无法改变的,我们只能想办法改变第3个条件,只要其中一个不满足,就不会发生线程安全问题
解决此问题的思想
  • 要是有一个办法可以将多条操作着共享变量的代码包成一个整体,相当于给这段代码加了一把锁,在某个线程执行的时候,其他线程
    进不来就可以了。
    直到这个线程执行完后,其他线程才能进入。同一时刻,只能是一个线程操作run方法。

java提供了一个机制给我们使用,这个机制就是用来解决线程安全问题的同步安全机制

同步的好处和弊处:
  • 同步的好处:
    解决了多线程的安全问题
  • 同步的弊端:
    加入同步之后,就相当于加了一把锁,这样每次进入同步代码块的时候都会判断一下,
    这样无形之中,降低了程序运行的效率。

解决方案一:

        语句定义格式:
            synchronized(对象){
                需要同步的代码;
            }
            同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
            1、这里的对象是什么?
                我们先随便创建一个对象试试;
            2、需要同步的代码又是哪些呢?
                是操作共享变量的代

同步代码时,锁的对象是谁?

  • 1、同步代码块的锁对象是谁?
    任意对象,多个线程之间的锁对象要是唯一的。
  • 2、同步方法所判断的锁对象是谁?
    当前对象this
  • 3、同步静态方法所判断的锁对象是谁?
    当前线程类的对象
使用synchronized关键字上锁的代码如下:
package com.shujia.wyh.day26;

/*
    定义一个类实现Runnable接口
 */
public class TicketWindow2 implements Runnable {
    //定义100张票
    private static int tickets = 100;
    //    private Object obj = new Object();
    private Demo d = new Demo();
    int i = 0;

//    @Override
//    public void run() {
//        while (true){
//            synchronized (obj){
//                if(tickets>0){
//                    try {
//                        Thread.sleep(100);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//                    System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票。。");
//                }
//            }
//        }
//    }

//    @Override
//    public void run() {
//        while (true) {
//            synchronized (d) {
//                if (tickets > 0) {
//                    try {
//                        Thread.sleep(100);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票。。");
//                }
//            }
//        }
//    }

    @Override
    public void run() {
        while (true) {
            if (i % 2 == 0) {

                synchronized (TicketWindow2.class) {
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票。。");
                    }
                }
            } else {
                sellTickets();
            }
            i++;
        }
    }

//    public synchronized void sellTickets() {
//        if (tickets > 0) {
//            try {
//                Thread.sleep(100);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票。。");
//        }
//    }

    public synchronized static void sellTickets() {
        if (tickets > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票。。");
        }
    }
}

package com.shujia.wyh.day26;

/*
        1、同步代码块的锁对象是谁?
            任意对象,多个线程之间的锁对象要是唯一的。

        2、同步方法所判断的锁对象是谁?
            当前对象this

        3、同步静态方法所判断的锁对象是谁?
            当前线程类的对象


 */
public class SellTicketDemo2 {
    public static void main(String[] args) {
        //创建Runnable类对象
        TicketWindow2 ticketWindow1 = new TicketWindow2();

        //借助Thread类创建线程对象
        Thread window1 = new Thread(ticketWindow1, "窗口1");
        Thread window2 = new Thread(ticketWindow1, "窗口2");
        Thread window3 = new Thread(ticketWindow1, "窗口3");

        //启动线程
        window1.start();
        window2.start();
        window3.start();
    }
}

解决线程安全问题方案二:使用Lock锁

注意点

  • 多个线程对象拥有的锁对象要一致
代码如下:
package com.shujia.wyh.day26;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/*
    定义一个类实现Runnable接口
 */
public class TicketWindow3 implements Runnable {
    //定义100张票
    private int tickets = 100;
    //    private Object obj = new Object();
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();
            if (tickets > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票。。");
            }
            lock.unlock();
        }
    }
}
    public static void main(String[] args) {
        //创建Runnable类对象
        TicketWindow3 ticketWindow1 = new TicketWindow3();

        //借助Thread类创建线程对象
        Thread window1 = new Thread(ticketWindow1, "窗口1");
        Thread window2 = new Thread(ticketWindow1, "窗口2");
        Thread window3 = new Thread(ticketWindow1, "窗口3");

        //启动线程
        window1.start();
        window2.start();
        window3.start();
    }
}

死锁现象

问题产生原因:

  • 两个或者两个以上的线程在争夺资源的过程中,出现了相互等待的现象,这个现象称之为死锁现象

举例说明

  • 中国人和外国人吃饭的案例:
    正常情况下:
    中国人:两支筷子
    外国人:一把刀,一把叉
    死锁的情况:
    中国人:一支筷子,一把刀
    外国人:一支筷子,一把叉
代码实现:

++先定义一个类,获取锁的对象++

public class MyLock {
    //创建两把锁
    public static final Object lock1 = new Object();
    public static final Object lock2 = new Object();
}

++发生死锁的代码:++

public class DieLock extends Thread{

    private boolean flag;
    DieLock(boolean flag){
        this.flag = flag;
    }

    @Override
    public void run() {
        if(flag){
            synchronized (MyLock.lock1){
                System.out.println("if lock1");
                synchronized (MyLock.lock2){
                    System.out.println("if lock2");
                }
            }
        }else {
            synchronized (MyLock.lock2){
                System.out.println("else lock2");
                synchronized (MyLock.lock1){
                    System.out.println("else lock1");
                }
            }
        }
    }
}

线程代码:

public class DieLockDemo {
    public static void main(String[] args) {
        //创建两个线程对象
        DieLock d1 = new DieLock(true);
        DieLock d2 = new DieLock(false);

        //启动线程
        d1.start();
        d2.start();
    }
}
posted @ 2022-04-12 08:19  a-tao必须奥利给  阅读(24)  评论(0编辑  收藏  举报