A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 段孟钊 中级黑马   /  2019-5-13 15:39  /  1015 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 小石姐姐 于 2019-5-17 11:12 编辑

一、进程和线程进程:是正在运行的应用程序
​        是系统进行资源分配和调用的独立单位
​        每一个进程都有它自己的内存空间和系统资源
线程:是进程中的单个顺序控制流,是一条执行路径
​        单线程:一个进程如果只有一条执行路径,则称为单线程(以前所学的都是单线程)
​        多线程:一个进程如果有多条执行路径,则称为多线程
二、继承Thread类的方式实现多线程
方法介绍
[td]
方法名
说明
void run()
在线程开启后,此方法被调用执行,里面写需要执行的代码
void start()
使此线程开始执行(开启线程),Java虚拟机会自动调用run()方法,使线程进入就绪状态
实现步骤
​        定义一个类MyThread继承Thread类
​        在MyThread类中重写run()方法
​        创建MyThread类的对象
​        启动线程
[Java] 纯文本查看 复制代码
//定义一个类MyThread继承Thread类
public class MyThread extends Thread{
    @override
    //在MyThread类中重写run()方法
    public void run(){
        for(int i = 0;i < 100; i++){
            System.out.println(i);
        }
    }
}
public class Test{
    public static void main(String[] args){
        //创建MyThread类的对象
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        //这只是一个不同的方法调用,
        //mt1.run();
        //mt2.run();
        
        //启动线程
        mt1.start();
        mt2.start();
    }
}
两个小问题
​        为什么要重写run()方法?
​        因为run()方法时用来封装需要被线程执行的代码
​        run()方法和start()方法的区别?
​        run():封装线程执行的代码,直接调用,相当于普通方法的调用
​        start():启动线程;然后由JVM调用此线程的run()方法
三、设置和获取线程名称方法介绍[td]
方法名
说明
void setName(String name)
将此线程的名称更改为参数name
String getName()
(获取)返回此线程的名称
Thread currentThread()
返回对当前正在执行的线程对象的引用
Thread()
无参构造方法
Thread(String name)
有参构造方法,将参数name传入给当前线程
[Java] 纯文本查看 复制代码
public class MyThread extends Thread{
    public MyThread(){}
    public MyThread(String name){
        super(name);
        }
    
    @override
    public void run(){
        for(int i = 0; i < 100; i++){
            System.out.println(getName() + " "+ i);
        }
    }
}
package thread;

//实现多线程的第一种方式:
// 继承Thread类,重写run方法,创建对象,调用start方法
public class ThreadTest1 {
    public static void main(String[] args) {
        //创建Thread类的子类MyThread类
        /*MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();*/
        //通过有参构造方法,调用父类构造方法,将参数name传入给调用该方法的线程
        MyThread mt1 = new MyThread("线程1");
        MyThread mt2 = new MyThread("线程2");
        //这个run方法是当普通方法使用的
        /*mt1.run();
        mt2.run();*/
        //setName(),设置线程名称
        /*mt1.setName("姓名");
        mt2.setName("居住地");*/

        //getName(),没有设置线程名称,系统会默认设置一个线程名称,
        // 一般写在run方法内部的输出语句,为了更加明确的看到是哪个线程在运行
        /*System.out.println(mt1.getName());//Thread-0,没有设置线程名称,系统会默认设置一个线程名称
        System.out.println(mt2.getName());//Thread-1*/
        //获取当前正在执行的线程的名称--这是在获取main方法里的线程名称
        System.out.println(Thread.currentThread().getName());

        //启动线程,
        mt1.start();
        mt2.start();
    }
}
四、线程优先级线程调度两种调度方式
​                分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
​                抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,
​                优先级高的线程获取的 CPU 时间片相对多一些
​        Java使用的是抢占式调度模型
随机性
​        假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就                                是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的,
还有,不是优先级越高,就一定要执行该线程,优先级越高,说明他得到时间片(CPU的使用权)的概率越大
优先级相关方法
​        
[td]
方法名
说明
final int getPriority()
(获取)返回此线程的优先级
final void setPriority(int newPriority)
更改此线程的优先级
static int NORM_PRIORITY
当前线程的默认优先级(5),优先级的范围(1-10)
[Java] 纯文本查看 复制代码
public class ThreadPriority extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}

package priority;
//线程的优先级练习,优先级越高,获取CPU的使用权概率越高
public class PriorityTest {
    public static void main(String[] args) {
        MyThread mt1 = new MyThread("线程1");
        MyThread mt2 = new MyThread("线程2");

        //获取当前线程的优先级
        /*System.out.println(mt1.getPriority());//5
        System.out.println(mt2.getPriority());//5*/

        //设置优先级(1-10)
        mt1.setPriority(10);
        mt2.setPriority(3);
        //获取当前线程的优先级
        System.out.println(mt1.getPriority());//10
        System.out.println(mt2.getPriority());//3

        //获取线程的最大优先级
        System.out.println(Thread.MAX_PRIORITY);
        //获取线程的最小优先级
        System.out.println(Thread.MIN_PRIORITY);
        //获取线程的默认优先级
        System.out.println(Thread.NORM_PRIORITY);


        //启动线程
        mt1.start();
        mt2.start();
    }
}

五、线程控制相关方法[td]
方法名
说明
static coid sleep(long millis)
使当前正在执行的线程停留(睡眠)(暂停执行)指定的毫秒数
void join()
等待这个线程死亡(执行完毕)
void setDaemon(boolean on)
参数on为true时,将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
sleep()练习
[Java] 纯文本查看 复制代码
package threadcontrol;

public class MyThread extends Thread {
    public MyThread() {
    }

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

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

package threadcontrol;
//sleep()练习
public class SleepTest {
    public static void main(String[] args) {
        MyThread mt1 = new MyThread("刘备");
        MyThread mt2 = new MyThread("曹操");
        MyThread mt3 = new MyThread("孙权");

        mt1.start();
        mt2.start();
        mt3.start();
    }
}

join()练习
[Java] 纯文本查看 复制代码
package threadcontrol;
public class JoinTest {
    public static void main(String[] args) {
        MyThread mt1 = new MyThread("康熙");
        MyThread mt2 = new MyThread("四阿哥");
        MyThread mt3 = new MyThread("八阿哥");

        mt1.start();
        try {
            mt1.join();//能使当前线程执行完毕,其它线程才能执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        mt2.start();
        mt3.start();
    }
}

setDaemon()练习
[Java] 纯文本查看 复制代码
package threadcontrol;

public class SetDaemonTest {
    public static void main(String[] args) {
        MyThread mt1 = new MyThread("关羽");
        MyThread mt2 = new MyThread("张飞");

        Thread.currentThread().setName("刘备");
        //setDaemon(),当进程里都是守护线程时,守护线程将会结束,不会立即停止运行
        mt1.setDaemon(true);
        mt2.setDaemon(true);
        mt1.start();
        mt2.start();
        mm();

    }
    public static void mm(){
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

六、线程的生命周期
线程的五种状态:
新建(实例化Thread类)——就绪(调用start方法)——运行(该线程抢夺到CPU使用权,被其他线程抢到CPU使用权,该线程会回到就绪状态)——死亡(功能执行完毕)——堵塞(sleep方法,或者其它堵塞方式,这时该线程会再次进入就绪状态)
七、实现Runnable接口的方式实现多线程Thread构造方法[td]
方法名
说明
Thread(Runnable target)
传入Runnable接口的实现类对象
Thread(Runnable target, String name)
传入Runnable接口的实现类对象和线程名称(String)
实现步骤
定义一个类MyRunnable实现Runnable接口
在MyRunnable类中重写run()方法
创建MyRunnable类的对象
创建Thread类的对象,把MyRunnable对象作为构造方法的参数
启动线程
[HTML] 纯文本查看 复制代码
package runnable;
//定义一个类MyRunnable实现Runnable接口

public class MyRunnable implements Runnable {
    //- 在MyRunnable类中重写run()方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
package runnable;
/*
- 定义一个类MyRunnable实现Runnable接口
- 在MyRunnable类中重写run()方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
- 启动线程

 */
public class Test {
    public static void main(String[] args) {
        //创建MyRunnable类的对象
        MyRunnable mr = new MyRunnable();
        //创建Thread类的对象,把MyRunnable对象作为构造方法的参数
        //Thread(Runnable target)
        Thread t1 = new Thread(mr);
        t1.setName("飞机");
        //Thread(Runnable target, String name)
        Thread t2 = new Thread(mr,"地铁");
        //启动线程
        t1.start();
        t2.start();
    }
}

八、卖票
[Java] 纯文本查看 复制代码
package runnable;
//定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
public class SellTicket implements Runnable {
    private int ticket = 100;
    /*
    在SellTicket类中重写run()方法实现卖票,代码步骤如下
  - 判断票数大于0,就卖票,并告知是哪个窗口卖的
  - 卖了票之后,总票数要减1
  - 票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
     */
    @Override
    public void run(){
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
                ticket--;
            }
        }
    }
}
package runnable;
/*
- 案例需求
  某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
- 实现步骤
  - 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
  - 在SellTicket类中重写run()方法实现卖票,代码步骤如下
  - 判断票数大于0,就卖票,并告知是哪个窗口卖的
  - 卖了票之后,总票数要减1
  - 票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
  - 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
  - 创建SellTicket类的对象
  - 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
  - 启动线程
-

 */
//定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
public class SellTicketTest {
    public static void main(String[] args) {
        //创建SellTicket类的对象
        SellTicket st = new SellTicket();
        //创建三个Thread类的对象,把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] 纯文本查看 复制代码
public class SellTicket implements Runnable {
    private int tickets = 100;

    @Override
    public void run() {
        //相同的票出现了多次
//        while (true) {
//            //tickets = 100;
//            //t1,t2,t3
//            //假设t1线程抢到CPU的执行权
//            if (tickets > 0) {
//                //通过sleep()方法来模拟出票时间
//                try {
//                    Thread.sleep(100);
//                    //t1线程休息100毫秒
//                    //t2线程抢到了CPU的执行权,t2线程就开始执行,执行到这里的时候,t2线程休息100毫秒
//                    //t3线程抢到了CPU的执行权,t3线程就开始执行,执行到这里的时候,t3线程休息100毫秒
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                //假设线程按照顺序醒过来
//                //t1抢到CPU的执行权,在控制台输出:窗口1正在出售第100张票
//                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
//                //t2抢到CPU的执行权,在控制台输出:窗口2正在出售第100张票
//                //t3抢到CPU的执行权,在控制台输出:窗口3正在出售第100张票
//                tickets--;
//                //如果这三个线程还是按照顺序来,这里就执行了3次--的操作,最终票就变成了97
//            }
//        }

        //出现了负数的票
        while (true) {
            //tickets = 1;
            //t1,t2,t3
            //假设t1线程抢到CPU的执行权
            if (tickets > 0) {
                //通过sleep()方法来模拟出票时间
                try {
                    Thread.sleep(100);
                    //t1线程休息100毫秒
                    //t2线程抢到了CPU的执行权,t2线程就开始执行,执行到这里的时候,t2线程休息100毫秒
                    //t3线程抢到了CPU的执行权,t3线程就开始执行,执行到这里的时候,t3线程休息100毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //假设线程按照顺序醒过来
                //t1抢到了CPU的执行权,在控制台输出:窗口1正在出售第1张票
                //假设t1继续拥有CPU的执行权,就会执行tickets--;操作,tickets = 0;
                //t2抢到了CPU的执行权,在控制台输出:窗口1正在出售第0张票
                //假设t2继续拥有CPU的执行权,就会执行tickets--;操作,tickets = -1;
                //t3抢到了CPU的执行权,在控制台输出:窗口3正在出售第-1张票
                //假设t2继续拥有CPU的执行权,就会执行tickets--;操作,tickets = -2;
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                tickets--;
            }
        }
    }
}

十、同步代码块解决数据安全问题
  • 安全问题出现的条件

    • 是多线程环境
    • 有共享数据
    • 有多条语句操作共享数据

  • 如何解决多线程安全问题呢?

    • 基本思想:让程序没有安全问题的环境

  • 怎么实现呢?

    • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
    • Java提供了同步代码块的方式来解决

  • 同步代码块格式:
      
      synchronized(任意对象) {
          多条语句操作共享数据的代码
      }
    synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
  • 同步的好处和弊端  

    • 好处:解决了多线程的数据安全问题
    • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率


[Java] 纯文本查看 复制代码
public class SellTicket implements Runnable {
    private int tickets = 100;
    //private Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            //tickets = 100;
            //t1,t2,t3
            //假设t1抢到了CPU的执行权
            //假设t2抢到了CPU的执行权
            //synchronized (obj) {
            synchronized (this) {//代表把当前方法锁起来,只有第一个抢到使用权的线程使用,使用完毕后,轮到下一个线程
                //t1进来后,就会把这段代码给锁起来
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                        //t1休息100毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //窗口1正在出售第100张票
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--; //tickets = 99;
                }
            }
            //t1出来了,这段代码的锁就被释放了
        }
    }
}

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();
    }
}

十一、同步方法解决数据安全问题
  • 同步方法的格式
    同步方法:就是把synchronized关键字加到方法上
      
      修饰符 synchronized 返回值类型 方法名(方法参数) {
          方法体;
      }同步方法的锁对象是什么呢?
    ​        this
  • 静态同步方法
    同步静态方法:就是把synchronized关键字加到静态方法上
      
      修饰符 static synchronized 返回值类型 方法名(方法参数) {
          方法体;
      }同步静态方法的锁对象是什么呢?
    ​        类名.class

[Java] 纯文本查看 复制代码
package synchronizedtest;

public class Person {
    public void test1() {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //非静态同步方法默认锁this--当前线程,锁不住非静态同步方法
    public synchronized void test2() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void test3() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //静态同步方法默认锁类名.class--Person.class
    public static synchronized void test4(){
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static synchronized void test5(){
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

package synchronizedtest;

public class MyThread1 extends Thread {
    private Person p;
    public MyThread1(){}

    public MyThread1(Person p) {
        this.p = p;
    }

    public MyThread1(String name, Person p) {
        super(name);
        this.p = p;
    }

    public Person getP() {
        return p;
    }

    public void setP(Person p) {
        this.p = p;
    }

    @Override
    public void run() {
//        p.test1();
//        p.test2();
        Person.test5();
    }
}

package synchronizedtest;

public class MyThread2 extends Thread {
    private Person p;
    public MyThread2(){}

    public MyThread2(Person p) {
        this.p = p;
    }

    public MyThread2(String name, Person p) {
        super(name);
        this.p = p;
    }

    public Person getP() {
        return p;
    }

    public void setP(Person p) {
        this.p = p;
    }

    @Override
    public void run() {
//        p.test2();
//        p.test3();
        Person.test4();
    }
}

package synchronizedtest;

public class Test {
    public static void main(String[] args) {
        Person p = new Person();
        MyThread1 m1 = new MyThread1("线程1",p);
        MyThread1 m2 = new MyThread1("线程2",p);

        m1.start();
        m2.start();
    }
}

十二、线程安全的类
  • StringBuffer--StringBuilder
    • 线程安全,可变的字符序列
    • 从版本JDK 5开始,被StringBuilder 替代。 通常应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步

  • Vector--ArrayList
    • 从Java 2平台v1.2开始,该类改进了List接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Vector被同步。 如果不需要线程安全的实现,建议使用ArrayList代替Vector

  • Hashtable--HashMap
    • 该类实现了一个哈希表,它将键映射到值。 任何非null对象都可以用作键或者值
    • 从Java 2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Hashtable被同步。 如果不需要线程安全的实现,建议使用HashMap代替Hashtable

  • Collections.synchronizedMap-List-Set,可以把非安全线程,转换为安全线程
十三、Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
  • ReentrantLock构造方法

[td]
方法名
说明
ReentrantLock()创建一个ReentrantLock的实例
加锁解锁方法
[td]
方法名
说明
void lock()获得锁(写在需要锁的功能的前面)
void unlock()释放锁(写在需要锁的功能的前面,表示把该功能给锁住了)
[Java] 纯文本查看 复制代码
public class SellTicket implements Runnable {
    private int tickets = 100;
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--;
                }
            } finally {
                lock.unlock();
            }
        }
    }
}
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();
    }
}
十四、生产者和消费者模式概述
  • 概述
    生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。
    所谓生产者消费者问题,实际上主要是包含了两类线程:
    ​        一类是生产者线程用于生产数据
    ​        一类是消费者线程用于消费数据
    为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
    生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
    消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
    生产者——共享数据区域——消费者
  • Object类的等待和唤醒方法
    [td]
    方法名
    说明
    void wait()导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
    void notify()唤醒正在等待对象监视器的单个线程
    void notifyAll()唤醒正在等待对象监视器的所有线程

十五、生产者和消费者案例案例需求
生产者消费者案例中包含的类:
奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作
测试类(BoxDemo):里面有main方法,main方法中的代码步骤如下
①创建奶箱对象,这是共享数据区域
②创建消费者创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
③对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
④创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
⑤启动线程
Box类——奶箱
[Java] 纯文本查看 复制代码
package productercustomer;
//奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
public class Box {
    private int mile = 0;
    private boolean state = false;//用来代表奶箱有没有牛奶
    //获取牛奶
    public synchronized void getMile() {
            //如果没有牛奶,就等待有牛奶
            if (!state) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //如果把牛奶拿走了,就把状态改成没有牛奶
            System.out.println("程序员拿了第" + this.mile + "瓶奶");
            state = false;
            //唤醒送奶工存储牛奶
            notifyAll();
    }
    //存储牛奶
    public synchronized void setMile(int mile) {
            //如果有牛奶,就等待程序员来拿牛奶
            if (state) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //如果没有牛奶就添加牛奶
            this.mile = mile;
            System.out.println("送奶工放入第" + this.mile + "瓶奶");
            //存储完牛奶后,就把状态改成有牛奶的状态
            state = true;
            //唤醒程序员来拿牛奶
            notifyAll();
    }
}
Producter类——送奶工
[Java] 纯文本查看 复制代码
package productercustomer;
//生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
public class Producter implements Runnable {
    private Box b;

    public Producter(Box b){
        this.b = b;
    }
    @Override
    public void run() {
        //让送奶工总共送一个月的奶,一天一瓶
        for (int i = 1; i <= 30; i++) {

            b.setMile(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
Customer类——程序员
[Java] 纯文本查看 复制代码
package productercustomer;

public class Customer implements Runnable {
    private Box b;

    public Customer(Box b){
        this.b = b;
    }

    @Override
    public void run() {
        //让程序员那边送多少奶,我就拿多少瓶
        while (true) {
            b.getMile();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
测试类
[Java] 纯文本查看 复制代码
package productercustomer;

public class BoxTest {
    public static void main(String[] args) {
        Box b  =new Box();

        Producter p = new Producter(b);
        Customer c = new Customer(b);

        Thread t1 = new Thread(p);
        Thread t2 = new Thread(c);

        t1.start();
        t2.start();
    }
}




0 个回复

您需要登录后才可以回帖 登录 | 加入黑马