zoukankan      html  css  js  c++  java
  • java线程详解(一)

    1,相关概念简介

    (1)进程:是一个正在执行的程序。每一个进程执行都有一个执行的顺序,该顺序就是一个执行路径,或者叫一个控制单元。用于分配空间。

    (2)线程:就是进程中一个独立的控制单元,线程在控制着进程的执行,一个进程中至少有一个线程。

         java虚拟机启动的时候会有一个进程java.exe,该进程中至少有一个线程在负责java程序的执行,这个线程运行的代码在main方法中,因此main方法是主线程。在更细节一点,java虚拟机不止一个线程,在启动main方法这个主线程时还有垃圾回收机制,其实这也是多线程。因此java虚拟机也是多线程。

    2,线程的创建和启动

    (1)继承Thread类创建线程

       步骤:

       (a)定义Thread类的子类。

       (b)重写该Thread 类的run()方法。

       (c) 创建一个线程对象。

       (d)调用对象的start()方法启动进程,调用run()方法。

    class Demo extends Thread   //(a)定义Thread类的子类。
    {
        public void run(){   //(b)重写该Thread 类的run()方法。
            for(int i = 0; i < 100; i++){
                System.out.println(i + " demo run!");
            }
            
        }
        public static void main(String[] args)
        {
            Demo d = new Demo();  //(c) 创建一个线程对象。
            d.start();   //(d)调用对象的start()方法启动进程。
            //一下代码进行测试
            for(int i = 0; i < 100; i++){
                System.out.println(i + " main run!");
            }
        }
    }

    测试结果:

          image

          可以看出主线程和我们自定义的线程交替执行。

    注意:

    (a)创建的线程对象不能调用run()方法。如果调用run()方法就和平常的函数调用一样,达不到多线程执行的效果。调用start()方法,不仅开启线程,而且还调用了run()方法。

    (b) 该程序多次运行的结果每次都不同,这是因为多个线程都在争夺cpu的执行权,每一个时刻只有一个线程在运行,cpu在做着快速切换,以达到看上去是同时运行的结果。这就是多线程的一个特点:随机性。谁抢到谁执行,执行多长时间是有cpu决定的。

    (c)为什么要覆盖run()方法?Thead类用于描述线程,该类中的run方法只是存放要运行的代码的位置。

    (d)其实Thread d = new Thread()可以创建一个线程的实例,但是d.start()却是开启Thread类的run()方法,该方法没有做任何定义。

    (2)实现Runnable接口创建线程类

         先看一个问题

    //火车站有16张票,需要从四个窗口卖出,如果按照上面的多线程实现,程序如下
    class Ticket extends Thread   
    {
        private int tick = 16;//票的张数---16
        public void run(){   
            while(true){
                if(tick>0){
                    //此处打印看是哪个窗口卖出的哪张票,这里简单的让票号从1  ---  16
                     System.out.println(Thread.currentThread().getName() + "...sale:" + tick--);    
                }
            }
            
        }
        public static void main(String[] args)
        {
            Ticket t1 = new Ticket();
            Ticket t2 = new Ticket();
            Ticket t3 = new Ticket();
            Ticket t4 = new Ticket();
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
         image

    结果我们可以看出不止卖了16张,其实每个窗口或者说线程都卖出16张票,这是不能忍受的。

    有个方法:private int tick = 16;这句变为:private static int tick = 16。但一般不定义静态,因为它的生命周期太长。

    正式进入第二种创建线程的方法

    //火车站有16张票,需要从四个窗口卖出,如果按照上面的多线程实现,程序如下
    class Ticket implements Runnable   
    {
        private int tick = 16;//票的张数---16
        public void run(){   
            while(true){
                if(tick>0){
                    //此处打印看是哪个窗口卖出的哪张票,这里简单的让票号从1  ---  16
                     System.out.println(Thread.currentThread().getName() + "...sale:" + tick--);    
                }
            }
            
        }
        public static void main(String[] args)
        {
            Ticket t = new Ticket();
    
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
            Thread t3 = new Thread(t);
            Thread t4 = new Thread(t);
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

         

    从这个结果我们可以看出达到了我们的目标。总结步骤:

    (a)定义Runnable接口的实现类,并且重写该接口的run()方法。

    (b)创建Runnable实现类的实例对象,并以此对象来作为Thread类的Target来创建Thread类对象。

           Ticket t = new Ticket(); 该对象t并非是线程对象

           Thread t1 = new Thread(t); 以对象t作为参数而创建的t1才是真正的线程对象

    (c)调用该线程对象t1的方法start()来启动多线程。

         image

        可以在创建Thread对象时为该对象指定一个名字。

    (3)常用方法

           (a)返回对当前正在执行的线程对象的引用。

             public static Thread currentThread() 

            (b)返回该线程对象的的名称。

                   public final String getName()

    (4)注意事项

           (a)继承Thread类的对象可以使用currentThread()方法,也可以使用this关键字。但是实现Runnable的实现类只能通过currentThread()方法获得当前对象。

           (b)Runnable接口方式创建的多个线程可以共享线程类的实例的属性。所以非常适合多个相同的线程来处理同一份资源。(也就是为什么引出这种线程方式的卖票的那个例子)

           (c)Runnable接口实现的方式下,还可继承其他的类。

    3,线程的生命周期

    image

    新建:当程序使用new关键字创建一个线程对象时,该线程就处于新建状态,此时这个对象和其他java对象一样,仅仅有虚拟机为其分配内存,并初始化其初始值,此时的对象没有表现出线程的特征。

    就绪:当新建的线程对象调用start()方法时(图中的1),该线程就处于就绪状态,虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程没有开始执行,只是表示可以执行了,至于什么时候执行,关键看虚拟机里面的调度器的调度。

    运行:当就绪状态的现场获取了cpu的执行权(图中的2)开始执行run方法,则表示该线程处于运行状态。

    阻塞:当线程开始运行时不可能一直处于运行状态,除非他的运行时间非常短,cpu还没有切换就已经运行结束,但这样的情况并不是大多数。一般情况下,cpu所分的时间片很多线程在这个时间内是完成不了的,此时线程运行就要中断,让其他线程获取执行机会。因此当一个运行中的线程发生如下情况时会进入阻塞状态。其实阻塞状态是一种运行中的线程的一种中断状态,此时不能运行,但是线程不能运行还有其他状态,比如说又恢复到就绪状态,下面就来说明。

    (a)运行中的线程进入阻塞状态

         (1)线程调用sleep()方法主动放弃所占用的处理器资源。

         (2)线程调用了一个阻塞式的IO方法,在方法在返回前线程被阻塞。

         (3)线程在等待某个通知。

         (4)线程试图获得一个同步监视器,但是该同步监视器被别的线程所持有。

         (5)程序调用了线程的suspend方法将该线程挂起。这个方法容易导致死锁,已过时。

    (b)运行中的线程进入就绪状态

         (1)并非通过sleep()方法使得线程失去资源

         (2)调用yield()方法就可以使得运行中的程序进入就绪状态。

    (c)阻塞状态的线程进入就绪状态

         (1)阻塞状态的线程只能进入就绪状态,不能直接进入运行状态

         (2)调用sleep()方法经过了指定的时间

         (3)线程调用的阻塞式IO方法已经返回

         (4)线程成功的获得了某个等待的同步监视器

         (5)线程在等待某个通知,而其他线程发出了一个通知

         (6)处于被挂起的线程被调用了resume()方法。这个方法容易导致死锁,已过时。

    死亡:线程结束就是死亡状态。

         (1)正常结束线程

         (2)线程抛出一个未捕获到的Exception或Error。

         (3)使用该线程的stop()方法来终止线程。容易死锁,已过时。   

    注意:

    (a)当新建一个线程对象时,线程处于新建状态,此时如果要是调用了该对象的run()方法,说明此时线程对象已经不是新建状态了,不能再来调用start()方法,只能对处于新建状态的线程对象调用start()方法。但是也不能对新建的线程对象调用两次start()方法。

    (b)不要对一个已经死亡的线程调用start()方法。

    (c)有一个方法可以检测该线程对象是否已经死亡。-----isAlive()方法,当线程处于新建和死亡状态,该方法返回false,反之!

  • 相关阅读:
    HDU 5313 bitset优化背包
    bzoj 2595 斯坦纳树
    COJ 1287 求匹配串在模式串中出现的次数
    HDU 5381 The sum of gcd
    POJ 1739
    HDU 3377 插头dp
    HDU 1693 二进制表示的简单插头dp
    HDU 5353
    URAL 1519 基础插头DP
    UVA 10294 等价类计数
  • 原文地址:https://www.cnblogs.com/yefengyu/p/4853689.html
Copyright © 2011-2022 走看看