Java JDK1.8(十) - 多线程入门之融会贯通

时间:2024-03-27 09:08:57

为什么会有线程的出现?

      荐阅:https://blog.csdn.net/Su_Levi_Wei/article/details/80629737

 

在上面这篇文章中,可以发现,刚开始时,电脑是单个CPU,随着科技的发展,CPU的性能也提升了,如果CPU只能运行一个应用的话,这样对CPU的性能太浪费了。

于是科学家们就在想,能不能同时运行多个应用,把这些应用隔开来,并且根据各自的使用情况使用对应额度的CPU资源。

很快的,概念就出来了,首先出来的这个概念是进程。

进程出来了,循环又开始了,CPU性能跟着摩尔定律发展了,性能越来越猛,于是线程的概念也出来了,对应的技术也出来了。

 

什么是线程?

      线程这个玩意听起来有点玄乎,作为开发人员,往往知道是什么,但是却无法解释出来,究竟线程是什么?

      还是老规矩,从生活中看下这玩意究竟是怎么样存在的。

      用手机下载应用程序,例如微信,在下载到手机后,这个东西是以文件的形式存储在手机的磁盘上的,这个东西是死。

      点击手机中的微信这个应用程序时,这个程序就活了,会帮助自动查找手机通讯录的人,会查找好友,会查找哪些好友发了朋友圈,这个时候是以进程的形式存在手机中。

      在使用微信时,会发现可以同时和多个人聊天,但是在打电话时,也只能和一个聊天,其实微信是对当前正在聊天的多个人都建立了一条电话线,就是线程。

 

区分

      程序:保存在计算机中的指令代码,以文件形式存储在磁盘上(下载微信)。

      进程:运行在计算机中的程序,分配了内存空间,操作系统进行调度(打开微信)。

      线程:运行在进程中的执行单元,可以获得CPU执行权,分配相应的资源(与某人聊天),一个进程中至少有一个线程。

      注:如果同时有多个线程在运行自己的任务,叫多线程。

 

优缺点

      优点:可以同时处理多个任务,提高了CPU的利用率,但并不能提升速度。

      缺点:多线程同时操作,必然需要一个专门维护和管理多线程的工作,并且付出额外的资源;线程切换会带来CPU资源损耗;会引发线程安全问题;会出现线程死锁的问题。

      注:操作系统能同时运行多个程序(进程),实际上是由于CPU分时机制(时间片轮法、短作业优先、先进先出)的作用,使得每个程序都能循环获得自己的CPU时间片,但由于CPU的轮换速度(运行速度)非常快,使得所有程序好像是在同时运行一样。

 

Java的多线程入门

      Java这个语言本身就内置了多线程,每个Java应用至少有一个线程,这就是主线程,由JVM创建并调用java应用程序的main方法。

JVM还会创建一些其他的线程,不过这些线程是不可见的,如在垃圾回收机制上也运行了一个线程,对象终止或其他JVM处理任务相关的线程。

      java.lang.Object.finalize():对象被垃圾回收线程回收时,会主动调用这个方法。

      System.gc():手动的运行垃圾回收器。

public class GcTest {

    public static void main(String[] args) throws Throwable {

         //一个对象如果使用完毕,则只要符合垃圾回收机制的规则,就会执行回收

         new TestGCA();

        

         //运行垃圾回收器

         System.gc();

         new TestGCA();

        

         System.out.println("主线程结束");

    }

}

class TestGCA {

    @Override

    protected void finalize() throws Throwable {

         System.out.println(this + " - 回收");

    }

}

 

创建方式

public class Create {

    public static void main(String[] args) throws Throwable {

        

         //方式一:启动线程要新建一个Thread对象

         Way01 way01 = new Way01();

         Thread thread = new Thread(way01);

         thread.start();

        

         //方式二:启动线程直接启动

         Way02 way02 = new Way02();

         way02.start();

        

         //方式三:匿名内部类

         Thread thread2 = new Thread(new Runnable() {

             @Override

             public void run() {

                  for (int i = 0; i < 100; i++) {

                      System.out.println("Way03" + i);

                  }

             }

         });

    }

}

/**

 * 方式一、实现Runnable接口

 */

class Way01 implements Runnable {

   

    /*

     * 要同步运行的业务逻辑

     * @overridden @see java.lang.Runnable#run()

     */

    @Override

    public void run() {

         for (int i = 0; i < 100; i++) {

             System.out.println("Way01" + i);

         }

    }

   

}

/**

 * 方式二、直接继承Thread

 * @see java.lang.Thread 会发现Thread类也是实现了Runnable接口。

 */

class Way02 extends Thread {

   

    /*

     * 要同步运行的业务逻辑

     * @overridden @see java.lang.Thread#run()

     */

    @Override

    public void run() {

         for (int i = 0; i < 100; i++) {

             System.out.println("Way02" + i);

         }

    }

}

 

      Runnable接口中实现的run()方法是一个纯粹的方法,没有包含其他的东西,也符合面向对象的分离设计思想,保持程序风格的一致性。

      实现Runnable接口可以解决Java单一继承的不足,如果继承了Thread类,就不能继承其他类了。

      继承Thread类的话,可以直接操纵线程类中的方法,简单。

public class J03Example {

    public static void main(String[] args) {

         //兔子线程

         Thread thread = new Thread(new Rabbit(),"兔子");

        

         //乌龟线程

         Thread thread2 = new Thread(new Runnable() {

             @Override

             public void run() {

                  new Tortoise().climb();

             }

         },"乌龟");

        

         System.out.println("--------------:开跑");

         thread.start();

         thread2.start();

 

         System.out.println("主线程(Main)运行结束");

 

    }

}

/**

 * 兔子

 */

class Rabbit implements Runnable {

 

    @Override

    public void run() {

         for (int i = 0; i < 10; i++) {

 

             String threadName = Thread.currentThread().getName();

            

             if(i == 6) {

                  try {

                      System.out.println(threadName + " 开始睡一觉");

                      Thread.sleep(100);

                  } catch (InterruptedException e) {

                      e.printStackTrace();

                  }

             }

 

             System.out.println(threadName + "跑了 " + i + " ");

            

             try {

                  Thread.sleep(100);

             } catch (InterruptedException e) {

                  e.printStackTrace();

             }

         }

    }

   

}

/**

 * 乌龟

 */

class Tortoise {

    /**

     * 定义一个方法

     */

    public void climb() {

         for (int i = 0; i < 10; i++) {

             String threadName = Thread.currentThread().getName();

             System.out.println(threadName + "跑了 " + i + " ");

            

             try {

                  Thread.sleep(100);

             } catch (InterruptedException e) {

                  e.printStackTrace();

             }

         }

    }

}

 

      如果主线程已经结束了,但其他线程还在运行,则整个进程并没有结束,等待所有线程都运行完成了,则整个进程结束,程序结束。

      注:同一个线程不能调用start()方法两次,会抛出java.lang.IllegalThreadStateException异常(非法线程状态异常)

 

线程优先级

public class J04Priority {

    public static void main(String[] args) {

         Thread t1 = new Thread(new Runnable() {

             @Override

             public void run() {

                  new PriorityA().test();

             }

         },"t1");

        

         Thread t2 = new Thread(new Runnable() {

             @Override

             public void run() {

                  new PriorityA().test();

             }

         },"t2");

        

         Thread t3 = new Thread(new Runnable() {

             @Override

             public void run() {

                  new PriorityA().test();

             }

         },"t3");

        

         //线程优先级:一个线程比其他线程的优先级更高的话,更容易获得CPU的执行权。

         //取值范围:1-10,最低1,最高是10,默认是5

        

         //Thread提供了三个常量MAX_PRIORITY(10)MIN_PRIORITY(1)NORM_PRIORITY(5)

         t1.setPriority(Thread.MAX_PRIORITY);

        

         t1.start();

         t2.start();

         t3.start();

         System.out.println("主线程(Main)运行结束");

    }

}

class PriorityA {

    public void test() {

         for (int i = 0; i < 8; i++) {

             try {

                  Thread.sleep(1000);

             } catch (InterruptedException e) {

                  e.printStackTrace();

             }

 

             System.out.println(Thread.currentThread().getName() + " " + i);

         }

    }

}

 

生命周期

Java JDK1.8(十) - 多线程入门之融会贯通

      1、启动线程,线程进入就绪状态。

      2、线程开始抢夺CPU执行权。

      3、获得CPU执行权,开始执行线程逻辑。

      4、可能在执行过程中,需要让别的线程执行,当前线程进入阻塞,别的线程执行完了,则继续执行当前线程。

      5、执行完毕。

 

public class TicketMain {

    public static void main(String[] args) {

         Thread t1 = new Thread(new Ticket(),"窗口1");

         Thread t2 = new Thread(new Ticket(),"窗口2");

         Thread t3 = new Thread(new Ticket(),"窗口3");

         t1.start();

         t2.start();

         t3.start();

        

    }

}

/**

 *

 */

class Ticket implements Runnable {

   

    /**

     * 线程共享变量,static修饰的在内存中只有一份JVM内部是线程安全的

     */

    static int num = 100;

   

    @Override

    public void run() {

         while (num > 0) {

             System.out.println(Thread.currentThread().getName() + " - 卖出 " + num + "号座");

             num--;

         }

         System.out.println(Thread.currentThread().getName() + "票已售空");

    }

}

 

      线程启动使用父类的start()方法

      如果线程对象之间调用run()方法,JVM是不会当做线程来运行的,会认为是普通的方法调用。

      线程启动只能有一次,即不能多次调用start()方法,否则会抛出异常。

      可以之间创建Thread类的对象并启动该线程,但是如果没有重写run()方法,什么也不执行。

      主线程出现异常,从JVM中结束,但其他线程并没有结束,所有线程都执行完毕了,JVM都会结束。

      线程的任务都是封装在Runnable接口子类对象的run()方法中,所以要在线程对象创建时,就明确要运行的任务。