在说多线程之前,首先要清楚为啥要提出多线程,这就要明白线程和进程间的区别了。
线程和进程间的区别
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行,相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,然而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。多个线程共享内存,从而极大地提高了程序的运行效率。
Java线程的生命周期
Java线程的生命周期分为如下几种:
新建:创建线程对象
就绪:拥有执行资格,但是没有执行权
运行:有执行资格且有执行权
阻塞:没有执行资格和执行权,但是可以被激活到就绪
死亡:线程对象变为垃圾,等待被回收。
如何创建Java线程
在Java中,可以通过继承Thread类或者实现Runnable接口来定义一个线程类,于此同时,要重写run方法。当然在继承Thread的时候由于Thread有run方法的默认实现,所以不重写就是执行父类Thread的run方法,实现Runnable必须要重写run方法。另外,之所以定义run方法,是为了申明只有run方法中的代码才会被线程执行,下面就是最简单的定义线程类的例子:
public class MyThread extends Thread { @Override public void run() { System.out.println("test Thread!"); } }
public class RunnableTest implements Runnable { @Override public void run() { System.out.println("Test Runnable"); } }
两类线程的启动方式有点区别,如下代码所示:
public class ThreadTest { public static void main(String args[]) { MyThread myThread = new MyThread(); myThread.start(); MyRunnable myRunnable = new MyRunnable(); new Thread(myRunnable).start(); } }
可以看出,实现Runnable的线程类的启动方式是包在Thread的构造方法中,然后还是调用Thread的start方法启动。
如何选择那种实现方式
在实际定义线程类时,到底选那种,这个时候就需要对比下两者的区别了。Thread的是一个类,其中该类实现了Runnable接口,从类和接口的区别可以看出,继承Thread类就会有单继承的局限;另外,从启动线程来看,继承Thread的类需要每次都new一个对象然后启动,而同一个实现Runnable的线程类可以包在多个Thread构造方法中去启动多线程,这样利于线程间的数据共享。总结起来就是:
1. Runnable能避免点继承的局限,一个类可以继承多个接口。
2. Runnable适合于资源的共享
结论是:开发中一般都多用实现Runnable这种方式,第一避免单继承局限,第二利于数据共享。
线程构造方法和重要方法的介绍
由于Runnable是一个接口,并且该接口定义了一个run方法,且该run方法都会被重写,所以只讲解Thread类的构造方法和重要方法,首先来看构造方法:
Thread() 分配新的 Thread 对象。 Thread(Runnable target) 分配新的 Thread 对象。 Thread(Runnable target, String name) 分配新的 Thread 对象。 Thread(String name) 分配新的 Thread 对象。 Thread(ThreadGroup group, Runnable target) 分配新的 Thread 对象。 Thread(ThreadGroup group, Runnable target, String name) 分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,并作为 group 所引用的线程组的一员。 Thread(ThreadGroup group, Runnable target, String name, long stackSize) 分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,作为 group 所引用的线程组的一员,并具有指定的堆栈大小。 Thread(ThreadGroup group, String name) 分配新的 Thread 对象。
从上述构造方法可以看出,可以通过构造方法设置Thread对象的名字,同时还能设置线程对象的线程组和堆栈大小等。下面是几个重要的方法:
static Thread currentThread() 返回对当前正在执行的线程对象的引用。 long getId() 返回该线程的标识符。 String getName() 返回该线程的名称。 int getPriority() 返回线程的优先级。 Thread.State getState() 返回该线程的状态。 ThreadGroup getThreadGroup() 返回该线程所属的线程组。 void interrupt() 中断线程。 static boolean interrupted() 测试当前线程是否已经中断。 boolean isAlive() 测试线程是否处于活动状态。 boolean isDaemon() 测试该线程是否为守护线程。 boolean isInterrupted() 测试线程是否已经中断。 void join() 等待该线程终止。 void join(long millis) 等待该线程终止的时间最长为 millis 毫秒。 void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 void setName(String name) 改变线程名称,使之与参数 name 相同。 void setPriority(int newPriority) 更改线程的优先级。 static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
- currentThread,getName,getID 方法
下面来测试每个方法,在前面谈线程和进程的关系中谈到一个线程可以启动和撤销另外的线程。在启动两种类型的线程中,说明还有一个背后的线程在启动这两个线程,那这个线程是谁呢?我们可以通过以上的前3个方法得知一二:
public class ThreadTest { public static void main(String args[]) { String threadName = Thread.currentThread().getName(); long threadID = Thread.currentThread().getId(); System.out.println(threadID+ ": " + threadName); } } //1: main