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,反之!

原文地址:https://www.cnblogs.com/yefengyu/p/4853689.html