Java 多线程

  • 多线程

    在讨论多线程之前,让我们讨论线程。线程是进程的轻量级最小部分,可以与同一进程的其他部分(其他线程)同时运行。线程是独立的,因为它们都具有独立的执行路径,这就是一个线程中发生异常的原因,它不会影响其他线程的执行。进程的所有线程共享公共内存。同时执行多个线程的过程称为多线程
    让我们总结一下要点:
    1.多线程的主要目的是提供程序的两个或更多部分的同时执行,以最大程度地利用CPU时间。多线程程序包含两个或多个可以同时运行的部分。程序的每个这样的部分称为线程
    2.线程是轻量级子进程,它们共享公共内存空间。在多线程环境中,受益于多线程的程序会最大利用的CPU时间,以便将空闲时间保持为最小(别让CPU闲着无事,蛋疼)。
    3.线程可以处于以下状态之一:
    • NEW – 尚未启动的线程处于此状态。
    • RUNNABLE – 在Java虚拟机中执行的线程处于这种状态。
    • BLOCKED – 等待监视器锁定而被阻塞的线程处于此状态。
    • WAITING – 无限期地等待另一个线程执行特定操作的线程处于此状态。
    • TIMED_WAITING – 正在等待另一个线程执行操作的线程最多达到指定的等待时间,该线程处于此状态。
    • TERMINATED – 退出的线程处于此状态。
    在给定的时间点,线程只能处于一种状态。
    线程的生命周期如下图所示:
  • 多任务,多线程,多进程,并行处理

    让我们举个例子来更好地理解这些概念:
    此类实现Serializable接口,这意味着可以对其进行序列化。此类中的所有字段都可以在转换为字节流之后写入文件中,除非那些字段声明为transient。在下面的示例中,我们有两个临时字段,这些字段将不参与序列化。
    如果您不熟悉Java,可能会在这些术语之间感到困惑,因为在我们讨论多线程时,它们经常被使用。 让我们简单地谈谈它们。
    • 多任务:能够同时执行多个任务的能力称为多任务。
    • 多线程:我们已经讨论过了。 它是同时执行多个线程的过程。 多线程也称为基于线程的多任务。
    • 多进程:与多任务处理相同,但是在多处理中涉及多个CPU。 另一方面,一个CPU参与多任务处理。
    • 并行处理:它是指在单个计算机系统中使用多个CPU。
  • 用Java创建线程

    这是使用Java创建线程的两种方法:
    1. 通过继承Thread类。
    2. 通过实现Runnable接口。
    在开始创建线程的程序(代码)之前,让我们看一下Thread类的这些方法。 在下面的示例中,我们很少使用这些方法。
    • getName():用于获取线程的名称
    • getPriority():获取线程的优先级
    • isAlive():确定线程是否仍在运行
    • join():等待线程终止
    • run():线程的入口点
    • sleep():将线程暂停一段时间
    • start():通过调用其run()方法启动线程
    方法1:通过继承Thread类创建线程
    class Count extends Thread {
            Count() {
                    super("my extending thread");
                    System.out.println("我的线程建立了:" + this);
                    start();
            }
    
            public void run() {
                    try {
                            for (int i = 0; i < 10; i++) {
                                    System.out.println("打印统计 " + i);
                                    Thread.sleep(1000);
                            }
                    } catch (InterruptedException e) {
                            System.out.println("我的线程中断了");
                    }
                    System.out.println("我的线程完成了");
            }
    }
    
    class ExtendingExample {
            public static void main(String args[]) {
                    Count cnt = new Count();
                    try {
                            while (cnt.isAlive()) {
                                    System.out.println("主线程将保持活动状态,直到子线程处于活动状态");
                                    Thread.sleep(1500);
                            }
                    } catch (InterruptedException e) {
                            System.out.println("主线程中断");
                    }
                    System.out.println("主线程的运行结束");
            }
    }
    输出如下:
    我的线程建立了:Thread[my extending thread,5,main]
    主线程将保持活动状态,直到子线程处于活动状态
    打印统计 0
    打印统计 1
    主线程将保持活动状态,直到子线程处于活动状态
    打印统计 2
    主线程将保持活动状态,直到子线程处于活动状态
    打印统计 3
    打印统计 4
    主线程将保持活动状态,直到子线程处于活动状态
    打印统计 5
    主线程将保持活动状态,直到子线程处于活动状态
    打印统计 6
    打印统计 7
    主线程将保持活动状态,直到子线程处于活动状态
    打印统计 8
    主线程将保持活动状态,直到子线程处于活动状态
    打印统计 9
    我的线程完成了
    主线程的运行结束
    提示:多运行几次,每次输出的结果不一定相同,CPU内部在这循环十次中切换线程的点有可能是不同的。
    方法2:通过实现Runnable接口创建线程
    class MultithreadingDemo implements Runnable{  
      public void run(){  
        System.out.println("My thread is in running state.");  
      }   
      public static void main(String args[]){  
         MultithreadingDemo obj=new MultithreadingDemo();  
         Thread tobj = new Thread(obj);  
         tobj.start();  
     }  
    }
    下面来看一个复杂点的例子,观察该程序的输出,并尝试了解该程序中正在发生的事情。 如果您了解每种线程方法的用法,那么理解此示例就不会遇到任何问题。
    class Count implements Runnable
    {
       Thread mythread ;
       Count()
       { 
          mythread = new Thread(this, "my runnable thread");
          System.out.println("我的线程建立" + mythread);
          mythread.start();
       }
       public void run()
       {
          try
          {
            for (int i=0 ;i<10;i++)
            {
              System.out.println("打印统计: " + i);
              Thread.sleep(1000);
            }
         }
         catch(InterruptedException e)
         {
            System.out.println("我的线程中断");
         }
         System.out.println("我的线程运行完成" );
       }
    }
    class RunnableExample
    {
        public static void main(String args[])
        {
           Count cnt = new Count();
           try
           {
              while(cnt.mythread.isAlive())
              {
                System.out.println("主线程将保持活动状态,直到子线程处于活动状态"); 
                Thread.sleep(1500);
              }
           }
           catch(InterruptedException e)
           {
              System.out.println("主线程中断");
           }
           System.out.println("主线程完成" );
        }
    }
    输出:
    我的线程建立Thread[my runnable thread,5,main]
    主线程将保持活动状态,直到子线程处于活动状态
    打印统计: 0
    打印统计: 1
    主线程将保持活动状态,直到子线程处于活动状态
    打印统计: 2
    主线程将保持活动状态,直到子线程处于活动状态
    打印统计: 3
    打印统计: 4
    主线程将保持活动状态,直到子线程处于活动状态
    打印统计: 5
    打印统计: 6
    主线程将保持活动状态,直到子线程处于活动状态
    打印统计: 7
    主线程将保持活动状态,直到子线程处于活动状态
    打印统计: 8
    主线程将保持活动状态,直到子线程处于活动状态
    打印统计: 9
    我的线程运行完成
    主线程完成
    提示:多运行几次,每次输出的结果不一定相同,CPU内部在这循环十次中切换线程的点有可能是不同的。
  • 线程优先级

    • 线程优先级是整数,它决定相对于其他线程应如何对待一个线程。
    • 线程优先级决定何时从一个正在运行的线程切换到另一线程,此过程称为上下文切换
    • 线程可以自愿释放控制权,并且把控制权交给已准备好运行的最高优先级线程。
    • 不管低优先级线程在做什么,都可以由高优先级线程抢占该线程。每当更高优先级的线程要运行时,它都会运行。
    • 为了设置线程的优先级,使用ThreadClass类的setPriority()方法。
    • 在发生在整数定义优先级的,我们可以使用MIN_PRIORITYNORM_PRIORITYMAX_PRIORITY
  • 方法:isAlive()和join()

    • 在所有实际情况下,主线程应最后完成,否则从主线程派生的其他线程也应完成。
    • 要知道线程是否已完成,我们可以调用isAlive()线程,如果线程未完成,则返回true。
    • 通过使用join()方法来实现此目的的另一种方法,当从父线程调用此方法时,该方法会使父线程等待直到子线程终止。
    • 这些方法在Thread类中定义。
  • 同步化

    • 多线程将异步行为引入程序。如果一个线程正在写入某些数据,那么另一个线程可能正在读取同一数据。这可能会导致不一致。
    • 当两个或多个线程需要访问共享资源时,应采用某种方式一次仅将一个线程使用该资源。实现此目的的过程称为同步。
    • 为了实现同步行为,java具有同步方法。一旦线程在同步方法中,则其他任何线程都无法在同一对象上调用任何其他同步方法。然后,所有其他线程等待,直到第一个线程从同步块中出来。
    • 当我们要同步对不是为多线程访问设计的类的对象的访问,并且我们无法使用需要同步访问的方法的代码时,在这种情况下,我们无法将同步添加到适当的方法中。在Java中,我们有解决方案,可以通过以下方式将对此类定义的方法的调用(需要同步)放入同步块中。
    Synchronized(object)
    {
        // statement to be synchronized
    }
    
  • 线程间通讯

    我们几乎没有方法可以通过java线程相互通信。这些方法是wait()notify()notifyAll()。所有这些方法只能从Synchronized方法中调用。
    1. 要了解同步,Java具有监视器的概念。可以将监视器视为只能容纳一个线程的盒子。线程进入监视器后,所有其他线程必须等待,直到该线程退出监视器。
    2. wait()告诉调用线程放弃监视器并进入睡眠状态,直到其他某个线程进入同一监视器并进行调用notify()
    3. notify()唤醒wait()在同一对象上调用的第一个线程。
    4. notifyAll()唤醒同一对象上所有调用wait()的线程。优先级最高的线程将首先运行。