Java SE核心多线程二

线程安全
  • 单线程程序不会出现线程安全 问题
  • 多线程程序没有访问共享数据不会出现线程安全问题
  • 多线程访问共享数据会产生线程安全问题
  • 当某一线程睡眠时,它即失去了cpu的执行权

在这里插入图片描述

  • 卖票案例
public class ThreadSafe implements Runnable{
    //定义一个多线程的票源
    private int ticket = 100;

    /**
     *  线程安全问题:买票
     * */
    //设置线程任务:卖票
    @Override
    public void run() {
        while (true) {
            //判断是否存在票源
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                        e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "线程正在卖第" + ticket + "张票");
                ticket--;
            }
        }
    }
}

public class DemoTicket {
    /**
     *  创建三个线程,模拟卖票程序
     *    因为要实现共享数据,所以要创建一个类
     * */
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        ThreadSafe run = new ThreadSafe();
        //创建Thread类对象,构造方法中传递Runnable接口实现类对象
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        Thread t3 = new Thread(run);

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

在这里插入图片描述

同步方法来解决线程安全问题
同步对象原理
  • 使用了对象锁,也叫同步锁,也叫做对象监视器
  • 当多个线程抢夺cpu资源时,谁先抢夺到谁执行run方法,当遇到synchronized代码块时,会判断有无锁对象,当有时,就会获取到锁对象,执行synchronized代码块中内容,当执行完毕之后,会归还锁对象给同步代码块。当另一个线程也抢夺到cpu资源,会执行run方法,直到遇到synchronized代码块,判断有无锁对象,当没有时cpu会进入阻塞状态,直到某一个线程归还锁对象,此时这个线程才会执行,代码块保证保证统一时刻仅有一个线程执行。(当某一线程执行完同步代码块中内容才会释放对象锁)
  • 因为每次都要判断有无synchronized关键字,因为效率会变低
1、同步代码块
  • 同步代码块中锁对象可以是任意对象
  • 必须保证多个线程使用的锁对象是同一个
  • 作用:把同步代码块锁住,只让一个线程在同步代码块中执行

public class ThreadSafe implements Runnable{
    //定义一个多线程的票源
    private int ticket = 100;

    /**
     *  线程安全问题:买票
     * */

    /**
     *  格式:
     *     synchronized(锁对象) {
     *         可能会出现线程安全问题的代码
     *     }
     * */
    /**
     *  同步代码块中锁对象可以是任意对象
     *  但必须保证多个线程使用的锁对象是同一个
     *  锁对象的作用:
     *      把同步代码块锁住,只让一个线程在同步代码块中执行
     * */

    //创建一个锁对象
    Object obj = new Object();

    //设置线程任务:卖票
    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
                //判断是否存在票源
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "线程正在卖第" + ticket + "张票");
                    ticket--;
                }
            }
        }
    }
}

在这里插入图片描述

2、同步方法
  • 同步方法也会把方法内部的代码锁住,只让一个线程执行
  • 同步方法锁对象是实现类对象
3、静态同步方法
  • 静态同步方法的锁对象是本类的class属性,即class文件对象(反射)
public class FunctionThread implements Runnable{
    //定义一个多线程的票源
    private static int ticket = 100;

    /**
     *  同步方法解决线程安全问题
     *    格式:synchronized  返回值类型  函数名() {
     *        可能会出现线程安全问题的代码块
     *    }
     *
     * */


    /**
     *  线程安全问题:买票
     * */
    //设置线程任务:卖票
    @Override
    public void run() {
        while (true) {
            //payTicket();
            payTicketStatic();
        }
    }

    //定义一个同步方法
    public synchronized void payTicket() {
        //判断是否存在票源
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "线程正在卖第" + ticket + "张票");
            ticket--;
        }
    }

    //静态同步方法
    public static synchronized void payTicketStatic() {
        //判断是否存在票源
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "线程正在卖第" + ticket + "张票");
            ticket--;
        }
    }
}

解决线程安全的第三种方法lock锁
/*
*    lock锁实现了比synchronized 更广泛的锁定操作
*     lock接口中的方法
*       void lock()   获取锁
*       void unlock()  释放锁
*
*     使用步骤:
*        1、在成员位置创建一个ReentrantLock对象
*        2、在可能出现安全问题的代码前调用Lock接口中的方法lock获取锁
*        3、在可能出现安全问题的代码后调用Lock接口中的方法unlock释放锁
*
* */

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

public class RunnableImple implements Runnable{
    private static int ticket = 100;

    //1、在成员位置创建一个ReentrantLock对象
    Lock l = new ReentrantLock();

    @Override
    public void run() {
        //重复卖票操作
        while (true) {
            //2、在可能出现安全问题的代码前调用Lock接口中的方法lock获取锁
            l.lock();

            //判断是否存在票源
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //3、在可能出现安全问题的代码后调用Lock接口中的方法unlock释放锁
                    l.unlock();  //无论程序是否异常,都会释放锁对象,写在这里更便捷
                }
                System.out.println(Thread.currentThread().getName() + "线程正在卖第" + ticket + "张票");
                ticket--;
            }

        }
    }
}
线程的六种状态

在这里插入图片描述

唤醒多个线程
  • 进入到TimeWaiting(记时等待)有俩种方式
    • 1、使用sleep(long m)方法,在毫秒值结束后,线程唤醒进入到Runnable/Blocked状态
    • 2、使用wait(long m)方法,wait方法在毫秒值结束时,还没有被notify唤醒,就会自动醒来,线程唤醒进入Runnable/Blocked状态
  • 唤醒的方法:
    • void notify() 唤醒在此对象监视器上等待的单个线程
    • void notifyAll() 唤醒在此对象监视器上等待的所有线程
public class demo11waitandnotify {
    public static void main(String[] args) {
        //创建 锁对象,保证唯一
        Object obj = new Object();
        //创建一个顾客线程
        new Thread() {
            @Override
            public void run() {
                //一直等着卖包子
                while (true) {
                    //使用同步技术 ,保证在等待线程和唤醒线程只有一个在执行
                    synchronized (obj) {
                        System.out.println("顾客1告知老板需要包子的种类和数量");
                        //调用wait方法放弃cpu的执行,进入到WAITING(无线等待状态)
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        //唤醒之后的代码
                        System.out.println("包子已经做好了,顾客1开吃");
                    }
                }
            }
        }.start();

        //创建一个顾客2线程
        new Thread() {
            @Override
            public void run() {
                //一直等着卖包子
                while (true) {
                    //使用同步技术 ,保证在等待线程和唤醒线程只有一个在执行
                    synchronized (obj) {
                        System.out.println("顾客2告知老板需要包子的种类和数量");
                        //调用wait方法放弃cpu的执行,进入到WAITING(无线等待状态)
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        //唤醒之后的代码
                        System.out.println("包子已经做好了,顾客2开吃");
                    }
                }
            }
        }.start();

        //创建老板线程
        new Thread() {
            @Override
            public void run() {
                //一直在卖包子
                while (true) {
                    //做包子花了5秒
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //使用同步技术 ,保证在等待线程和唤醒线程只有一个在执行
                    synchronized (obj) {
                        System.out.println("老板5秒后包子已经做好了,告知顾客");
                        //做好包子后,使用notify唤醒顾客吃包子
                        //obj.notify();  //随机唤醒一个线程

                        //唤醒所有线程
                        obj.notifyAll();
                    }
                }
            }
        }.start();
    }
}
等待唤醒机制
线程之间的通信
  • 等待唤醒机制图解
  • 多个线程处理同一个资源,但是执行任务不同

在这里插入图片描述

线程唤醒机制
  • wait:线程不再活动,不再参加调度,等待notify()唤醒
  • notify():选取wait set的一个线程释放
  • notifyAll():选取wait set的所有线程释放
  • 总结:如果可以获取到锁,则线程从WAITING状态进入RUNNING状态
  • 否则,从wait set出来,又进入entey set状态,线程从WAITING 状态变成BLOCKED状态
  • notify()和wat()要使用同一个锁对象调用,必须在同步代码块和同步方法中执行
  • 包子铺案例
public class BaoZi {
    //皮
    String pi;
    //肉馅
    String xian;
    //有无包子
    boolean flag = false;
}

public class BaoZiPu extends Thread {
    //设置一个包子变量
    private BaoZi bz;
    //为包子变量赋值
    public BaoZiPu(BaoZi bz) {
        this.bz = bz;
    }

    //设置线程任务:生产包子
    @Override
    public void run() {
        //定义一个变量统计包子数量
        int count = 0;
        //让包子铺一直生产包子
        while (true) {
            //使用同步技术保证俩个线程只有一个在执行
            synchronized (bz) {
                //判断有无包子
                if (bz.flag == true) {
                    //包子铺线程进入等待状态
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //包子铺线程被唤醒之后执行带代码
                //增加趣味性,生产俩种包子
                if (count % 2 == 0) {
                    //薄皮,三鲜馅
                    bz.pi = "薄皮";
                    bz.xian = "三鲜馅";
                } else {
                    //厚皮,牛肉馅
                    bz.pi = "厚皮";
                    bz.xian = "牛肉馅";
                }

                count++;
                System.out.println("正在生产包子:" + bz.pi + " " + bz.xian + "包子");
                //生产包子需要3000毫秒
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //生产之后设置包子状态为有
                bz.flag = true;
                //唤醒消费者线程吃包子
                bz.notify();
                System.out.println("包子铺已经生产好了:" + bz.pi + bz.xian + "包子,可以开吃了");
            }
        }
    }
}

public class ChiHuo extends Thread{
    //设置包子变量
    private BaoZi bz;
    //为包子变量赋值
    public ChiHuo(BaoZi bz) {
        this.bz = bz;
    }

    //设置线程任务:吃包子

    @Override
    public void run() {
        //让吃货一直吃包子
        while (true) {
            //使用线程同步技术,保证生产者和消费者为同一个对象锁
            synchronized (bz) {
                //判断有无包子
                if (bz.flag == false) {
                    //吃货调用wait()方法进入等待状态
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //线程被唤醒之后吃包子
                System.out.println("吃货正在吃:" + bz.pi + bz.xian + "的包子");
                //设置包子状态
                bz.flag = false;
                //吃货唤醒包子铺线程生产包子
                bz.notify();
                System.out.println("吃货已经把" + bz.pi + bz.xian + "的包子吃完了,包子铺开始生产包子");
                System.out.println("--------------------------------------------------------------");
            }
        }
    }
}


public class Demo {
    public static void main(String[] args) {
        //创建包子对象
        BaoZi bz = new BaoZi();
        //创建包子线程,生产包子
        new BaoZiPu(bz).start();
        //创建吃货线程,吃包子
        new ChiHuo(bz).start();
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值