概述

线程与进程

进程

  • 内存中运行的应用程序,每个进程都有一个独立的内存空间(堆,栈)

线程

  • 进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换(让某一个线程休息一下),并发执行,一个进程至少有一个线程。(线程更细致)
  • 线程实际上是在进程基础上的进一步的划分,一个进程启动之后,里面的若干执行路径可以划分成若干个线程

线程与调度

分时调度

  • 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

    抢占式调度

  • 优先让优先级高的线程使用 CPU ,如果现成的优先级相同,那么线程随机性,Java 使用的是抢占式调度

  • CPU 使用抢占式调度模式在多个线程间进行高速的切换,对于 CPU 的一个核心而言,某个时刻只能执行一个线程,而 CPU 的在多个线程之间切换的速度好快,看上去就是在同一时刻在运行。其实,多线程程序并不能提高程序的运行速度,但是可以提高运行效率,让 CPU 的使用率更高。

同步与异行

  • 同步:排队执行,效率低但是安全。
  • 异步:同时执行,效率高但是不安全。

(五个壮汉吃饭,排队吃时间很久,但是不会打架;围一圈吃,效率高,但是容易打架)

并发与并行

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

继承 Thread

每个线程都拥有自己的栈空间,并有一份堆内存

  1. public class Demo1 {
  2. public static void main(String[] args) {
  3. MyThread n = new MyThread();
  4. n.start(); //并发 抢占式调度 不确定的
  5. for(int i=0; i<10; i++){
  6. System.out.println("上山打老虎"+i);
  7. }
  8. //上山打老虎0
  9. //一二三四五0
  10. //上山打老虎1
  11. //一二三四五1
  12. //上山打老虎2
  13. //一二三四五2
  14. //上山打老虎3
  15. //一二三四五3
  16. //上山打老虎4
  17. //一二三四五4
  18. //上山打老虎5
  19. //一二三四五5
  20. //一二三四五6
  21. //一二三四五7
  22. //一二三四五8
  23. //一二三四五9
  24. //上山打老虎6
  25. //上山打老虎7
  26. //上山打老虎8
  27. //上山打老虎9
  28. }
  29. }
  30. public class MyThread extends Thread{
  31. /**
  32. * run 方法就是线程要执行的任务方法
  33. * */
  34. @Override
  35. public void run() {
  36. //这里的代码就是一条新的执行路径
  37. //这个执行路径的触发方式,并不是调用 run 方法,而是通过 Thread 对象的 start() 来启动任务
  38. for(int i=0; i<10; i++){
  39. System.out.println("一二三四五"+i);
  40. }
  41. }
  42. }

也可以通过匿名内部类来实现

实现 Runnable

与继承 Thread 相比,有如下几点优势

  • 通过创建任务,然后给线程分配 的方式来实现多线程,更适合多个线程同时执行相同人物的情况
  • 可以避免单继承所带来的局限性
  • 任务与线程本身是分离的,提高了程序大的健壮性
  • 后续学习的线程池技术,接受 Runnable 类型的任务,不接受 Thread 类型的线程 ```java public class Demo1 { public static void main(String[] args) {
    1. //1. 创建一个任务对象
    2. MyRunnable r = new MyRunnable();
    3. //2. 创建一个线程,并且为它分配任务
    4. Thread t = new Thread(r);
    5. //3. 执行这个进程
    6. t.start();
    7. for(int i=0; i<5; i++){
    8. System.out.println("lalala"+i);
    9. }
    } }

public class MyRunnable implements Runnable { @Override public void run() { for(int i=0; i<5; i++){ System.out.println(“12345”+i); } } }

  1. <a name="ZJgz9"></a>
  2. ## Thread 类
  3. 守护线程依附于用户线程
  4. | `static int` | `[MAX_PRIORITY](#MAX_PRIORITY)` | 线程可以拥有的最大优先级。 |
  5. | --- | --- | --- |
  6. | `static int` | `[MIN_PRIORITY](#MIN_PRIORITY)` | 线程可以拥有的最低优先级。 |
  7. | `static int` | `[NORM_PRIORITY](#NORM_PRIORITY)` | 分配给线程的默认优先级。 |
  8. | `void` | `[setPriority](#setPriority(int))(int newPriority)` | 更改此线程的优先级。 |
  9. | --- | --- | --- |
  10. | `long` | `[getId](#getId())()` | 返回此Thread的标识符。 |
  11. | --- | --- | --- |
  12. | `[String](String.html)` | `[getName](#getName())()` | 返回此线程的名称。 |
  13. | `static void` | `[sleep](#sleep(long))(long millis)` | 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。 |
  14. | --- | --- | --- |
  15. | `static void` | `[sleep](#sleep(long,int))(long millis, int nanos)` | 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性。 |
  16. | `void` | `[setDaemon](#setDaemon(boolean))(boolean on)` | 将此线程标记为 [daemon](#isDaemon())线程或用户线程。 |
  17. | --- | --- | --- |
  18. 如何将一个线程停止,不能用`stop();` 因为不安全,我们要通知进程去死亡,定义一个变量为1做标记 ,如果编程-1就算死亡
  19. <a name="mLkfs"></a>
  20. ### 设置线程名称
  21. ```java
  22. public class Demo3 {
  23. public static void main(String[] args) {
  24. System.out.println(Thread.currentThread().getName());
  25. new Thread(new MRunnable(), "0001").start();
  26. new Thread(new MRunnable()).start();
  27. new Thread(new MRunnable()).start();
  28. }
  29. static class MRunnable implements Runnable{
  30. @Override
  31. public void run() {
  32. System.out.println(Thread.currentThread().getName());//获取当前正在执行的线程
  33. }
  34. }
  35. }

线程休眠

  1. public static void main(String[] args) throws InterruptedException {
  2. for(int i=0; i<5; i++){
  3. System.out.println(i);
  4. Thread.sleep(500,500);//500 ms 加上 500 ns
  5. Thread.sleep(1000);//只是1000 ms
  6. }
  7. }

线程阻塞

所有消耗时间的操作,比如读取文件,接收用户输入

线程的中断

一个线程是一个独立的路径,它是否应该结束应该由它自己决定(人道主义!!!)
所以说不能 stop 相当于掐死一个线程!

  1. public class Demo4 {
  2. public static void main(String[] args) throws InterruptedException {
  3. Thread t1 = new Thread(new WoRunnable());
  4. t1.start();//开始这个线程
  5. for(int i=0; i<5; i++){
  6. System.out.println(Thread.currentThread().getName()+": "+i);
  7. try {
  8. Thread.sleep(1000);
  9. } catch (InterruptedException e) {
  10. }
  11. }
  12. //给线程t1添加中断标记
  13. t1.interrupt();
  14. }
  15. static class WoRunnable implements Runnable{
  16. @Override
  17. public void run() {
  18. for(int i=0; i<10; i++){
  19. System.out.println(Thread.currentThread().getName()+": "+i);
  20. try {
  21. Thread.sleep(1000);
  22. } catch (InterruptedException e) {
  23. //e.printStackTrace();
  24. System.out.println("发现了中断标记,我要自杀了!");
  25. return;//直接就会跳出去了
  26. }
  27. }
  28. }
  29. }
  30. }

守护线程

用户线程:当一个进程不包含任何存活的用户线程时,进程结束
守护线程:守护用户线程的,当最后一个用户线程结束时,它会自己死亡。
直接创建的进程都是用户线程

  1. Thread t1 = new Thread(new WoRunnable());
  2. t1.setDaemon(true);//把 t1 设置为守护线程
  3. t1.start();//开始这个线程

线程安全问题

举例!卖票

  1. public class Demo4 {
  2. public static void main(String[] args) {
  3. Runnable run = new Ticket();//多态
  4. new Thread(run).start();
  5. new Thread(run).start();
  6. new Thread(run).start();
  7. }
  8. static class Ticket implements Runnable{
  9. //票数
  10. private int count = 10;
  11. @Override
  12. public void run() {
  13. while(count>0){
  14. System.out.println("正在准备卖了");
  15. try {
  16. Thread.sleep(800);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. count--;//卖票
  21. System.out.println("出票成功"+count);
  22. }
  23. }
  24. }
  25. }

最后结果有这样:
出票成功0
出票成功-1
出票成功-2
有线程不安全的问题,原因分析:第一个售票线程走到 count = 1 处,还没有来得及进行 count— 时,其他两个的售票进程抢占,会造成count多减掉了两次。

同步代码块

格式:synchronized(锁对象){
}
锁的力度很细
只有同一把锁才会排队!!!

  1. public class Demo4 {
  2. public static void main(String[] args) {
  3. Runnable run = new Ticket();//多态
  4. new Thread(run).start();
  5. new Thread(run).start();
  6. new Thread(run).start();
  7. }
  8. static class Ticket implements Runnable{
  9. //票数
  10. private int count = 10;
  11. private final Object o = new Object();
  12. @Override
  13. public void run() {
  14. while(true){
  15. synchronized (o){//创建了同一把锁 锁的是 if 语句
  16. if(count>0){
  17. System.out.println("正在准备卖了");
  18. try {
  19. Thread.sleep(800);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. count--;//卖票
  24. System.out.println(Thread.currentThread().getName()+"出票成功"+count);
  25. }else{
  26. break;
  27. }
  28. }
  29. //虽然锁住了,但是回首掏太厉害了
  30. }
  31. }
  32. }
  33. }

同步方法

如果一个类中有好多个方法锁,那么一次只能执行一个,就好比有十个试衣间,十个试衣间的外面有一个总的大锁,当有一个人进来使用,会把大门锁住,其他的试衣间都不可用,只能等到这个人使用完毕才会开放。

  1. //票数
  2. private int count = 10;
  3. private final Object o = new Object();
  4. @Override
  5. public void run() {
  6. while(true){
  7. boolean flag = sale();
  8. if(!flag){
  9. break;
  10. }
  11. }
  12. }
  13. public synchronized boolean sale(){
  14. if(count>0){
  15. System.out.println("正在准备卖了");
  16. try {
  17. Thread.sleep(800);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. count--;//卖票
  22. System.out.println(Thread.currentThread().getName()+"出票成功"+count);
  23. return true;
  24. }
  25. return false;
  26. }

显示锁Lock

上面两个方式都是隐式锁
这个方式更好一些,更能体现出面向对象的思想
private Lock l = new ReentrantLock();

  1. static class Ticket implements Runnable{
  2. //票数
  3. private int count = 10;
  4. //显示锁 l
  5. private Lock l = new ReentrantLock();
  6. @Override
  7. public void run() {
  8. while(true){
  9. l.lock();//锁上
  10. if(count>0){
  11. System.out.println("正在准备卖了");
  12. try {
  13. Thread.sleep(800);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. count--;//卖票
  18. System.out.println(Thread.currentThread().getName()+"出票成功"+count);
  19. }else{
  20. break;
  21. }
  22. l.unlock();//解锁
  23. }
  24. }
  25. }

公平锁和非公平锁

公平锁

先来先到,排队的流程
private Lock l = new ReentrantLock(true);
传一个 true 就是公平锁

非公平锁

抢!!!

线程死锁

小明想去小红家睡觉,但是他得等她出来他才能去;小红想去小明家睡觉,但是她也等小明出来,所以现在造成了一种尴尬的局面,他俩等对方先行动,然后都饿死了。

根源上,在任何有可能产生锁的方法里不要调用另外一个方法,让锁产生

多线程通信问题

生产者与消费者问题
厨师正在做菜,此时服务员在睡觉;厨师炒完菜了,叫醒服务员去送菜,厨师睡觉;服务员送完菜回来了,叫醒厨师去做菜,服务员睡着……

  1. public class Demo5 {
  2. public static void main(String[] args) {
  3. Food f = new Food();
  4. new Cook(f).start();
  5. new Waiter(f).start();
  6. }
  7. static class Cook extends Thread{
  8. private Food f;
  9. public Cook(Food f) {
  10. this.f = f;
  11. }
  12. @Override
  13. public void run() {
  14. for(int i=0; i<100; i++){
  15. if(i%2==0){
  16. f.setNaT("橄榄菜", "辣辣");
  17. }else{
  18. f.setNaT("煎饼", "甜辣");
  19. }
  20. }
  21. }
  22. }
  23. static class Waiter extends Thread{
  24. private Food f;
  25. public Waiter(Food f) {
  26. this.f = f;
  27. }
  28. @Override
  29. public void run() {
  30. for(int i=0; i<100; i++){
  31. try {
  32. Thread.sleep(100);
  33. } catch (InterruptedException e) {
  34. e.printStackTrace();
  35. }
  36. f.getNaT();
  37. }
  38. }
  39. }
  40. static class Food{
  41. private String name;
  42. private String taste;
  43. public void getNaT() {
  44. System.out.println("服务员端走的菜是"+name+", 口味是"+taste);
  45. }
  46. public void setNaT(String name, String taste) {
  47. this.name = name;
  48. try {
  49. Thread.sleep(100);
  50. } catch (InterruptedException e) {
  51. e.printStackTrace();
  52. }
  53. this.taste = taste;
  54. }
  55. }
  56. }

结果中会出现
服务员端走的菜是橄榄菜, 口味是甜辣
服务员端走的菜是煎饼, 口味是辣辣
服务员端走的菜是煎饼, 口味是甜辣
服务员端走的菜是煎饼, 口味是辣辣
是因为在厨师做菜的过程中,服务员就把菜端走了(厨师做菜时间有点长),配合的不是很好….

  1. public synchronized void getNaT() {
  2. System.out.println("服务员端走的菜是"+name+", 口味是"+taste);
  3. }
  4. public synchronized void setNaT(String name, String taste) {
  5. this.name = name;
  6. try {
  7. Thread.sleep(100);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. this.taste = taste;
  12. }

这样解决???
不可以! 有一个人会回首掏,一直他妈的做菜或者端盘子
怎么做呢,做到唤醒,睡觉,就行了,配合的好一点!

  1. static class Food{
  2. private String name;
  3. private String taste;
  4. //true 表示可以做饭
  5. boolean flag = true;
  6. public synchronized void getNaT() {
  7. if(!flag){
  8. System.out.println("服务员端走的菜是"+name+", 口味是"+taste);
  9. flag = true;//让厨师去做饭
  10. }
  11. this.notifyAll();//唤醒所有进程
  12. try {
  13. this.wait();//睡觉
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. public synchronized void setNaT(String name, String taste) {
  19. if (flag){
  20. this.name = name;
  21. try {
  22. Thread.sleep(100);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. this.taste = taste;
  27. flag = false;//不会出现两次生产
  28. this.notifyAll();//唤醒所有进程
  29. try {
  30. this.wait();//厨师睡觉
  31. } catch (InterruptedException e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. }
  36. }

线程的状态

  • 线程状态。线程可以处于以下状态之一:
    • NEW
      刚被创建,尚未启动的线程处于此状态。
    • RUNNABLE
      在Java虚拟机中执行的线程处于此状态。
    • BLOCKED
      被阻塞等待监视器锁定的线程处于此状态。
    • WAITING
      无限期等待另一个线程执行特定操作的线程处于此状态。
    • TIMED_WAITING
      正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。
    • TERMINATED
      已退出的线程处于此状态。
  • 线程在给定时间点只能处于一种状态。 这些状态是虚拟机状态,不反映任何操作系统线程状态。

带返回值的线程 Callable

  1. 编写类实现 Callable 接口,实现 call 方法
  2. 创建 FutureTask对象,并传入第一部编写的 Callable 类对象

Callable c = new MyCallable();
FutureTask task = new FutureTask<>(c);

  1. 通过 Thread 启动线程。

线程池

是一个容纳线程的容器,防止频繁的创建线程浪费时间

  • 降低资源消耗
  • 提高响应速度
  • 提高线程的可管理性

缓存线程池

(长度无限制)任务加入后的执行流程

  1. 判断线程池是否存在空闲线程
  2. 存在则使用
  3. 不存在,则创建线程,并且放入线程池中,然后使用

    1. public class Demo7 {
    2. public static void main(String[] args) {
    3. ExecutorService service = Executors.newCachedThreadPool();
    4. //指挥线程池执行新的任务
    5. service.execute(new Runnable() {
    6. @Override
    7. public void run() {
    8. System.out.println(Thread.currentThread().getName()+"一二三四五");
    9. }
    10. });
    11. service.execute(new Runnable() {
    12. @Override
    13. public void run() {
    14. System.out.println(Thread.currentThread().getName()+"一二三四五");
    15. }
    16. });
    17. service.execute(new Runnable() {
    18. @Override
    19. public void run() {
    20. System.out.println(Thread.currentThread().getName()+"一二三四五");
    21. }
    22. });
    23. try {
    24. Thread.sleep(1000);
    25. } catch (InterruptedException e) {
    26. e.printStackTrace();
    27. }
    28. service.execute(new Runnable() {
    29. @Override
    30. public void run() {
    31. System.out.println(Thread.currentThread().getName()+"一二三四五");
    32. }
    33. });
    34. //pool-1-thread-2一二三四五
    35. //pool-1-thread-1一二三四五
    36. //pool-1-thread-3一二三四五
    37. //pool-1-thread-3一二三四五
    38. }
    39. }

    定长线程池

(长度是指定的数值)任务加入后的执行流程

  • 判断线程池是否存在空闲线程
  • 存在则使用
  • 不存在空闲线程并且线程池未满的情况下,则创建线程,并且放入线程池中,然后使用
  • 不存在空闲线程并且线程池已满的情况下,则等待线程池出现空闲线程 ```java public class Demo7 { public static void main(String[] args) {
    1. ExecutorService service = Executors.newFixedThreadPool(2);
    2. service.execute(new Runnable() {
    3. @Override
    4. public void run() {
    5. System.out.println(Thread.currentThread().getName()+"一二三四无");
    6. try {
    7. Thread.sleep(3000);
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. }
    12. });
    13. service.execute(new Runnable() {
    14. @Override
    15. public void run() {
    16. System.out.println(Thread.currentThread().getName()+"一二三四无");
    17. try {
    18. Thread.sleep(3000);
    19. } catch (InterruptedException e) {
    20. e.printStackTrace();
    21. }
    22. }
    23. });
    24. service.execute(new Runnable() {
    25. @Override
    26. public void run() {
    27. System.out.println(Thread.currentThread().getName()+"一二三四无");
    28. }
    29. });
    30. //pool-1-thread-1一二三四无
    31. //pool-1-thread-2一二三四无
    32. //等了三秒钟
    33. //pool-1-thread-2一二三四无
    } }
  1. <a name="aiC6K"></a>
  2. ### 单线程线程池
  3. - 判断线程池是否存在空闲线程
  4. - 存在则使用
  5. - 不空闲则等待池中的某个线程空闲后使用
  6. <a name="psXew"></a>
  7. ### 周期定长线程池
  8. ```java
  9. public class Demo7 {
  10. public static void main(String[] args) {
  11. ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
  12. service.scheduleAtFixedRate(new Runnable() {
  13. @Override
  14. public void run() {
  15. System.out.println("cnm");
  16. }
  17. }, 3,1, TimeUnit.SECONDS);
  18. }
  19. }

Lambda 表达式

函数式编程