一:基本概念

(1)程序:

是为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

(2)进程:

进入内存运行的程序叫做进程(程序都要进入内存中才能运行)。

(3)线程:

线程是进程的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程,一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

(4)简而言之

一个程序运行后至少有一个进程,一个进程中可以包含多个线程。

(5)并发:

指两个或多个事件在同一个时间段内发生。(交替执行)(一个CPU交替执行多个任务)。

(6)并行:

指两个或多个事件在同一时刻发生。(同时发生)(多个CPU同时执行多个任务)。

(6)拓展:

一个Java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然,如果发生异常,会影响主线程。

(7)多线程的优点:

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
  2. 提高计算机系统CPU的利用率。
  3. 改善程序结构。将既长又复杂的进城分为多个线程,独立运行,利于理解和修改。

    (8)何时需要多线程:

  4. 程序需要同时执行两个或多个任务。

  5. 程序需要实现一些需要等待的任务时,如用户输入、文件读写、网络操作、搜索等。
  6. 需要一些后台运行的程序时。

    二:线程的创建和使用(重点)

    Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现。

    (1)Thread类的特性:

    java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类。

    1. 每个线程都是通过某个特定的Thread对象的run()方法来完成操作的经常把run()方法的主体称为线程体。
    2. 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()。

      (2)Thread类中常用的方法:

      1. static Thread currentThread();//静态方法。返回当前正在执行的线程对象的引用。可以先获取到当前正在执行的线程,再使用线程中的方法getName()获取线程的名称。
      2. public static void sleep(long millis);
      3. //使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。毫秒数结束之后,线程继续执行。使用方式:Thread.sleep(毫秒数)。
      4. void start();//启动线程,并执行对象的run()方法。
      5. void run();//线程被调度时执行的操作
      6. String getName();//返回线程的名称
      7. void setName();//设置线程的名称
      8. void yield();//释放当前CPU的执行权(当然,有可能在下一刻再次抢到执行权)
      9. void join();//在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完毕,线程a才结束阻塞状态。
      10. void stop();//强制线程生命周期结束,不推荐使用。(此方法已经过时)
      11. sleep(long millistime);//让当前线程“睡眠”指定的毫秒数。在指定的毫秒时间内,当前线程处于阻塞状态。
      12. boolean isAlive();//判断当前线程是否存活。

      代码案例:

      1. class MyThread extends Thread{
      2. //run();设置线程任务
      3. @Override
      4. public void run() {
      5. for (int i = 0; i < 100; i++) {
      6. if(i % 2 == 0){
      7. try {
      8. //sleep();让当前线程“睡眠”指定的毫秒数
      9. sleep(10);
      10. } catch (InterruptedException e) {
      11. e.printStackTrace();
      12. }
      13. //currentThread();返回当前线程.
      14. //getName();返回线程的名称.
      15. System.out.println(Thread.currentThread().getName()+"---"+i);
      16. }
      17. if(i % 20 == 0){
      18. //释放当前CPU的执行权(当然,有可能在下一刻再次抢到执行权)
      19. yield();
      20. }
      21. }
      22. }
      23. //使用构造器给线程命名
      24. public MyThread(String name){
      25. super(name);
      26. }
      27. }
      28. //Thread类中常用方法测试
      29. public class ThreadMethodTest {
      30. public static void main(String[] args) {
      31. MyThread mt = new MyThread("线程1");
      32. //setName();设置线程的名称
      33. //mt.setName("线程1");
      34. //start();启动线程,并执行对象的run()方法。
      35. mt.start();
      36. //给主线程命名
      37. Thread.currentThread().setName("主线程");
      38. for (int i = 0; i < 100; i++) {
      39. if(i%2==0){
      40. System.out.println(Thread.currentThread().getName()+"---"+i);
      41. }
      42. if (i == 20){
      43. try {
      44. //join();在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完毕,线程a才结束阻塞状态。
      45. mt.join();
      46. } catch (InterruptedException e) {
      47. e.printStackTrace();
      48. }
      49. }
      50. }
      51. //boolean isAlive();判断当前线程是否存活。
      52. System.out.println(mt.isAlive());
      53. }
      54. }

      (3)创建多线程的两种方法:

      A:第一种方法实现步骤:

  7. 创建一个Thread类的子类。

  8. 在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做的事)。
  9. 创建Thread类的子类的对象。
  10. 通过此对象调用Thread类中的start方法,开启新的线程,执行run方法。
    1. start()的两个作用(1.启动当前线程。2.调用当前线程的run()方法。)

案例代码:

  1. //遍历100所有偶数案例
  2. public class ThreadText {
  3. //1.创建一个Thread类的子类。
  4. static class MyThread extends Thread{
  5. //2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做的事)。
  6. @Override
  7. public void run() {
  8. for (int i = 0; i < 100; i++) {
  9. if (i % 2 == 0){
  10. System.out.println(i);
  11. }
  12. }
  13. }
  14. }
  15. public static void main(String[] args) {
  16. //3.创建Thread类的子类的对象。
  17. MyThread thread = new MyThread();
  18. //4.通过此对象调用Thread类中的start方法,开启新的线程,执行run方法。
  19. thread.start();
  20. }
  21. }

多线程创建过程中两个问题的说明:

  1. 不能直接调用run()方法来启动线程,如果这么做,那么它只是调用了run()方法,却没有开启新的线程,run()方法中的任务依旧是在main()主线程中执行的。
  2. 一个线程对象只能start()一次。

    B:第二种方法:(实现Runnable接口)

    java.lang.Runable
    Runnable接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义为一个称为run的无参数方法。
    java.lang.Thread构造方法:
    Thread(Runnable target):分配新的Thread对象。
    Thread(Runnable target,String name):分配新的Thread对象。
    实现步骤:

  3. 创建一个Runnable接口的实现类。

  4. 在实现类中重写Runnable接口的run方法,设置线程任务
  5. 创建一个Runnable接口的实现类对象
  6. 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
  7. 调用Thread类中的strat方法,开启新的线程执行run方法。

代码案例:

  1. //1.创建一个Runnable接口的实现类。
  2. class MyRunnable implements Runnable{
  3. //2.在实现类中重写Runnable接口的run方法,设置线程任务
  4. @Override
  5. public void run() {
  6. for (int i = 0; i < 100; i++) {
  7. System.out.println(Thread.currentThread().getName()+i);
  8. }
  9. }
  10. }
  11. public class RunnableTest {
  12. public static void main(String[] args) {
  13. //3.创建一个Runnable接口的实现类对象
  14. MyRunnable mr = new MyRunnable();
  15. //4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
  16. Thread t = new Thread(mr);
  17. //5.调用Thread类中的strat方法,开启新的线程执行run方法。
  18. t.start();
  19. }
  20. }

两种创建多线程方式的比较:

开发中,优先选择:实现Runnable接口的方式。
原因:

  1. 实现的方式没有类的单继承的局限性
  2. 实现的方式更适合来处理多个线程有共享数据的情况

联系:Thread类也实现了Runnable接口
相同点:两种方式都需要重写run();将线程要执行的逻辑声明在run()中。

拓展:匿名内部类方式实现线程的创建

匿名:没有名字
内部类:写在其他类内部的类
匿名内部类的作用简化代码

  1. 把子类继承父类、重写父类的方法、创建子类对象合成一步完成。
  2. 把实现类接口,重写接口中的方法,创建实现类对象合成一步完成。
  3. 匿名内部类的最终产物:子类/实现类对象,而这个类没有名字。

代码案例

  1. new Thread(){
  2. @Override
  3. public void run() {
  4. //设置线程任务(开启线程要做的事)
  5. }
  6. }.start();

三:线程的优先级问题

(1)线程调度:

分时调度

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

抢占式调度

优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),java使用的为抢占式调度。

(2)线程的优先级等级:(三个常量)

  1. MAX_PRIOPITY:10
  2. MIN_PRIOPITY:1
  3. NORM_PRIOPITY:5

    涉及的方法:

    1. getPriority();//返回线程的优先级
    2. setPriority(int newPriority):改变线程的优先级
    3. //说明:
    4. //线程创建时继承父线程的优先级。
    5. //低优先级只是获得调度的概率低,并非一定是在高优先级之后才被调用。
    **