大数据笔记13—java基础篇9(多线程)


苟有恒,何必三更眠五更起; 最无益,莫过一日曝十日寒。

多进程

迅雷这个软件同时可以下载多部电影,并且可以边下载边播放。
我们在生活中使用到的很多软件都能同时做很多事情!
回想一下我们自己写的java程序,代码都是从上往下运行,在同一个时间内只能做一件事情。并不能像这些常见的软件一样可以同时做很多事情。
想要让我们自己写的程序也能同时做很多事情,就得使用到多线程技术。
比如说:可以使用java代码同时复制多部电影(模拟迅雷下载多部电影)
那么什么是多线程呢??我们先从并发与并行讲起

1.1 并发与并行

并行:指两个或多个事件在同一时刻发生(同时发生)。

并发:指两个或多个事件在同一个时间段内发生。

在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。

注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。

1.2 线程与进程

进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
线程:进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

• 一个应用程序中至少得有一个进程 一个进程中至少有一个线程

进程:一个应用软件中至少包含一个进程 进程是系统最基本的执行单位
线程:一个进程中最小执行单元 一个进程中至少要包含一个线程

并发:同一个时间段内同时做多个事情
并行:听一个时间刻 在同时做多个事情

多线程执行:

Java中多线程执行 抢占式的执行 线程执行具有随机性

CPU会将物理线程的执行时间切割为一个个小的事件片段 我们创建的线程会争抢这些时间片段,CPU会在多个我们创建的线程之间来回切换执行 切换到哪一个线程(那一个线程抢占到时间片段) 哪一个线程就会执行

为什么给我们感官好像是多个线程会同时执行??
CPU的切换速度足够快…

核心
物理线程:多个物理线程才可以并行执行
超线程技术
多线程是否可以提高程序执行效率??
不一定:看电脑的配置 如果是多核多线程的 是可以提高执行效率 因为可以并行执行
但是 如果是单核心的就无法提高执行效率
我们主要看中的是体验:可以同时执行多个任务!!

进程与线程的区别

• 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。

• 线程:堆空间是共享的栈空间是独立的,线程消耗的资源比进程小的多。

**注意:**下面内容为了解知识点

1:因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于 CPU 的调度,程序员是干涉不了的。而这也就造成的多线程的随机性。

2:Java 程序的进程里面至少包含两个线程,主进程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个线程,java 本身具备了垃圾的收集机制,所以在 Java 运行时至少会启动两个线程。

3:由于创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是创建多进程。

创建线程类

Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建启动多线程的步骤如下:

定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。

创建Thread子类的实例,即创建了线程对象

调用线程对象的start()方法来启动该线程

代码如下:

//创建线程 
public class MyThread extends Thread{
    //定义指定线程名称的构造方法
    public MyThread(String name){
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i < 2000; i++) {
            System.out.println(getName()+"run正在执行!");
        }
    }
}
//测速类
public static void main(String[] args) {
        //创建自定义线程对象
        MyThread myThread = new MyThread("新的线程");
        myThread.start();
        for (int i = 0; i < 2000; i++) {
            System.out.println("主线程");
        }
    }

多线程原理

在这里插入图片描述

程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。

多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。

当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。

Thread类

在API 中可以看到

构造方法:

• public Thread():分配一个新的线程对象。

• public Thread(String name):分配一个指定名字的新的线程对象。

• public Thread(Runnable target):分配一个带有指定目标新的线程对象。

• public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

常用方法:

• public String getName():获取当前线程名称。

• public void start():导致此线程开始执行; Java虚拟机调用此线程的run方法。

• public void run():此线程要执行的任务在此处定义代码。

• public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。

• public static Thread currentThread():返回对当前正在执行的线程对象的引用。

翻阅API后得知创建线程的方式总共有两种,一种是继承Thread类方式,一种是实现Runnable接口方式,方式一我们上一天已经完成,接下来讲解方式二实现的方式。

实现接口方式(Runnable)

采用java.lang.Runnable也是非常常见的一种,我们只需要重写run方法即可。

步骤如下:

定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

调用线程对象的start()方法来启动线程

//接口
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 2000; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
}

//测试类
public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
        Thread thread = new Thread(mr,"小强");
        thread.start();
        for (int i = 0; i < 2000; i++) {
            System.out.println("wangcai"+i);
        }

}

匿名内部类方式

使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。
使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法:
public class NoNameInnerClassThread {
public static void main(String[] args) {
// new Runnable(){
// public void run(){
// for (int i = 0; i < 20; i++) {
// System.out.println(“张宇:”+i);
// }
// }
// }; //—这个整体 相当于new MyRunnable()
Runnable r = new Runnable(){
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println(“张宇:”+i);
}
}
};
new Thread®.start();

    for (int i = 0; i < 20; i++) {
      	System.out.println("费玉清:"+i);
    }
}

}

线程安全

线程安全

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
我们通过一个案例,演示线程的安全问题:
电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个(本场电影只能卖100张票)。
我们来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票)
需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟
模拟票:

public class Ticket implements Runnable {
    private int ticket = 100;
    /*
     * 执行卖票操作
     */
    @Override
    public void run() {
        //每个窗口卖票的操作 
        //窗口 永远开启 
        while (true) {
            if (ticket > 0) {//有票 可以卖
                //出票操作
                //使用sleep模拟一下出票时间 
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                //获取当前线程对象的名字 
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在卖:" + ticket--);
            }
        }
    }
}

测试类:
public class Demo {
	public static void main(String[] args) {
		//创建线程任务对象
		Ticket ticket = new Ticket();
		//创建三个窗口对象
		Thread t1 = new Thread(ticket, "窗口1");
		Thread t2 = new Thread(ticket, "窗口2");
		Thread t3 = new Thread(ticket, "窗口3");
		
		//同时卖票
		t1.start();
		t2.start();
		t3.start();
	}
}

结果中有一部分这样现象:
在这里插入图片描述

发现程序出现了两个问题:
1.相同的票数,比如5这张票被卖了两回。
2.不存在的票,比如0票与-1票,是不存在的。
这种问题,几个窗口(线程)票数不同步了,这种问题称为线程不安全。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

线程同步(synchronized)

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。
要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。
根据案例简述:
窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。
那么怎么去使用呢?有三种方式完成同步操作:
1.同步代码块。
2.同步方法。
3.锁机制。
2.3 同步代码块
•同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:

synchronized(同步锁){
     需要同步操作的代码
}

同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
1.锁对象 可以是任意类型。
2.多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
使用同步代码块解决代码:


```java
public class Ticket implements Runnable{
	private int ticket = 100;
	
	Object lock = new Object();
	/*
	 * 执行卖票操作
	 */
	@Override
	public void run() {
		//每个窗口卖票的操作 
		//窗口 永远开启 
		while(true){
			synchronized (lock) {
				if(ticket>0){//有票 可以卖
					//出票操作
					//使用sleep模拟一下出票时间 
					try {
						Thread.sleep(50);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					//获取当前线程对象的名字 
					String name = Thread.currentThread().getName();
					System.out.println(name+"正在卖:"+ticket--);
				}
			}
		}
	}
}

当使用了同步代码块后,上述的线程的安全问题,解决了。

同步方法

•同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
格式:
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步锁是谁?
对于非static方法,同步锁就是this。

对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
使用同步方法代码如下:

public class Ticket implements Runnable{
	private int ticket = 100;
	/*
	 * 执行卖票操作
	 */
	@Override
	public void run() {
		//每个窗口卖票的操作 
		//窗口 永远开启 
		while(true){
			sellTicket();
		}
	}
	
	/*
	 * 锁对象 是 谁调用这个方法 就是谁 
	 *   隐含 锁对象 就是  this
	 *    
	 */
	public synchronized void sellTicket(){
        if(ticket>0){//有票 可以卖	
            //出票操作
            //使用sleep模拟一下出票时间 
            try {
              	Thread.sleep(100);
            } catch (InterruptedException e) {
              	// TODO Auto-generated catch block
              	e.printStackTrace();
            }
            //获取当前线程对象的名字 
            String name = Thread.currentThread().getName();
            System.out.println(name+"正在卖:"+ticket--);
        }
	}
}

当然这个线程比较简单,我们只需要记住这个怎么用就可以了…

线程的生命周期

概述
线程的生命周期指的是某一个线程从开始创建直至销毁时, 所经历的全部阶段, 主要分为:
新建
就绪
运行(运行的时候可能会发生阻塞)
死亡

图解

在这里插入图片描述

线程进阶

线程优先级

两种调度方式
1.分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
2.抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
Java使用的是抢占式调度模型

–随机性
假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的
•优先级相关方法

方法名说明
final int getPriority()返回此线程的优先级
final void setPriority(int newPriority)更改此线程的优先级, 线程默认优先级是5;线程优先级的范围是:1-10
 public class ThreadPriority extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}
public class ThreadPriorityDemo {
    public static void main(String[] args) {
        ThreadPriority tp1 = new ThreadPriority();
        ThreadPriority tp2 = new ThreadPriority();
        ThreadPriority tp3 = new ThreadPriority();

        tp1.setName("高铁");
        tp2.setName("飞机");
        tp3.setName("汽车");

        //public final int getPriority():返回此线程的优先级
        System.out.println(tp1.getPriority()); //5
        System.out.println(tp2.getPriority()); //5
        System.out.println(tp3.getPriority()); //5

        //public final void setPriority(int newPriority):更改此线程的优先级
//        tp1.setPriority(10000); //IllegalArgumentException
        System.out.println(Thread.MAX_PRIORITY); //10
        System.out.println(Thread.MIN_PRIORITY); //1
        System.out.println(Thread.NORM_PRIORITY); //5

        //设置正确的优先级
        tp1.setPriority(5);
        tp2.setPriority(10);
        tp3.setPriority(1);

        tp1.start();
        tp2.start();
        tp3.start();
    }
}

线程控制

相关方法

方法名说明
static void sleep(long millis)使当前正在执行的线程停留(暂停执行)指定的毫秒数
void join()等待这个线程死亡
void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,
Java虚拟机将退出
public class ThreadSleep extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(getName()+":" + i );
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//睡眠
public static void main(String[] args) {
        ThreadSleep ts1 = new ThreadSleep();
        ThreadSleep ts2 = new ThreadSleep();
        ThreadSleep ts3 = new ThreadSleep();

        ts1.setName("曹操");
        ts2.setName("刘备");
        ts3.setName("孙权");

        ts1.start();
        ts2.start();
        ts3.start();
    }

 Join演示:
public class ThreadJoin extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}
public class ThreadJoinDemo {
    public static void main(String[] args) {
        ThreadJoin tj1 = new ThreadJoin();
        ThreadJoin tj2 = new ThreadJoin();
        ThreadJoin tj3 = new ThreadJoin();

        tj1.setName("康熙");
        tj2.setName("四阿哥");
        tj3.setName("八阿哥");

        tj1.start();
        try {
            tj1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tj2.start();
        tj3.start();
    }
}

Daemon演示:
public class ThreadDaemon extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}
public class ThreadDaemonDemo {
    public static void main(String[] args) {
        ThreadDaemon td1 = new ThreadDaemon();
        ThreadDaemon td2 = new ThreadDaemon();

        td1.setName("关羽");
        td2.setName("张飞");

        //设置主线程为刘备
        Thread.currentThread().setName("刘备");

        //设置守护线程
        td1.setDaemon(true);
        td2.setDaemon(true);

        td1.start();
        td2.start();

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

Lock锁

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

方法名说明
ReentrantLock()创建一个ReentrantLock的实例

•加锁解锁方法

方法名说明
void lock()获得锁
void unlock()释放锁

代码:


//lock锁
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.lock();
            }
        }
    }
}

//lock锁的测试类
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();
    }
}

线程池

有了多线程技术, 服务器端就可以同时为多个客户端提供服务, 但是线程运行结束后就会死亡, 频繁创建新的线程,导致系统开销过大, 因此Java在JDK5版本中引入了线程池概念. 将多个线程对象存储到容器中, 用的时候从容器中取出, 使用完毕后再放回,这样可以大大减少系统开销

实现方式一(继承)

线程池工厂类:Executors

public static ExecutorService newFixedThreadPool(int nThreads)
方法解释: 线程池工厂类创建固定线程数的方法

线程池接口:ExecutorService

– public Future<?> submit(Runnable task)

方法解释: 使用线程池对象方法submit提交线程执行任务,这是实现多线程的第一种方式
– public void shutdown()
方法解释: 关闭线程池

public static void main(String[] args) {
        //1. 工厂类创建线程池对象,该池中存储10个线程对象
        ExecutorService es = Executors.newFixedThreadPool(10);
        //2. 提交任务
        /*
            上例的线程池对象已经包含了10个线程,线程池提供了submit方法,
            接收不同的线程执行目标类对象,接收到后,线程池会自动分配一个线程执行。
            格式: ex.submit(线程执行目标类对象);
         */
        //这里使用匿名内部类的方式简单演示:

        for (int i = 0; i < 2; i++) {
            es.submit(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 5; i++) {
                        System.out.println(Thread.currentThread().getName() + "HelloWorld" + i);
                    }
                }
            });
        }
        es.shutdown();
console:
pool-1-thread-1HelloWorld0
pool-1-thread-2HelloWorld0
pool-1-thread-2HelloWorld1
pool-1-thread-2HelloWorld2
pool-1-thread-1HelloWorld1
pool-1-thread-1HelloWorld2
pool-1-thread-1HelloWorld3
pool-1-thread-1HelloWorld4
pool-1-thread-2HelloWorld3
pool-1-thread-2HelloWorld4

    }

实现方式二(接口)

JDK5以后, 实现多线程有了一种新的方式, 即: 通过Callable接口实现
好处:
线程执行后,有返回值
可以抛出异常
弊端
该方式只能结合线程池一起使用
涉及到的API
线程池工厂类:Executors

public static ExecutorService newFixedThreadPool(int nThreads)

方法解释: 线程池工厂类创建固定线程数的方法

public static ExecutorService newCachedThreadPool()

方法解释: 创建一个可根据需要创建新线程的线程池
线程池接口:ExecutorService
public Future<?> submit(Runnable task)
方法解释: 使用线程池对象方法submit提交线程执行任务,这是实现多线程的第二种方式
public Future submit(Callable task)
方法解释: 使用线程池对象方法submit提交线程执行任务,这是实现多线程的第三种方式
public void shutdown()
方法解释: 关闭线程池

//Callable 接口
public class MyCallable implements Callable {

    @Override
    public Object call() throws Exception {
        System.out.println("实现Callable接口的方式 实现多线程");
        return "我是返回值";
    }
}

//测试类
public static void main(String[] args) throws ExecutionException, InterruptedException {

        ExecutorService service  = Executors.newFixedThreadPool(5);
        //方式一: 提交任务但没有返回值
        service.submit(new MyCallable());
		System.out.println("=======================");
        //方式二: 提交任务,接受返回值
        Future fu1 = service.submit(new MyCallable());
        System.out.println(fu1.get());
System.out.println("=======================");
        //方式三  提交Runnable接口的子类对象
        Future fu = service.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("Runnable接口的方式实现 线程池,,,");
            }
        });
        System.out.println(fu.get());
        service.shutdown();
    }
console:
=======================
实现Callable接口的方式 实现多线程
实现Callable接口的方式 实现多线程
我是返回值
=======================
Runnable接口的方式实现 线程池,,,
null

细节:
Callable:
• Callable是另外一种形式的线程执行目标类,相当于Runnable接口.
• 其中的call方法相当于Runnable中的run方法。
• 不一样的是,call方法具有返回值,run方法没有返回值。
Future:
• 将方法返回值封装成了对象,结合线程提供了返回结果的更多信息和功能。
• Future获取具体返回值的方法:V get()
• 比如run方法的返回值为void,获取到的值即为null。而返回值的类型是随着submit方法的调用而传入的。

例子(生产者消费者)

生产者和消费者模式概述
图解在这里插入图片描述
概述
生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。
所谓生产者消费者问题,实际上主要是包含了两类线程:
一类是生产者线程用于生产数据
一类是消费者线程用于消费数据
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
在这里插入图片描述
Object类的等待和唤醒方法
为了体现生产和消费过程中的等待和唤醒, Java就提供了几个方法供我们使用, 这几个方法在Object类中.

方法名说明
void wait()导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void notify()唤醒正在等待对象监视器的单个线程(随机唤醒)
void notifyAll()唤醒正在等待对象监视器的所有线程

生产者和消费者案例
案例需求
生产者消费者案例中包含的类:
奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作


生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作


消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作


测试类(BoxDemo):里面有main方法,main方法中的代码步骤如下
①创建奶箱对象,这是共享数据区域
②创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
③创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
④创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
⑤启动线程


//Box类
public class Box {
   //定义一个成员变量,表示第x瓶奶
   private int milk;
   //定义一个成员变量,表示奶箱的状态
   private boolean state = false;
   //提供存储牛奶和获取牛奶的操作
   public synchronized void put(int milk){
       //如果有牛奶,等待消费
       if(state){
           try {
               wait();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
       //如果没有牛奶,就生产牛奶
       this.milk = milk;
       System.out.println("送奶工将"+this.milk+
               "瓶奶放入奶箱");
       //生产完毕之后,修改奶香状态
       state = true;


       //唤醒其他等待的进程
       notifyAll();

   }

   public synchronized void get() {
       //如果没有牛奶,等待生产
       if (!state) {
           try {
               wait();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }

       //如果有牛奶就拿
       System.out.println("用户拿到第" + this.milk + "瓶奶");

       //拿走之后,更改用户状态
       state = false;

       //唤醒其他等待的进程
       notifyAll();
   }
}

//送奶类(重写送奶就行了)
public class Producer implements Runnable {
  private Box b;

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

   @Override
   public void run() {
       for (int i = 1; i <= 10; i++) {
           b.put(i);
       }
   }
}


//拿奶类(只需要重写拿奶的方法就ok)
public class customer implements Runnable {
   private Box b;

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

   @Override
   public void run() {
       while (true){
           b.get();
       }
   }
}

// Demo_milk (在此类中利用thread实现这个Runnable)
public class Demo_milk {
   public static void main(String[] args) {
       //创建奶箱对象,这是共享数据区域
       Box b = new Box();
       //创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
       Producer p = new Producer(b);
       //创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
       customer c = new customer(b);
       //创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
       Thread t1 = new Thread(p);
       Thread t2 = new Thread(c);
       t1.start();
       t2.start();

   }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值