JavaSE02_Day01(上)-进程和线程

一、进程和线程

1.1 进程

       Windows操作系统是多任务操作系统,以进程为单位,每个独立执行的程序都称之为进程,也就是正在执行的程序。系统可以分配给每一个进程一段有限的使用CPU的时间,也可以称之为CPU时间片,CPU在这段时间中执行某个进程,然后下一个时间片又跳到另一个进程中去执行,由于CPU转换较快,所以使得每个进程好像是同时执行一样。

eg:电脑启动以后,正在运行的软件,每个软件就是一个进程。如:正在运行的QQ,正在运行的音乐。

1.2 线程

       一个线程是进程中的执行流程,一个进程中可以同时包括多个线程,每个线程也可以得到一小段程序的执行时间,这样一个进程就可以具有多个并发执行的线程。

       单线程:程序代码按照调用顺序一句一句的依次往下执行,如果需要一个进程同时需要多段代码的执行,那就得需要多线程。

       多线程:多个单一顺序执行流程“同时”(并发)执行,造成“感官上同时执行”的效果。作用:改变代码执行方式。

       并发:每个线程实际运行是走走停停的,线程调度程序会将CPU运行时间划分为若干时间片段,并尽可能均匀的分配给每个线程,拿到时间片的线程被CPU执行这段时间。当超时后线程调度程序会再次分配给一个线程一个时间片段,使得CPU执行它,如此反复。由于CPU执行时间在纳秒级别,我们感觉不到切换线程运行的过程,所以微观上走走停停,宏观上感觉一起运行的现象称为并发运行。     

       用途:    

    1、当出现多个代码片段执行顺序有冲突时,希望它们各干各的时就应当放在不同线程“同时”运行。    

    2、一个线程可以运行,但是多个线程可以更快时,可以使用多线程运行。

              多线程改变了代码的执行方式,从原来的单一顺序执行流程变为多个执行流程“同时”执行,可以让多个代码片段的执行互不干扰。线程之间是并发执行的,并非真正意义上的同时运行。

eg:在电脑中一款正在运行的QQ音乐软件,可以一边听着音乐,一边收藏音乐,不同的线程处理不同的操作。

1.3 线程状态

二、线程的创建

创建线程有两种方式
  1:继承Thread并重写run方法
        定义一个线程类,重写run方法,在其中定义线程要执行的任务(希望和其他线程并发执行的任务)
  2:实现Runnable接口,单独定义线程任务

2.1 方式一:继承Thread类,并重写run()方法

  • 优点:代码书写简单,理解简单,利于匿名内部类形式创建。

  • 缺点:线程执行的任务序列比较固定,不够灵活,如果后期需要对这个线程进行更改任务序列较为麻烦,代码耦合度较高。

  •           1.由于java是单继承的,这会导致继承Thread就无法再继承其他类去复用方法;

  •           2.定义线程的同时重写了run方法,这等于将线程的任务定义在了这个线程中,导致线程只能干这件事。重(chong)用性能低。

 package cn.tedu.thread;
 /**
  * 线程创建方式一案例:一种线程只能干一件事,无法再继承其他类去复用方法,重用性能低
  * @author cjn
  *
  */
 public class ThreadDemo01 {
 
     public static void main(String[] args) {
         //创建线程实例
         MyThread1 thread1 = new MyThread1();
         MyThread2 thread2 = new MyThread2();
         
         //调用strat方法开启线程
         thread1.start();
         thread2.start();
     
    }
 
 }
 
 /**
  * 外部类
  */
 class MyThread1 extends Thread{
     /**
      * 重写Thread线程类中的run方法
      *
      */
     @Override
     public void run() {
         //run方法中需要去写明线程需要执行的业务逻辑,专业名词称之为任务序列
         for (int i = 0; i < 50; i++) {
             System.out.println(i + "好好学习");
        }
    }
 }
 
 class MyThread2 extends Thread{
     @Override
     public void run() {
         for (int i = 0; i < 50; i++) {
             System.out.println(i + "天天向上");
        }
    }
 }


注意: 
  启动线程要调用该线程的start方法,而不是run方法!!!
  线程调用完start方法后会纳入到系统的线程调度器程序中被统一管理。线程调度器会分配时间片段给线程,使得CPU执行该线程这段时间,
  用完后线程调度器会再分配一个时间片段给一个线程,如此反复,使得多个线程都有机会执行一会,做到走走停停,并发运行。
  线程第一次被分配到时间后会执行它的run方法开始工作。

2.2 方式二:实现Runnable接口,单独定义线程任务

  • 优点:线程和任务序列分开,减少了代码的耦合度;

  • 缺点:因为没有继承Thread线程类,有关于线程的相关API方法使用较为麻烦。

 package cn.tedu.thread;
 /**
  * 线程创建方式二案例
  * @author cjn
  *
  */
 public class ThreadDemo02 {
 
     public static void main(String[] args) {
         //创建任务序列对象 也可以通过向上造型实现:Runnable runnable = new MyRunnable1();
         MyRunnable runnable = new MyRunnable();
         MyRunnable2 runnable2 = new MyRunnable2();

         //创建线程对象并指派任务
         Thread thread1 = new Thread(runnable);
         Thread thread2 = new Thread(runnable2);

         //启动线程:thread对象调用了Thread类中定义的方法start,里面调用了run方法
         thread1.start();
         thread2.start();
 
    }
 
 }
 /**
  * 线程任务序列
  * Runnable是一个接口,实现该接口,需要实现run方法
  *
  */
 class MyRunnable implements Runnable {
 
     @Override
     public void run() {
         for (int i = 0; i < 50; i++) {
             System.out.println(i + "好好学习");
        }
    }
     
 }
 
 
 class MyRunnable2 implements Runnable {
 
     @Override
     public void run() {
         for (int i = 0; i < 50; i++) {
             System.out.println(i + "天天向上");
        }
         
    }
     
 }
 

2.3 使用匿名内部类的方式创建

 package cn.tedu.thread;
 
 /**
  * 使用匿名内部类的方式进行创建线程
  *
  * @author cjn
  *
  */
 public class ThreadDemo03 {
 
     public static void main(String[] args) {
         /*
          * 匿名内部类的语法: 父类型 引用变量名 = new 父类的构造器{};
          * 匿名内部类的作用: 1.继承父类 2.创建子类的实例
          */
         /*
          * 线程方式一:继承Thread并重写run方法
          */
         Thread thread = new Thread() {
             @Override
             public void run() {
                 for (int i = 0; i < 50; i++) {
                     System.out.println("好好学习");
                }
            }          
        };
      //或 lambda表达式
      Thread thread = new Thread(()->{
        for (int i = 0; i < 20; i++) {
          System.out.println("好好学习");
        }
      });

         /*
          * 线程方式二:实现Runnable接口,单独定义线程任务
          */
         Runnable runnable = new Runnable() {            
             @Override
             public void run() {
                 for (int i = 0; i < 50; i++) {
                     System.out.println(",天天向上");
                }              
            }
        };

      //或 lambda表达式
      Runnable runnable = ()-> {//'Runnable' is abstract; cannot be instantiated
        for (int i = 0; i < 50; i++) {
          System.out.println("天天向上");
        }
      };
         Thread thread2 = new Thread(runnable);
         
         //线程的启动
         thread.start();
         thread2.start();
    }
 }
 

三、线程相关API

3.1 获取当前线程currentThread

       main方法本质上也是一条线程执行的,只不过不是由咱们编程人员进行创建的。

 package cn.tedu.thread;
 /**
  *
  * 获取当前线程
  * currentThread静态方法
  * @author cjn
  *
  */
 public class CurrentThreadDemo {
 
     public static void main(String[] args) {
         Thread thread = Thread.currentThread();
         /*
          * 输出结果:Thread[main,5,main]
          * 括号中第一个内容是线程名
          * 括号中第二个内容是优先级
          * 括号中第三个内容是线程组
          */
         System.out.println("程序执行main方法的线程为:"+thread);
         
         Thread thread2 = new Thread() {
             @Override
             public void run() {
                 Thread thread = Thread.currentThread();
                 System.out.println("程序执行自定义的线程为:"+thread);
            }
        };
         thread2.start();
         
         customThread();
 
    }
     
     public static void customThread() {
         Thread thread = Thread.currentThread();
         System.out.println("程序执行自定义方法中的线程为:"+thread);
         
    }
 
 }

输出结果

  程序执行main方法的线程为:Thread[main,5,main]
  程序执行自定义方法中的线程为:Thread[main,5,main]
  程序执行自定义的线程为:Thread[Thread-0,5,main]

3.2 获取线程信息(会查手册即可)

  • getName:获取线程的名称

  • getId:获取线程的标识符

  • getState:获取线程的状态

  • isAlive:查看线程是否是活跃状态

  • getPriority:获取线程的优先级

  • isDaemon:查看线程是否是守护线程

  • isInterrupted:查看线程是否中断

 package cn.tedu.thread;
 
 import java.lang.Thread.State;
 
 /**
  * 查看线程信息案例
  * @author cjn
  *
  */
 public class ThreadInfoDemo {
 
     public static void main(String[] args) {
         Thread thread = Thread.currentThread();
         System.out.println(thread);
         
         //获取线程的标识符
         long threadId = thread.getId();
         System.out.println("线程id为:" + threadId);
         
         //修改线程的名称
         thread.setName("Thread-1");
         //获取线程的名称
         String threadName = thread.getName();      
         System.out.println("线程name为:" + threadName);
         
         //查看线程是否是活跃状态
         boolean isAlive = thread.isAlive();
         System.out.println("线程是否为活跃状态:" + isAlive);
         
         //查看线程的优先级
         int num = thread.getPriority();
         System.out.println("线程的优先级为:" + num);
         
         //查看线程状态
         State state = thread.getState();
         System.out.println("线程的状态为:" + state);
         
         //查看线程是否是守护线程
         boolean isDaemon = thread.isDaemon();
         System.out.println("线程是否为守护线程:" + isDaemon);
         
         //查看线程是否被中断
         boolean isInterrupted = thread.isInterrupted();
         System.out.println("线程是否被中断:"+ isInterrupted );
         
    }
 
 }

输出结果为:

  当前线程为:Thread[main,5,main]
  线程的名字为:main
  线程修改后的名字为:Thread-1
  线程的标识符为:1
  线程的优先级为:5
  线程的状态为:RUNNABLE
  线程是否为活跃状态:true
  线程是否为守护线程:false
  线程是否被中断:false

3.3 线程的优先级

       线程的优先级总共划分为10个等级,值分别为1~10,1表示优先级最低,10表示优先级最高,因为直接写数值表示优先级代码的可读性并不是很好,源码中也提供了相关的常量用于表示优先级。

 MIN_PRIORITY   //1 最低
 NORM_PRIORITY //5 默认
 MAX_PRIORITY   //10 最高

      设置的优先级越高,只能改善线程获取时间片的几率,并不能通过代码进行干预操作系统对线程的调度。

 package cn.tedu.thread;
 /**
  * 线程优先级案例
  * @author cjn
  *
  */
 public class Thread_priority {
 
     public static void main(String[] args) {
         //创建线程
         Thread thread_min = new Thread() {
             @Override
             public void run() {
                 for (int i = 0; i < 50; i++) {
                     System.out.println("min" + i);
                }
            }
        };
         //设置线程的优先级1
         thread_min.setPriority(Thread.MIN_PRIORITY);
         
         Thread thread_max = new Thread() {
             @Override
             public void run() {
                 for (int i = 0; i < 50; i++) {
                     System.out.println("max" + i);
                }
            }
        };
         //设置线程的优先级10
         thread_max.setPriority(Thread.MAX_PRIORITY);
         
         Thread thread_def = new Thread() {
             @Override
             public void run() {
                 for (int i = 0; i < 50; i++) {
                     System.out.println("def" + i);
                }
            }
        };
         //设置线程的优先级5
         thread_def.setPriority(Thread.NORM_PRIORITY);
         
             
         //启动线程
         thread_min.start();
         thread_max.start();
         thread_def.start();

    }
 }
 

3.4 线程的休眠sleep

       sleep方法是线程类提供的静态方法,可以让当前线程进入阻塞的状态。在sleep方法中可以传递进行休眠的毫秒值,毫秒值过后,线程会重新进入到Runnable状态,等待操作系统分配CPU时间片。

 package cn.tedu.thread;
 /**
  * 线程休眠sleep案例:要先设置线程优先级,然后再启动线程
  * @author cjn
  *
  */
 public class Thread_sleep {
 
     public static void main(String[] args) {
         System.out.println("有10份作业需要进行书写!");
         try {
             System.out.println("休息10秒钟,然后再进行书写作业");
             Thread.sleep(1000 * 10);
             System.out.println("10秒钟结束了,开始书写作业");
        } catch (InterruptedException e) {
             e.printStackTrace();
        } catch (Exception e) {
             e.printStackTrace();
        }
         
         for (int i = 1; i <= 10; i++) {
             System.out.println("第" + i + "份作业已经写完!");
             try {
                 Thread.sleep(1000 * 5);
            } catch (InterruptedException e) {
                 e.printStackTrace();
            } catch (Exception e) {
                 e.printStackTrace();
            }
        }
         
    }
 }
 

输出结果

  有10份作业需要进行书写!
  先休息10秒钟,然后再进行书写作业
  10秒钟结束了,开始书写作业
  第1份作业已经写完!
  第2份作业已经写完!
  第3份作业已经写完!
  第4份作业已经写完!
  第5份作业已经写完!
  第6份作业已经写完!
  第7份作业已经写完!
  第8份作业已经写完!
  第9份作业已经写完!
  第10份作业已经写完!

3.5 线程的唤醒interrupt(慢慢记)

 package cn.tedu.thread;
 /**
  * 线程休眠过程中的唤醒案例演示
  * 如果说一个正在阻塞状态的线程,被唤醒以后
  * 会抛出InterruptedException异常
  *
  * 公主被巫婆下了诅咒沉睡,王子进行唤醒公主
  * @author cjn
  *
  */
 public class Thread_interrupt {
 
     public static void main(String[] args) {
         //创建公主线程
         Thread thread1 = new Thread() {
             @Override
             public void run() {
                 System.out.println("公主被巫婆施了魔法诅咒,进入沉睡阶段!!!");
                 try {
                     Thread.sleep(99999999);
                } catch (InterruptedException e) {
                     System.out.println("王子亲了公主以后,公主就醒来!!!");
                     e.printStackTrace();
                } catch (Exception e) {
                     e.printStackTrace();
                }
                 System.out.println("王子和公主过上了幸福的生活!!!");
            }
        };  
         
         //创建王子线程
         Thread thread2 = new Thread() {
             @Override
             public void run() {
                 System.out.println("王子要开始吻公主了,目的是为了唤醒沉睡的公主");
                 //王子吻了10次公主,然后公主就可以清醒了
                 for (int i = 0; i < 10; i++) {
                     System.out.println("么么哒(づ ̄ 3 ̄)づ");
                     try {
                         Thread.sleep(1000 * 10);
                    } catch (InterruptedException e) {
                         e.printStackTrace();
                    } catch (Exception e) {
                         e.printStackTrace();
                    }
                }
                 System.out.println("王子亲完公主了");
                 /*
                  * 王子唤醒公主
                  * 哪个线程对象需要被唤醒,哪个线程对象就需要自己进行调用interrupt.
                  * interrupt方法调用的位置,一定是唤醒方代码中书写
                  */
                 thread1.interrupt();
            }
        };
                 
         //线程的启动
         thread1.start();
         thread2.start();
 
    }
 }

输出结果

  公主被巫婆施了魔法诅咒,进入沉睡阶段!!!
  王子要开始吻公主了,目的是为了唤醒沉睡的公主!
  么么哒(づ ̄ 3 ̄)づ
  么么哒(づ ̄ 3 ̄)づ
  么么哒(づ ̄ 3 ̄)づ
  么么哒(づ ̄ 3 ̄)づ
  么么哒(づ ̄ 3 ̄)づ
  么么哒(づ ̄ 3 ̄)づ
  么么哒(づ ̄ 3 ̄)づ
  么么哒(づ ̄ 3 ̄)づ
  么么哒(づ ̄ 3 ̄)づ
  么么哒(づ ̄ 3 ̄)づ
  王子亲完公主了
  王子亲了公主以后,公主就醒来!!!
  java.lang.InterruptedException: sleep interrupted
  王子和公主过上了幸福的生活!!!
    at java.lang.Thread.sleep(Native Method)
    at cn.tedu.thread.Thread_interrupt$1.run(Thread_interrupt.java:12)

3.6 守护线程setDaemon(慢慢记)

定义:

       守护线程也可以称之为后台线程,在这里实际上和其他的普通的线程并没有太多的区别,如果需要将一个线程设置为守护线程,需要使用setDaemon(true)。

特点:

  • 如果在程序中前台线程终止了,进程中只存在守护线程的情况,所有的守护线程都需要进行终止。

  • 如果说程序都终止了,也就是进程终止,那么程序中所有的守护线程也需要都终止。

应用场景:

       GC就是运行在一个守护线程上的。

注意事项:

       如果设置一个线程为守护线程,一定要在这个线程启动之前进行设置,否则会报错。

 package cn.tedu.thread;
 /**
  * 守护线程案例演示
  * 业务场景:
  * 1)使用内部类进行创建两个线程,分别为thread1和thread2.
  * 2)thread1线程实现每间隔1秒,在控制台输出字符串"王子开始亲公主"
  * 3)设置线程thread2为守护线程(后台线程),当王子开始亲公主时,
  *   守护线程每间隔1秒钟就输出字符串"狼人开始抢公主"
  * 4)观察当前台线程结束以后,然后查看控制台的详细输出内容,
  *   最后输出一次字符串"狼人开始抢公主"以后后台线程就不再执行了
  * 5)比如测试不设置守护线程,控制台的打印结果
  * @author cjn
  *
  */
 public class Thread_setDaemon {
 
     public static void main(String[] args) {
         //创建前台线程
         Thread thread1 = new Thread() {
             @Override
             public void run() {
                 for (int i = 0; i < 5; i++) {
                     System.out.println("王子开始亲公主!!!");
                     try {
                         Thread.sleep(1000);
                    } catch (InterruptedException e) {
                         e.printStackTrace();
                    } catch (Exception e) {
                         e.printStackTrace();
                    }
                }
                 System.out.println("前台线程的业务逻辑执行完毕!!!");
            }
        };
         
         //创建后台线程
         Thread thread2 = new Thread() {
             @Override
             public void run() {
                 while (true) {
                     System.out.println("狼人开始抢公主");
                     try {
                         Thread.sleep(1000);
                    } catch (InterruptedException e) {
                         e.printStackTrace();
                    } catch (Exception e) {
                         e.printStackTrace();
                    }
                }
            }
        };  
 
         //启动线程前设置thread2为后台线程
         thread2.setDaemon(true);
         
         //启动线程
         thread1.start();
         thread2.start();
         
    }
 }
 

输出结果:

  王子开始亲公主
  狼人开始抢公主
  王子开始亲公主
  狼人开始抢公主
  狼人开始抢公主
  王子开始亲公主
  王子开始亲公主
  狼人开始抢公主
  王子开始亲公主
  狼人开始抢公主
  狼人开始抢公主
  前台线程的业务逻辑执行完毕!

3.7 线程插队join

       当线程调用join方法以后,这个线程也会进入到一个阻塞状态,等待另一条线程的任务序列执行结束以后,然后再进行执行当前这个阻塞状态的线程任务序列。

eg:在浏览网页的过程中,假设浏览的是一个带有图片的网页,在网页加载显示的过程中,往往图片较大,加载的速度会比较慢,当图片没有加载完毕的时候,不能算为网站加载完毕,而是等待网站中的图片加载完毕以后,网站加载完毕,并且可以留意到网站在没有加载完毕的时候,浏览器的加载按钮是一直加载的状态。

       代码中用两条线程进行模拟这个业务场景,一条线程用于进行下载图片,另一条线程用于显示图片,当图片全部都下载完毕以后,再进行显示图片,所以说用于显示的线程必须要等待下载的线程执行完下载的任务序列以后才可以进行显示。

 package cn.tedu.thread;
 /**
  * 插队线程案例演示
  * @author cjn
  *
  */
 public class Thread_join {
  //是否下载成功标志位
     public static boolean isFinish = false;
     
     public static void main(String[] args) {
         //下载图片线程
         Thread download = new Thread() {
             @Override
             public void run() {
                 System.out.println("下载图片线程,开始下载图片中...");
                 for (int i = 1; i < 101; i++) {
                     //打印下载的进度
                     System.out.println("下载进度为:" + i + "%");
                     try {
                         Thread.sleep(100);
                    } catch (InterruptedException e) {
                         e.printStackTrace();
                    } catch (Exception e) {
                         e.printStackTrace();
                    }
                }
                 System.out.println("下载图片线程已经完成图片的下载!!!");
                 isFinish = true;//下载完毕后,设置为true
            }
        };
         
         //显示图片线程
         Thread show = new Thread() {
             @Override
             public void run() {
                 System.out.println("显示图片线程:等待下载图片完成以后再进行显示图片");
                 try {
                     download.join();//插队,必须下载完毕后才能显示
                } catch (InterruptedException e) {
                     e.printStackTrace();
                } catch (Exception e) {
                     e.printStackTrace();
                }
                 System.out.println("显示图片线程:开始显示图片资源");
                 //根据标志位决定是否显示图片
                 if (isFinish) {
                     System.out.println("显示图片线程:显示图片完毕");
                } else {
                     System.out.println("显示图片线程:显示图片失败");
                }
            }
        };
         
         //启动线程
         download.start();
         show.start();
         
    }
 }

输出结果:

下载图片线程:开始下载图片中...
显示图片线程:等待下载图片完成以后再进行显示图片
下载进度为:1%
下载进度为:2%
下载进度为:3%
下载进度为:4%
下载进度为:5%
下载进度为:6%
下载进度为:7%
下载进度为:8%
下载进度为:9%
下载进度为:10%
下载进度为:11%
下载进度为:12%
下载进度为:13%
下载进度为:14%
下载进度为:15%
下载进度为:16%
下载进度为:17%
下载进度为:18%
下载进度为:19%
下载进度为:20%
下载进度为:21%
下载进度为:22%
下载进度为:23%
下载进度为:24%
下载进度为:25%
下载进度为:26%
下载进度为:27%
下载进度为:28%
下载进度为:29%
下载进度为:30%
下载进度为:31%
下载进度为:32%
下载进度为:33%
下载进度为:34%
下载进度为:35%
下载进度为:36%
下载进度为:37%
下载进度为:38%
下载进度为:39%
下载进度为:40%
下载进度为:41%
下载进度为:42%
下载进度为:43%
下载进度为:44%
下载进度为:45%
下载进度为:46%
下载进度为:47%
下载进度为:48%
下载进度为:49%
下载进度为:50%
下载进度为:51%
下载进度为:52%
下载进度为:53%
下载进度为:54%
下载进度为:55%
下载进度为:56%
下载进度为:57%
下载进度为:58%
下载进度为:59%
下载进度为:60%
下载进度为:61%
下载进度为:62%
下载进度为:63%
下载进度为:64%
下载进度为:65%
下载进度为:66%
下载进度为:67%
下载进度为:68%
下载进度为:69%
下载进度为:70%
下载进度为:71%
下载进度为:72%
下载进度为:73%
下载进度为:74%
下载进度为:75%
下载进度为:76%
下载进度为:77%
下载进度为:78%
下载进度为:79%
下载进度为:80%
下载进度为:81%
下载进度为:82%
下载进度为:83%
下载进度为:84%
下载进度为:85%
下载进度为:86%
下载进度为:87%
下载进度为:88%
下载进度为:89%
下载进度为:90%
下载进度为:91%
下载进度为:92%
下载进度为:93%
下载进度为:94%
下载进度为:95%
下载进度为:96%
下载进度为:97%
下载进度为:98%
下载进度为:99%
下载进度为:100%
下载图片进行:已经完成图片的下载!!!
显示图片线程:开始显示图片资源
显示图片线程:显示图片完毕

 
posted @ 2021-06-27 23:42  Coder_Cui  阅读(115)  评论(0编辑  收藏  举报