2018/12/7 Java语言程序设计-多线程 教师:段鹏飞
1、进程和线程的区别 2、Thread和Runnable的区别 3、能够使用Thread或Runable创建线程 4、如何进行线程同步 2018/12/7 知识点 1、进程和线程的区别 2、Thread和Runnable的区别 3、能够使用Thread或Runable创建线程 4、如何进行线程同步
1958年,麦卡锡提出问题:A输入一个命令,需要10秒,电脑只需要1秒,电脑大部分时间在等待... 多用户多进程
多核时代的多进程
界面卡死
1、进程与线程的概念 2、Java中多线程的编程 3、线程的同步与死锁 主要内容 继承Thread类与使用Runnable接口
计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。 进程与线程的区别 计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。
假定工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,其他车间都必须停工。背后的含义就是,单个CPU一次只能运行一个任务。
进程 进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。
调度策略 Java的调度方法 进程的调度 时间片 抢占式:高优先级的进程抢占CPU 同优先级线程组成先进先出队列,使用时间片策略 对高优先级,使用优先调度的抢占式策略 1 2
一个车间里,可以有很多工人。他们协同完成一个任务。
线程就好比车间里的工人,一个进程可以包括多个线程。
线程共享 车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。
多线程
可是,每间房间的大小不同,有些房间最多只能容纳一个人,比如厕所。里面有人的时候,其他人就不能进去了。这代表一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。
一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫"互斥锁"(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。
还有些房间,可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。这好比某些内存区域,只能供给固定数目的线程使用。
这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做"信号量"(Semaphore),用来保证多个线程不会互相冲突。 不难看出,mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。
程序 进程 进程的特点 进程和线程 程序是一段静态的代码,它是应用程序执行的蓝本 进程是指一种正在运行的程序,有自己的地址空间 动态性 并发性 独立性
进程和线程 线程的定义 进程内部的一个执行单元,它是程序中一个单一的顺序控制流程。如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程
进程与多线程
进程与线程 什么时候用进程,什么时候用线程? ——多线程是十字路口 多进程是立交桥。
多线程的优势 多线程使系统空转时间减少,提高CPU利用率 进程间不能共享内存,但线程之间共享内存非常容易 使用多线程实现多任务并发比多进程的效率高
线程要经历创建、就绪、运行、阻塞和死亡等5个状态 ,称为生命周期。 线程的生命周期 线程要经历创建、就绪、运行、阻塞和死亡等5个状态 ,称为生命周期。
2 线程的使用方法 1、创建Thread子类构造线程 2、实现Runnable接口构造线程
两种方法来创建线程 线程的创建和启动 继承Java.lang.Thread类,并覆盖run() 方法 实现Java.lang.Runnable接口,并实现run() 方法 class MyThread extends Thread { public void run( ) { /* 覆盖该方法*/ } class MyThread implements Runnable{ public void run( ) { /* 实现该方法*/ }
启动线程 线程的创建和启动 新建的线程不会自动开始运行,必须通过start( )方法启动 启动继承Thread的线程 启动实现Runnable接口的线程 MyThread t = new MyThread (); t.start(); MyThread mt = new MyThread (); Thread t = new Thread(mt); t.start();
继承Java.lang.Thread类 线程的创建和启动 public class ThreadDemo1 { public static void main(String args[]) { MyThread1 t = new MyThread1(); t.start(); while (true) { System.out.println("兔子领先了,别骄傲"); } class MyThread1 extends Thread { public void run() { System.out.println("乌龟领先了,加油"); Java程序启动时,会立刻创建主线程,main就是在这个线程上运行。当不再产生新线程时,程序是单线程的 演示示例:创建和启动多线程
实现Java.lang.Runnable接口 线程的创建和启动 实现Java.lang.Runnable接口 public class ThreadDemo2 { public static void main(String args[]) { MyThread2 mt = new MyThread2(); Thread t = new Thread(mt); t.start(); while (true) { System.out.println("兔子领先了,加油"); } class MyThread2 implements Runnable { public void run() { System.out.println("乌龟超过了,再接再厉"); 演示示例:创建和启动多线程
两种线程创建方式的比较 线程的创建和启动 继承Thread类方式的多线程 实现Runnable接口方式的多线程 优势:编写简单 劣势:无法继承其它父类 实现Runnable接口方式的多线程 优势:可以继承其它类,多线程可共享同一个Thread对象 劣势:编程方式稍微复杂,如果需要访问当前线程,需要调用Thread.currentThread()方法
线程的创建与启动 创建Thread子类构造线程 (1)创建一个Thread类的子类; (2)在子类中重新定义自己的run()方法,这个中包含了线程要实现的操作; (3)用关键字new 创建一个线程对象; (4)调用start()方法启动线程。
3、构造线程:Thread(Runnable对象名); 4、启动线程:线程对象.start( );
Thread类的常用方法 线程的创建和启动 方 法 功 能 static Thread currentThread() 得到当前线程 final String getName( ) 返回线程的名称 final void setName(String name) 将线程的名称设置为由name指定的名称 void start( ) 调用run( )方法启动线程,开始线程的执行 void run( ) 存放线程体代码
新生 可运行 阻塞 死亡 线程的状态 使用new关键字创建一个线程后,尚未调用其start方法之前 调用线程对象的start方法之后 这个状态当中,线程对象可能正在运行,也可能等待运行 阻塞 一种“不可运行”的状态,在得到一个特定的事件之后会返回到可运行状态 死亡 线程的run方法运行完毕或者在运行中出现未捕获的异常时
线程同步的必要性
使用多线程进行开发,让两个用户同时操作一个银行账户。每次取款100元,取款前先检查余额是否足够。如果不够,放弃取款 线程同步的必要性 使用多线程进行开发,让两个用户同时操作一个银行账户。每次取款100元,取款前先检查余额是否足够。如果不够,放弃取款 创建银行账户类Account 创建取款线程TestAccount 创建测试类TestWithdrawal,让两个用户同时取款
当多个线程访问同一个数据时,容易出现线程安全问题。需要让线程同步,保证数据安全 线程同步的必要性 当多个线程访问同一个数据时,容易出现线程安全问题。需要让线程同步,保证数据安全 线程同步 当两个或两个以上线程访问同一资源时,需要某种方式来确保资源在某一时刻只被一个线程使用 线程同步的实现方案 同步代码块 同步方法
在Java中,每个对象有一个“互斥锁”,该锁可用来保证在同一时刻只能有一个线程访问该对象。 锁的使用过程(当一个线程要操作一个对象时) 线程的同步与互斥 对象互斥锁 在Java中,每个对象有一个“互斥锁”,该锁可用来保证在同一时刻只能有一个线程访问该对象。 锁的使用过程(当一个线程要操作一个对象时) 准备 加锁 对象是否 已加锁 进入 临界区 执行操作 解锁 否 是
线程同步的实现 可以是任何存在的对象 synchronized(obj){ //此处代码为同步代码块 } 同步的代码内容
线程同步的实现
线程同步的实现 访问修饰符 synchronized 返回类型 方法名{ }
死锁 线程同步的好处 线程同步的缺点 死锁 解决了线程安全问题 性能下降 会带来死锁 当两个线程相互等待对方释放“锁”时就会发生死锁 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续 多线程编程时应该注意避免死锁的发生
练习——线程同步 需求说明: 使用线程同步实现两个用户同时安全操作一个银行账户
生产者和消费者问题 线程间通信 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
线程的同步与互斥 线程间需协调与通讯:生产者/消费者问题 进队 出队 生产者 消费者
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件 线程间通信的必要性 这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件 对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费 对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费 在生产者消费者问题中,仅有synchronized是不够的 synchronized可阻止并发更新同一个共享资源,实现了同步 synchronized不能用来实现不同线程之间的消息传递(通信)
Java提供了3个方法解决线程之间的通信问题 线程间通信的必要性 Java提供了3个方法解决线程之间的通信问题 方法名 作 用 final void wait() 表示线程一直等待,直到其它线程通知 void wait(long timeout) 线程等待指定毫秒参数的时间 final void wait(long timeout,int nanos) 线程等待指定毫秒、微妙的时间 final void notify() 唤醒一个处于等待状态的线程 final void notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先运行 均是java.lang.Object类的方法 都只能在同步方法或者同步代码块中使用,否则会抛出异常
定义产品类 线程间通信的实现 class SharedData{ private char c; private boolean isProduced = false; // 信号量 public synchronized void putShareChar(char c) { // 如果产品还未消费,则生产者等待 if (isProduced) { try{ System.out.println("消费者还未消费,因此生产者停止生产"); wait(); // 生产者等待 } catch (InterruptedException e) {e.printStackTrace(); } } this.c = c; isProduced = true; // 标记已经生产 notify(); // 通知消费者已经生产,可以消费 System.out.println("生产了产品" + c + " 通知消费者消费..."); public synchronized char getShareChar() { // 如果产品还未生产,则消费者等待 if (!isProduced){ try{ System.out.println("生产者还未生产,因此消费者停止消费"); wait(); // 消费者等待 } catch (InterruptedException e) {e.printStackTrace();} isProduced = false; // 标记已经消费 notify(); // 通知需要生产 System.out.println("消费者消费了产品" + c + " 通知生产者生产..."); return this.c; 线程间通信的实现 定义产品类
定义生产者线程类和消费者线程类 线程间通信的实现 //生产者线程 class Producer extends Thread { private SharedData s; Producer(SharedData s){ this.s = s; } public void run(){ for (char ch = 'A'; ch <= 'D'; ch++){ try{ Thread.sleep((int) (Math.random() * 3000)); } catch (InterruptedException e) { e.printStackTrace(); s.putShareChar(ch); // 将产品放入仓库 //消费者线程 class Consumer extends Thread { private SharedData s; Consumer(SharedData s){ this.s = s; } public void run(){ char ch; do { try { Thread.sleep((int)(Math.random()*3000)); } catch (InterruptedException e) { e.printStackTrace(); ch = s.getShareChar(); // 从仓库中取出产品 } while (ch != 'D');
线程间通信的实现3-3 定义测试类 //测试类 class CommunicationDemo{ public static void main(String[] args){ //共享同一个共享资源 SharedData s = new SharedData(); //消费者线程 new Consumer(s).start(); //生产者线程 new Producer(s).start(); } 演示示例:使用多线程实现生产者消费者问题
Java中实现线程通信的三个方法的作用是什么? 总结 进程和线程有什么联系和区别? 创建线程的两种方式分别是什么? 如何实现线程同步? Java中实现线程通信的三个方法的作用是什么?
2018/12/7 谢谢