Java并发:创建线程的两种方法:继承Thread类和实现Runnable接口(一)

【1】了解进程与线程的概念

  • 进程是一个程序在其自身的地址空间中执行一次活动,是资源申请、调度和独立运行的单位。
  • 线程是进程中的一个单一的连续控制流程,一个进程可以包含一个或多个线程。
  • 如果要在一个程序中实现多段代码同时交替运行,就需要产生多个线程,并指定每个线程上要运行的程序代码段,这就是多线程。
  • 当程序运行时,就自动产生了一个线程,主函数main就是在这个线程上运行的,当不再产生新的线程时,程序就是单线程的。例如,在的博客中本文——“JAVA——package语句、class环境变量配置”中的程序就是单线程的。

【2】实现多线程的两种方法

  • 当启动程序时就自动产生了一个线程,主函数main就是在这个线程上运行的。当不再产生新的线程时,程序就是单线程的。
  • 通过查阅JDK文档的Thread类,可以发现创建多线程有两种方法:继承Thread类、实现Runable接口。
    这里写图片描述
    翻译:每个线程都有优先级。优先级较高的线程优先执行具有较低优先级的线程。每个线程可能也可能被标记为守护进程。当代码运行在某个线程中创建一个新的线程对象时,新线程的优先级初始设置为创建线程的优先级,当且仅当创建线程为守护进程时,该线程是一个守护线程。
  • 守护线程与非守护线程的相关内容,可参见网址:
    http://www.cnblogs.com/super-d2/p/3348183.html
  • JAVA中的线程是通过java.lang.Thread类来控制的,一个Thread类的对象代表一个线程,且只能代表一个线程,通过Thread类和它定义的对象,可以获取当前线程对象、获取某一线程的名称,可以实现控制线程暂停一段时间等功能。

  • 第一种创建线程的方法:继承Thread类
    两种方法的示例为:例如,一个计算大于给定值的素数的线程可以写成如下。
    这里写图片描述

  • 方法步骤总结:
    (1)定义一个类(如PrimeThread)继承Thread;
    (2)重写Thread类中的run方法,将需要被多线程执行的代码存储到该run方法当中。
    (3)建立Thread类的子类创建线程对象。
    (4)直接调用子类从Thread类继承的start方法,开启一个线程(调用该线程的run方法)。

  • 第二种创建线程的方法:实现Runable接口

    这里写图片描述

  • Thread类有一个Thread(Runnable target)构造方法,在Runable接口类中只有一个run()方法。当使用Thread(Runnable target)方法创建线程对象时,需要为该方法传递一个实现 Runnable接口的对象,这样创建的线程将调用那个实现了Runnable接口类对象中的run()方法作为其运行代码,而不再是调用Thread类中的run方法了。

  • 方法步骤总结:
    (1)定义一个类(如PrimeRun)实现Runnable接口,覆盖Runnable接口中的run方法,将线程要运行的代码存放在该run方法中;
    (2)通过Thread类建立线程对象,将Runnable接口的子类实例对象作为实际参数传递给Thread类的构造方法。

  • 两种方式区别:

    (1)继承Thread: 线程代码存放Thread子类run方法中,且该run方法被调用。
    (2)实现Runnable:线程代码存在实现了Runnable类接口的对象的run方法中,且该run方法被调用。

  • Thread类中的run方法(函数)
    这里写图片描述

  • 要实现多线程,就得编写一个继承了Thread类的子类,要将一段代码在一个新的线程上运行,该代码应该在一个类的run函数中,并且run函数所在的类是Thread类的子类。从JDK文档可知,Thread类的子类应该覆盖run方法(函数)。

  • 启动一个新的线程,不是直接调用Thread子类的对象的run方法,而是调用Thread子类对象的start方法,start方法是从Thread类中继承的方法,Thread类对象的start方法将产生一个新的线程,并在该线程上运行该Thread类对象中的run方法。根据面向对象的多态性可知,在该线程上实际运行的是我们编写的那个类(Thread的子类)对象中的run方法。
  • 由于线程的代码段在run方法中,那么该方法执行完以后,线程也就相应的结束了,因而可以通过控制run方法中的循环条件来控制线程的终止。这个问题就是后面会讲到的“线程生命的控制”问题。

【2-1】直接继承Thread

  • 首先,为了确保类名的唯一性,在以下代码使用包(package)将类组织起来。
/*代码1*/
package mythread;
class TestThread extends Thread
{
    public void run()
    {
        while(true)
        {
            System.out.println(Thread.currentThread().getName() + " is running");
        }
    }
}
public class ThreadDemo
{
    public static void main(String[] args) 
    {
        new TestThread().start();
        while(true)
        {
            System.out.println("main thread is running");
        }
    }
}
  • Thread.currentThread()静态函数获得代码当前执行时对应的那个线程对象。得到当前线程对象后,调用getName()方法,取出当前线程的名称字符串。

  • 以上代码1中,没有直接调用TestThread类对象的run方法,而是调用了TestThread类对象从Thread类继承来的start方法。

  • 编译、运行(可使用 Ctrl+C 终止程序运行):
 javac -d . ThreadDemo.java
 java mythread.ThreadDemo

这里写图片描述

  • 可以发现,两个while循环处的代码同时交替运行。main函数调用TestThread.start()方法启动了TestThread.run()函数后,main函数不等待TestThread.run()函数返回就继续运行(即main中的while语句继续运行)。TestThread.run()函数一边独自运行,不影响原来的main函数的运行。但是,TestThread.run()函数和main中的while语句是否在同一时刻运行的,众说纷纭,希望大神们指教!
  • 有这么一个说法:多核CPU在同一时刻可以执行多个线程,每个单核CPU每一时刻只能执行一个线程。在单核CPU中,多线程是通过时间片轮换技术执行的,每一时间段被一个线程所独占,只是占用的时间很短,短到我们感觉是在同一时刻。

【2-2】实现Runable接口

/*代码2*/
package myrunnable;
class TestThread implements Runnable
{
    public void run()
    {
        while(true)
        {
            System.out.println(Thread.currentThread().getName()+" is running");
        }
    }
}
public class ThreadDemo1
{
    public static void main(String[] args)
    {
        TestThread tDemo = new TestThread();
        new Thread(tDemo).start();//等价于Thread t = new Thread(tDemo); t.start();

        while(true)
        {
            System.out.println("main thread is running");
        }

    }
}
  • 编译、运行:
javac -d . ThreadDemo1.java
java myrunnable.ThreadDemo1
  • 可以发现,两个while循环处的代码同时交替运行,实现了多线程。
    这里写图片描述

【3】两种方法的对比分析

  • 修改“程序1”:同一个线程对象同时调用start函数四次(启动四个线程)
package mythread;
class TestThread extends Thread
{
    public void run()
    {
        while(true)
        {
            System.out.println(Thread.currentThread().getName() + " is running");
        }
    }
}
public class ThreadDemo
{
    public static void main(String[] args) 
    {
        TestThread t = new TestThread();
        t.start();
        t.start();
        t.start();
        t.start();

        while(true)
        {
            System.out.println("main thread is running");
        }
    }
}
  • 编译、运行,可以发现,一个线程对象只能启动一个线程。本例只是成功的启动了一个线程,无论调用多少遍start()方法,结果都只有一个线程。
    这里写图片描述
  • 修改“程序2”:同一个线程对象同时调用start函数四次(启动四个线程)
package myrunnable;
class TestThread implements Runnable
{
    public void run()
    {
        while(true)
        {
            System.out.println(Thread.currentThread().getName()+" is running");
        }
    }
}
public class ThreadDemo1
{
    public static void main(String[] args)
    {
        TestThread tDemo = new TestThread();
        new Thread(tDemo).start();
        new Thread(tDemo).start();
        new Thread(tDemo).start();
        new Thread(tDemo).start();
    }
}
  • 编译、运行,可以发现,成功的启动了四个线程。
    这里写图片描述

这里写图片描述

【3-1】实现Runnable接口方法实现多线程分析

  • 以张孝祥所编著的《Java就业培训教程》的一个例子为例:某一趟列车有10张坐票,通过4个窗口售票。这样,对于每一张坐票,要保证每一个窗口的售票不能将其重复售出。
/*代码3*/
package myrunnable;
class TestThread implements Runnable
{
    private int Numbe = 10;
    public void run()
    {
        while(true)
        {
            if (Numbe > 0)
            {
                try
                {
                    Thread.sleep(10);
                }
                catch (Exception e)
                {
                    System.out.println(e.getMessage());
                }
                System.out.println(Thread.currentThread().getName() +" is saling ticket "+ Numbe--);
            }
        }
    }
}
public class ThreadDemo1
{
    public static void main(String[] args)
    {
        TestThread tDemo = new TestThread();
        new Thread(tDemo).start();
        new Thread(tDemo).start();
        new Thread(tDemo).start();
        new Thread(tDemo).start();
    }
}
  • 编译、运行。可以发现,四个窗口把10张票给出售了,而且没有出现将某一张票给重复出售。但是,除了原来的10张票,系统还可以多出售序号为-1和0的两张票。这在实际情况下,是绝对不可行的。
    这里写图片描述

  • 这种意外的问题就是“线程安全”问题。那么,要解决以上安全问题,这就涉及到:如何实现线程间的同步问题。


版权声明:本文为jingzi123456789原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。