zoukankan      html  css  js  c++  java
  • java多线程

    一、多线程概述

      一个进程中至少有一个线程,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。

    不能没一个问题都使用多线程,能使用单线程解决的问题就不要使用多线程解决。

    使用多线程的弊端:

      一旦开启了多个程序,电脑就会变卡,原因就是每个程序被CPU处理的几率变小了。最明显的例子就是如果在复制文件的时候如果开启了多个其他程序,则复制文件所需要的时间就会明显变长。

      使用多核CPU可以解决一部分问题,它们的瓶颈就是内存。

      在学习多线程之前我们写的Java程序也是多线程的,只不过除了主线程之外的其他线程是虚拟机隐式调用的。最明显的例子就是垃圾回收机制。

      Object类中有一个方法名为finalize,该方法由垃圾回收器自动调用,不需要程序员维护

      需要注意的是,堆中产生垃圾之后并不会立刻被垃圾回收器回收,而是经过一段时间后才会被回收。这么做的原因就是如果立即回收,就会和主方法争夺CPU的执行权,影响程序执行效率,这在垃圾产生较为频繁的程序中尤其明显。

    演示垃圾回收机制:

    public class Demo {
        public int data;
        public Demo(int data)
        {
            this.data=data;
        }
        public void finalize()
        {
            System.out.println(this.data);
        }
    
    }
    
    public class Main {
        public static void main(String args[])
        {
            new Demo(1);
            new Demo(2);
            new Demo(3);
            new Demo(4);
            System.gc();
            System.out.println("Hello,World!");
        }
    }

    使用System.gc()方法可以告知垃圾回收器已经产生垃圾了,但是垃圾回收器可能不会立即启动。

    我们使用多线程的目的就是让程序并发执行两段或者两段以上代码。

    二、线程创建的方式一:继承Thread类

      Thread类位于lang包下,而且不是抽象类,因此使用起来很方便。创建线程的目的是为了开启一条执行路径,去运行指定的代码并和其他代码实现同时运行。Thread类用于描述线程,而线程是需要任务的,这个任务由run方法体现。也就是说run方法就是封装自定义线程任务的函数。

      使用Thread类创建线程的步骤:

        1.定义一个类继承Thread类。

        2.覆盖Thread类的run方法

        3.创建Thread子类对象。

        4.调用start方法启动线程。

    Thread类中有两个方法:getName方法用于获取线程名,currentThread用于获取当前线程。前者是非静态类,后者是静态类。

    代码演示:

    class Demo extends Thread
    {
        public void run()
        {
            for(int i=1;i<=3;i++)
            {
                System.out.println("Thread_Name="+Thread.currentThread().getName());
            }
        }
    }
    public class Test
    {
        public static void main(String args[])
        {
            new Demo().start();
            new Demo().start();
            for(int i=1;i<=3;i++)
            {
                System.out.println("Thread_Name="+Thread.currentThread().getName());
            }
        }
    }

    应当注意,虽然main方法已经结束,但是仍然打印出了Thread-0,是因为每个线程在内存中都会开辟一个单独的栈,栈与栈之间互不影响。

    三、线程的几种状态。 

     线程状态类型
    1. 新建状态(New):新创建了一个线程对象。
    2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
    3. 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
    4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
    (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
    (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
    5. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。


    线程状态图

    四、创建线程的第二种方式:实现Runnable接口

    Runnable接口中只有一个方法run,它和Thread类结合可以实现创建新线程的功能。

    使用Runnable接口创建新线程的步骤是:

      1).覆盖接口的run方法,将线程的任务代码封装到run方法中。
      2).通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread累的构造函数进行传递。
          为什么?因为线程的任务都封装在Runnable接口的run方法中。所以要在线程对象创建时就必须明确要运行的任务。
                    也就是说,如果你不传递,则线程对象调用自己的run(new Thread())方法,如果你传递,则线程对象调用传递的run(new Thread(target))方法。
      3).调用线程对象的start方法开启线程。

    class Demo implements Runnable
    {
        public void run()
        {
            for(int i=1;i<=3;i++)
            {
                System.out.println("Thread_Name="+Thread.currentThread().getName());
            }
        }
    }
    public class Main
    {
        public static void main(String args[])
        {
            new Thread(new Demo()).start();
            new Thread(new Demo()).start();
            for(int i=1;i<=3;i++)
            {
                System.out.println("Thread_Name="+Thread.currentThread().getName());
            }
        }
    }

    使用Runnable方式的好处是什么?

      1.将线程的任务从线程的子类中分离出来,进行了单独的封装。
          按照面向对象的思想将任务封装成对象。
      2.避免了java单继承的局限性。

    所以实际开发中大多使用实现Runnable接口的方式创建线程,而不是继承Thread类,这符合少用继承、多用组合原则。

     五、综合案例:通过买票系统比较两种方式的异同

    1.使用Thread的方式:

    class Ticket extends Thread
    {
        public static int sum=10;
        public void run()
        {
            while(true)
            {
                if(sum>0)
                {
                    System.out.println(Thread.currentThread().getName()+":"+sum--);
                }
            }
        }
    
    }
    public class Demo01
    {
        public static void main(String args[])
        {
            Ticket t0=new Ticket();
            Ticket t1=new Ticket();
            Ticket t2=new Ticket();
            Ticket t3=new Ticket();
            t0.start();
            t1.start();
            t2.start();
            t3.start();
    
        }
    }

    2.使用Runnable方式

    class Ticket implements Runnable
    {
        public int sum=10;
        public void run()
        {
            while(true)
            {
                if(sum>0)
                {
                    System.out.println(Thread.currentThread().getName()+":"+sum--);
                }
            }
        }
    }
    public class Demo
    {
        public static void main(String args[])
        {
            Ticket t=new Ticket();
            new Thread(t).start();
            new Thread(t).start();
            new Thread(t).start();
            new Thread(t).start();
        }
    }

    虽然两种方法都可以达到目的,但是后者并没有采用静态成员变量的方式,具有更大的灵活性。

  • 相关阅读:
    JavaEE——SpringMVC(11)--拦截器
    JavaEE——SpringMVC(10)--文件上传 CommonsMultipartResovler
    codeforces 460A Vasya and Socks 解题报告
    hdu 1541 Stars 解题报告
    hdu 1166 敌兵布阵 解题报告
    poj 2771 Guardian of Decency 解题报告
    hdu 1514 Free Candies 解题报告
    poj 3020 Antenna Placement 解题报告
    BestCoder5 1001 Poor Hanamichi(hdu 4956) 解题报告
    poj 1325 Machine Schedule 解题报告
  • 原文地址:https://www.cnblogs.com/zxqstrong/p/5292863.html
Copyright © 2011-2022 走看看