多线程
- 程序、进程、线程:
- 程序Program:指令集,静态概念。
- 进程Process:操作系统,调度程序,动态概念。
- 进程是程序的一次动态执行过程,占用特定的地址空间
- 每个进程都是独立的,由三部分组成:cpu,data,code
- 一个程序就是一个进程。
- 线程Thread:在进程内,多条执行路径。(不同的执行路径)
- 一个进程可以拥有多个并行的(concurrent)线程。
- 一个进程中的线程共享相同的内存单元/内存地址空间->可以访问相同的变量和对象,而且它们从同一个堆中分配对象->通信、数据交换、同步操作。
- 会造成并发的问题。
- 线程和进程的区别:
- 进程:作为资源分配的单位
- 线程:调度和执行的单位
- 包含关系:
- 没有线程的进程,可以看做单线程的,如果一个进程内部拥有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的。
- 线程的基本概念
- 线程是一个程序内部的顺序控制流。
- 线程和进程的区别
- 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销。
- 线程可以看成时轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。
- 多进程: 在操作系统中能同时运行多个任务(程序)
- 多线程: 在同一应用程序中有多个顺序流同时执行
- Java的线程是通过java.lang.Thread类来实现的。
- 扩充
- 一个进程是一个正运行的应用程序的实例 。它由两个部分组成:一个是操作系统用来管理这个进程的内核对象。另一个是这个进程拥有的地址空间。从执行角度方面看,一个进程由一个或多个线程组成。
- 一个线程是一个执行单元,它控制CPU执行进程中某一段代码段。
- 一个线程可以访问这个进程中所有的地址空间和资源。
- 一个进程最少包括一个线程来执行代码,这个线程又叫做主线程。
- 线程除了能够访问进程的资源外,每个线程还拥有自己的栈
- 线程的创建
- 第一种方式
- 继承Thread类,重写run方法。
- 第二种
- 实现Runnable接口,重写run方法。
- 第一种方式
- 第二种方式代码示例:
-
public class Demo2 { public static void main(String[] args) { //1、创建真是角色 Programme p1 = new Programme(); //2、创建代理角色+真实角色引用 Thread thread = new Thread(p1); //3、代理角色调用start方法开启线程 thread.start(); for (int i = 0; i < 2000; i++) { System.err.println("一边查文档"); } } } /** * @author Johnny * @category 实现了Runnable接口 */ class Programme implements Runnable { public void run() { for (int i = 0; i < 2000; i++) { System.out.println("一边写代码"); } } }
-
- 实现Runnable接口优点:
- 可以同时实现继承。实现Runnable接口方式要通用一些。
- 避免单继承。
- 方便共享资源,同一份资源,多个代理访问。
- 线程的五个状态
- 新生状态:
- 用new关键字和Thread类或其子类建立一个线程对象后,访问线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable)
- 就绪状态:
- 处于就绪状态的线程已经具备了运行条件,但还没有分配到cpu,处于线程就绪队列,等待系统为其分配cpu。等待状态并不是执行状态。
- 运行状态:
- 在运行状态和线程执行自己的run方法中代码,直到调用其他方法而终止或等待某资源而阻塞或完成任务而终止。
- 阻塞状态:
- 处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。
- 终止状态:
- 终止状态是线程生命周期中的最后一个阶段。线程终止的原因有两个,一个是正常运行的线程完成了它的全部工作;另一种是线程被强制执行的终止,如:通过执行stop或destory方法来终止一个线程(前者会产生异常,后者是强制终止不会释放锁,都不推荐)
- 新生状态:
- 线程的常用方法:
- isAlive()
- 判断线程是否处于活动状态,即线程是否还未终止。
- getPriority()
- 获得线程的优先级数值
- setPriority()
- 设置线程的优先级数值
- Thread.sleep()
- 将当前线程睡眠指定毫秒数
- join()
- 调用某线程的该方法,将当前线程与该线程“合并”,即等待该线程结束,再恢复当前线程的运行。
- yield()
- 让出CPU,当前线程进入就绪队列等待调度。
- wait()
- 当前线程进入对象的wait pool。
- notify()/notifyAll()
- 唤醒对象的wait pool中的一个/所有等待线程。
- static void sleep(long millis)
- 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
- isAlive()
- interrupt
- 中断线程的方法。
- 中断线程同时抛出一个异常:InterruptedException。
- InterruptedException:该异常是编译异常(也称受检查异常)。
- join()
- 实现了“整体插队”的效果,将当前加入的线程执行完毕,然后继续执行外面的线程。
- thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
- 比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
- yield()
- yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
- 结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
- 两种线程模式:
- 协作式:一个线程保留对处理器的控制直到它自己决定放弃
- 速度快、代价低
- 用户编程非常麻烦
- 抢先式:系统可以任意的从线程中夺回对CPU的控制权,再把控制权分给其它的线程 。
- 两次切换之间的时间间隔就叫做时间片
- 效率不如协作式高 ,OS核心必须负责管理线程
- 简化编程,而且使程序更加可靠
- 多数线程的调度是抢先式的。
- 协作式:一个线程保留对处理器的控制直到它自己决定放弃
- synchronized
- 由于同一个进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。
- java语言提供了专门机制以解决这种冲突,有效的避免了同一个数据被多个线程同时访问。
- 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,
- 这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块确保资源安全,线程安全。
- 关键字synchronized 来与对象的互斥锁联系。当某个对象synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
- Web12306线程安全代码:
-
public class Web12306XCAQ implements Runnable{ private int num = 20;//共享资源 private boolean flag = true; @Override public void run() { while(flag){ method2(); } } public synchronized void method1(){ if(num<1){ flag=false; }else{ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } //说出当前线程的名称,谁抢了第?张票 System.out.println( Thread.currentThread().getName()+"抢了第"+num--+"张票"); } } public void method2(){ synchronized (this) { if(num<1){ flag=false; }else{ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } //说出当前线程的名称,谁抢了第?张票 System.out.println( Thread.currentThread().getName()+"抢了第"+num--+"张票"); } } } }
-
- 线程死锁
- 过多的同步容易造成死锁
- 一个线程中锁定了另一个线程需要的对象,而另一个线程锁定了这个线程需要的对象。
- 也就是说:两个线程互相锁定对方所需要的对象。
- synchronized总结
- 无论synchronized关键字加在方法上还是对象上,它取得的锁都是锁在了对象上,而不是把一段代码或函数当作锁,而且同步方法很可能还会被其他线程的对象访问。
- 每个对象只有一个锁(lock)与之相关联。
- 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
- 搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。
- wait() :等待、释放锁
- notify()、notifyAll() :唤醒
- 他们三个都是在同步中使用
- 生产者缴费者模式,解决线程死锁问题(是一个解决方案)
- 该问题的关键就是要保证生产者不会再缓冲区满时加入数据,消费者也不会再缓冲区空时消耗数据。
- 通常常用的方法有信号灯法。
- Wait sleep区别
- 来源不同
- Sleep是Thread提供的方法
- Wait来自Object
- 代码位置不同
- Wait需要写在Synchronize语句块里面
- 是否释放锁定对象
- 调用wait方法,释放锁定该对象
- Sleep时别的线程也不可以访问锁定对象
- 来源不同