Java学习日志Day22_多线程_卖票_同步锁_等待唤醒

一、多线程实现方式二:实现关系(Runnable)

1.步骤:
1)自定义一个类,实现Runnable接口,重写run方法
2)在用户线程(main)创建当前"资源类"对象
然后创建Thread类对象,将"资源类"对象作为参数传递
public Thread(Runnable target,String name)
3)分别启动线程
//Thread类的静态功能
public static Thread currentThread():表示正在执行的线程对象的引用

举例:
public class MyRunnable implements  Runnable{
    //t1,t2,t3
    @Override
    public void run() {
        for(int x = 0 ; x < 100 ; x ++){
            System.out.println(Thread.currentThread().getName()+":"+x);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        //创建资源类对象
        MyRunnable my = new MyRunnable() ;//共享资源

        //创建线程类对象
        Thread t1 = new Thread(my,"t1") ;
        Thread t2 = new Thread(my,"t2") ;
        Thread t3 = new Thread(my,"t3") ;

        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

多线程实现方式1: 继承关系

	SellTicket st1 = new SellTicket() ;
    SellTicket st2 = new SellTicket() ;
    SellTicket st3 = new SellTicket() ;

    //设置线程名称
    st1.setName("窗口1") ;
    st2.setName("窗口2") ;
    st3.setName("窗口3") ;

    //启动线程
    st1.start() ;
    st2.start() ;
    st3.start();

三个栈指向三个堆:分别在出售100张票 “没有数据共享”
继承关系具有局限性—>重写run方法----->Thread类的run---->通过实现Runnable接口的run 方法
不仅仅继承run方法,还将其他无关方法继承过来,不利于功能的扩展! (体现不出来:面向接口编程)

多线程实现方式2: (推荐)
“资源共享”
每一个线程都在使用同一个对象 st(SellTicket对象的引用) 体现出 “面向接口编程”
静态代理 模式
最大特点:真实角色和代理类都必须实现同一个接口!
代理类 对真实角色的功能进行方法增强!
代理类
真实角色

        SellTicket 实现Runnable接口 完成run方法重写
        Thread类 本身实现Runnable接口 完成run方法重写

             SellTicket st = new SellTicket() ;
            //创建多个线程类对象,将资源共享类作为参数传递
            Thread t1 = new Thread(st,"窗口1") ;
            Thread t2 = new Thread(st,"窗口2") ;
            Thread t3 = new Thread(st,"窗口3") ;

            //启动线程
            t1.start();
            t2.start();
            t3.start();

二、案例:电影卖票

/* 电影院有三个窗口,总共100张票,使用多线程模拟!
 *
 * 实现方式1:
 *      使用继承关系
 *
 *      本身描述的就是三个窗口都在卖自己的100张(弊端:体现不出来的资源共享的概念)
 * /
public class SellTicket  extends  Thread{
    //有100张票
    public static int tickets = 100 ;
    //创建一个把锁
    private Object obj = new Object() ;

    @Override
    public void run() {
        //模拟一直有票
        while(true){
            synchronized (obj){
                //加入网络延迟,睡眠100毫秒
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if(tickets>0){
                    System.out.println(getName()+"正在出售第"+tickets+"张票");
                    tickets -- ;
                }else{
                    break ;
                }
            }
        }
    }
}

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

        //创建三个线程
        SellTicket st1 = new SellTicket() ;
        SellTicket st2 = new SellTicket() ;
        SellTicket st3 = new SellTicket() ;

        //设置线程名称
        st1.setName("窗口1") ;
        st2.setName("窗口2") ;
        st3.setName("窗口3") ;

        //启动线程
        st1.start() ;
        st2.start() ;
        st3.start();
    }
}
 /* 电影院有三个窗口,总共100张票,使用多线程模拟!
 *  实现方式2
 *        使用Runnable接口的方式
 * 第二种方式能够体现  "资源共享"
 * 程序出现:
 *      1)一个张票可能被卖多次(出现同票)
 *              线程的执行具有随机性(原子性操作:最简单,最基本的操作语句:--,++...)
 *      2)出现了负票
 *             线程的延迟性导致(加入睡眠100毫秒)
 *      现在的程序存在 "多线程安全问题"
 */
public class SellTicket implements Runnable {
    public static int tickets = 100 ;//100张票
    //t1,t2,t3
    @Override
    public void run() {

        //为了模拟一直有票
        while(true){
            if(tickets>0){  //t1进来
                //睡眠100毫秒

                //为了模拟网络延迟
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName()+
                        "正在第"+(tickets--)+"张票");
            }

            //线程的执行:具有"原子性操作": tickets--   (线程的随机性导致的)
            //t1先进来, 记录以前的值:100
            //在t1记录100数据的时候,准备--的时候,t2进来,可能将t1之前记录的100值输出,100 出现同票

            //第二种情况:出现了负票
            //当前t1线程---第2张票
            //睡眠100毫秒
            //t3线程抢占到--->(将t1线程的2记录---完成--)第1张票
            //t1,t2,t3 ---->t2先抢占到 ---->记录之前1,--完成后,变成0张票
            //当前内存中记录值还没输出0的时候,t1抢占到之后,记录t2记录0张票, t1的票数--,   出现-1
        }
    }
}

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

        //创建资源共享类对象
        SellTicket st = new SellTicket() ;
        //创建多个线程类对象,将资源共享类作为参数传递
        Thread t1 = new Thread(st,"窗口1") ;
        Thread t2 = new Thread(st,"窗口2") ;
        Thread t3 = new Thread(st,"窗口3") ;

        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

三、多线程安全问题

多线程
线程的生命周期
现在的程序虽然能够体现"资源共享",程序出负票和同票,如何解决安全问题?

  1. 校验多线程安全问题的标准是什么?
    1)查看当前程序是否是多线程环境 是
    2)是否存在共享数据 是 tickets
    3)是否有多条语句对共享数据进行操作 存在

    1),2)都必须具备:现在使用的就是多线程环境,而且存在共享数据

    从3)入手
    Java提供一个同步机制:将多条语句对共享数据操作的使用同步代码块包括起来
    格式:
    synchronized(锁对象){ //多个线程必须使用的同一把锁 (可以任意的Java类对象)
    将多条语句对共享数据操作
    }

四、同步锁

synchronized:关键字---- 内置语言实现的(jvm来实现锁定操作: 锁的释放:自动释放)
可以是通过代码块(代码中),可以在方法上使用(同步方法)
synchronized同步锁—属于"悲观锁"
悲观锁:自己线程本身在执行的时候,其他线程不能进入同步代码块中,其他线程不能进入
修改数据,保证数据的安全性! (针对频繁的写入操作)

public class SellTicket implements Runnable {
    public static int tickets = 100 ;//100张票

    private Demo demo = new Demo();
   // private Object obj = new Object() ;

    //t1,t2,t3
    @Override
    public void run() {
        //为了模拟一直有票
        while(true){
        //t1,t2,t3
            //加入同步代码块
            //把它理解为:门的开/关
//            synchronized(new Object()){ //三个线程有自己的锁对象

            //t1进来,t2和t3没有办法进入的
           // synchronized(obj){ //一个锁对象
            //锁对象可以是任意的Java类对象
            synchronized(demo){ //一个锁对象
                if(tickets>0){  //t1进来
                    //睡眠100毫秒
                    //为了模拟网络延迟
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+
                            "正在第"+(tickets--)+"张票");
                }
            }
        }
    }
}

//自定义的类
class Demo{
}

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

        //创建资源共享类对象
        SellTicket st = new SellTicket() ;
        //创建多个线程类对象,将资源共享类作为参数传递
        Thread t1 = new Thread(st,"窗口1") ;
        Thread t2 = new Thread(st,"窗口2") ;
        Thread t3 = new Thread(st,"窗口3") ;

        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}
  1. 代理:
    让别人替自己本身完成一些事情!

    举例: 过年回家买车票,让别人帮忙代买!
    
    代理:
            代理角色:帮助真实角色对他本身的功能进行增强(完成代理完成不了的事情!)
            真实角色:只只专注于自己完成的事情
    
            举例: 结婚这件事情
    
                真实角色:you :你
                代理角色:weddingCompany:婚庆公司
    

    Java代理模式----结构型设计模式

        静态代理
                 同一个接口
                 特点:代理角色和真实角色必须同一个接口
    
         动态代理(后期框架底层使用的这个模式)
                  JDK动态代理:基于接口
                  CGLib动态代理:基于子类
    
                  多线程的实现方式2:
                  MyRunnable myrunnable = new MyRunnable() ;  //MyRunnable implements Runnable接口
                  //线程类
                  Thread t1 = new Thread(myrunnable,"t1") ;   //Thread类本身实现 Runnable接口
                  Thread t2 = new Thread(myrunnable,"t2") ;
                  Thread t3 = new Thread(myrunnable,"t3") ;
    
举例:
public class StaticProxyDemo {

    public static void main(String[] args) {

        //接口多态
        //真实角色
        Marry marry = new You() ;
        marry.marry() ;

        System.out.println("-----------------------");

        //加入婚庆代理角色
        You you = new You() ;
        //创建婚庆公司
        WeddingCompany wdc = new WeddingCompany(you) ;
        wdc.marry();
    }
}

//定义接口:Marry
interface  Marry{
    public abstract  void marry() ;
}

//定义一个子实现类:真实角色
class You implements Marry{

    @Override
    public void marry() {
        System.out.println("我很高兴,我要结婚了...");
    }
}

//代理角色weddingCompany:婚庆公司 要实现Marry接口
class WeddingCompany implements  Marry{
    private You you ;
    public WeddingCompany(You you){
        this.you = you ;
    }

    @Override
    public void marry() {
        System.out.println("结婚之前,婚庆公司布置婚礼现场...");
        you.marry();
        System.out.println("结婚完毕,很高兴,给婚庆付尾款...");
    }
}
举例:
public class SellTicket implements  Runnable {
    //100张票
    public static int tickets = 100 ;
//    public  Object obj = new Object() ;

    //定义一个统计变量
    int x = 0  ;

    @Override
    public void run() {
        //模拟一直有票
        while(true){
            if(x % 2 == 0){
//                synchronized (this){
                synchronized (SellTicket.class){
                    if(tickets>0){
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
                    }
                }
            }else{
                //x%2!=0
                sellTicket();
            }
            x ++ ;
        }
    }
    //如果一个方法进来就是一个同步代码块---->
    // 可以将synchronized抽取到方法声明上:同步方法 (非静态)
    //同步方法:它的锁对象:this 当前类对象的地址值引用
   /* private   synchronized  void sellTicket() {

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

   静态的同步方法 ---静态的东西都和类直接相关 :锁对象---就是 (反射相关)类名.class--->class 包名.类名{}  字节码文件对象
    private  static  synchronized  void sellTicket() {

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

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

        //Class  sellTicketClass = SellTicket.class;
       // System.out.println(sellTicketClass);//class com.qf.thread_06.SellTicket :字节码文件对象

        //创建资源共享类对象
        SellTicket st = new SellTicket() ;
        //创建多个线程类对象,将资源共享类作为参数传递
        Thread t1 = new Thread(st,"窗口1") ;
        Thread t2 = new Thread(st,"窗口2") ;
        Thread t3 = new Thread(st,"窗口3") ;

        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

JDK5以后Java提供了比syncrhonized(jvm来操作:自动释放锁)更广泛的锁定操作,
程序员可以在某个位置自己去加锁,(弊端):手动释放锁
java.util.concurrent.locks.Lock 接口 不能实例化
提供成员方法
void lock() :获取锁
void unlock():手动试图释放锁

ReentrantLock :子实现类 跟synchronized用于法相似
可重入的互斥锁!

synchronized:  关键字---> 内置语言实现---通过jvm调用的
      由jvm自动释放锁
      它不仅仅可以应用在方法体中(同步代码块),也可以引用在方法上(同步方法)

Lock :是一个接口
       手动加锁和手动释放锁
       一般都是在方法中的语句中使用
举例:
public class SellTicket implements Runnable {

    //100张票
    public static int tickets = 100 ;

    //声明一个锁:Lock
    private Lock lock = new ReentrantLock() ;

    @Override
    public void run() {
        //模拟一直有票
        while(true){

            //获取一个锁:某个线程执行到这块:必须持有锁
            lock.lock();

            //执行完毕,手动释放锁
            //在开发中 :try...catch...finally :捕获异常 ,不会使用throws
            //变形格式:try...catch...catch...
            //try...finally...
            try{
                if(tickets >0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
                }
            }finally {
                //释放资源代码块
                //释放锁
                lock.unlock();
            }
        }
    }
}

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

        //创建资源类对象
        SellTicket st  = new SellTicket() ;

        Thread t1 = new Thread(st,"窗口1") ;
        Thread t2 = new Thread(st,"窗口2") ;
        Thread t3 = new Thread(st,"窗口3") ;

        //启动线程
        t1.start();
        t2.start();
        t3.start();
     }
}

五、死锁——等待唤醒机制

死锁的解决
Java的等待唤醒机制
虽然syncrhonized可以解决线程安全问题:同步代码块/同步方法,但是执行效率低,可能出现死锁
两个线程或者多个线程出现互相等待的情况!

   解决死锁问题方案:"生产者消费者思想" 必须保证多个多线程必须使用的同一个资源对象!
举例:
public class MyDieLock {

    //创建两把锁对象 (静态实例变量)
    public static final Object objA = new Object() ;
    public static final Object objB = new Object() ;
}

public class DieLock implements Runnable {

    //提供一个boolean类型的变量
    private boolean flag ;
    public DieLock(boolean flag){
        this.flag = flag ;
    }

    @Override
    public void run() {
        if(flag){
            synchronized (MyDieLock.objA){
                System.out.println("if objA");
                synchronized (MyDieLock.objB){
                    System.out.println("if objB");
                }
            }
        }else{
            synchronized (MyDieLock.objB){
                System.out.println("else objB");
                synchronized (MyDieLock.objA){
                    System.out.println("else objA");
                }
            }
        }
    }
}

public class DieLockDemo {

    public static void main(String[] args) {

        //创建资源类对象
        DieLock dl1 = new DieLock(true) ;
        DieLock dl2 = new DieLock(false) ;

        //创建线程类对象
        Thread t1 = new Thread(dl1) ;
        Thread t2 = new Thread(dl2) ;

        //启动线程
        t1.start();
        t2.start();
        /**
         *   if(flag){
         *             synchronized (MyDieLock.objA){
         *                 System.out.println("if objA");
         *                 synchronized (MyDieLock.objB){
         *                     System.out.println("if objB"); //需要等待第二个线程 objB释放锁掉
         *                 }
         *             }
         *         }else{
         *             synchronized (MyDieLock.objB){
         *                 System.out.println("else objB");
         *                 synchronized (MyDieLock.objA){
         *                     System.out.println("else objA");//需要等待第一个线程 将objA锁释放掉
         *                 }
         *             }
         *         }
         *
         *
         * 情况1:
         * if objA
         * else objB
         *
         * 当第一个ObjA锁被使用,输出"if objA"
         * 同时第二个线程dl2: ObjB锁被使用 "else objB"
         *
         *
         * 情况2:
         * else objB
         * if objA
         *
         *
         * 情况3:理想情况:
         * if objA
         *      if objB
         *  else objB
         *      else objA
         */
    }
}
等待唤醒机制:
/* 包子数据
 */
public class Student {

    String name ;
    int age ;

    //是否存在学生数据标记
    boolean flag ; //true有数据,false没有数据

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

/* 生产者资源类
 */
public class SetThread implements  Runnable {

    private Student s ;
    public SetThread(Student s){
        this.s =  s ;
    }

    //统计变量
    int x = 0 ;
    @Override
    public void run() {

        //不断产生数据
        while(true){

            synchronized (s){
                //判断如果当生产者有数据
                if(s.flag){
                    try {
                        //生产者有数据,需要等待消费者线程使用
                        s.wait(); //锁对象在调用wait()
                        //wait():当前锁对象该调用方法的时候,会立即是否锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //当前在执行下面代码的之前:线程的执行资格被GetThread所在的消费者线程
                if(x % 2 == 0){
                    s.name  = "高圆圆" ;
                    s.age = 41 ;

                }else{
                    s.name = "杨德财" ;
                    s.age = 27 ;
                }
                x ++ ;

                //更改标记
                s.flag = true ;
                //通知对方线程:赶紧使用数据
                s.notify(); //锁对象调用的唤醒方法
            }
        }
       // Student s = new Student();
    }
}

/* 消费者资源类
 */
public class GetThread implements Runnable {

    private Student s ;
    public GetThread(Student s){
        this.s = s ;
    }

    @Override
    public void run() {

        //模拟一直消费数据
        while(true){

            synchronized (s){
                if(!s.flag){
                    //如果消费者没有数据了,需要等待生产线程产生数据
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println(s.name+"---"+s.age);

                //更改标记
                s.flag = false ;//没有数据了
                //通知对方线程:生产线程 :产生数据
                s.notify() ;
            }
        }
       // Student s = new Student() ;
    }
}

/* 测试类
 *      SetThread:生产数据
 *      GetThread:消费数据
 *      Student:学生数据
 *
 *      问题1 :出现数据 null--0 :
 *
 *      在生产资源类和消费者资源类中:分别new Student() :不是同一个对象
 *
 *      模拟一直有数据:加入循环操作while(true)
 *
 *      问题2:姓名和年龄不符
 *      线程的执行具有随机性导致,多线程程序不安全
 *
 *      校验多线程安全问题的标准是什么?
 *          1)查看是否是多线程环境
 *          2)是否存在共享数据
 *          3)是否有多条语句对共享数据进行操作
 *
 *          3)改进:使用同步代码块包括起来!
 *
 *          问题3:学生数据输出的时候,一次性打印很多
 *                  线程的一点点时间片:足够当前线程执行很多次!
 *
 *          优化:
 *          不需要输出学生数据的时候, 一次性输出很多,而是分别依次输出
 *                  "高圆圆" 41
 *                  "杨德财" 27
 *                  ...
 *                  ..
 */
public class ThreadDemo {
    public static void main(String[] args) {
        Student s = new Student() ; //同一个资源类对象
        //创建生产资源类对象
        SetThread st = new SetThread(s) ;
        //创建消费者资源类对象
        GetThread gt = new GetThread(s) ;

//        创建线程类对象
        Thread sThread = new Thread(st) ;
        Thread gThread = new Thread(gt) ;

        //启动
        sThread.start();
        gThread.start();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

igfff

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值