一、进程
要理解线程,首先要理解“ 进程”。
进程:运行中的应用程序。它有独立的内存空间。
二、线程
线程:是进程中的实体,是被系统独立调度和分配的基本单位。线程本身并不拥有资源,只拥有在运行中必不可少的资源。它可以与同属于一个进程的其他线程共享进程所拥有的全部资源。同一个进程中的多个线程之间可以并发执行。
具体一点就是
A、线程是轻量级的进程。
B、线程没有独立的地址空间(内存空间)。
C、线程是由进程创建的(寄生在进程内部)。
D、一个进程可以拥有多个线程。
E、线程的基本状态(新建,可运行,运行中,阻塞,死亡)
三、线程的5大基本状态
① 新建状态(new)
当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。
② 就绪状态(Runnable)
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
③ 运行状态(Runninng)
当线程获得CPU空间后,它才进入运行状态,开始执行run()方法.
④ 阻塞状态 (Blocked)
线程运行过程中,可能由于各种原因进入阻塞状态:
1>线程通过调用sleep方法进入睡眠状态;
2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
3>线程试图得到一个锁,而该锁正被其他线程持有;
4>线程在等待某个触发条件;
⑤ 死亡状态(Dead)
有两个原因会导致线程死亡:
1) run方法正常退出而自然死亡,
2) 一个未捕获的异常终止了run方法而使线程猝死。
五大状态之间的转换
四、如何使用线程
在Java中,一个类要作为线程类使用有两种方法:
1、继承Thread类,并且重写 run()函数
2、实现Runnable接口,并重写run()函数
下面分别使用两种方法来实现
A、继承Thread类,重写run()函数
/**
* 通过继承Thread的方法,实现每隔1秒 输出hello,world.
* 输出10次之后就不再输出
*
*/
public class Test1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Cat cat = new Cat();
//启动线程 ,会调用run()函数
cat.start();
}
}
class Cat extends Thread
{
//重写Override
public void run(){
int times =0;
while(true)
{
try {
//休眠1秒,后再次启动
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
times++;
System.out.println("Hello, world! "+times);
if(times==10){
break;
}
}
}
}
B、实现Runnable接口,重写run()函数
/**
* 通过实现runnable接口,每隔1秒输出hello, world
* 输出10次之后自动退出
*
*/
public class Test2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建dog对象
Dog dog = new Dog();
//注意 启动时无法直接调用start 函数
//先创建Thread函数 ,再启动
Thread t = new Thread(dog);
t.start();
}
}
class Dog implements Runnable{
@Override
public void run() {
int times=0;
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
times++;
System.out.println("Hello, word! + "+ times);
if(times==10){
break;
}
}
}
}
C、在同一个函数中启动两个线程
/**
* 编写两个线程,1个计算求和,1个可以一直输出hello
*
*/
public class Test3 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Bird bird = new Bird(10);
Pig pig = new Pig(10);
Thread t1 = new Thread(bird);
Thread t2 = new Thread(pig);
t1.start();
t2.start();
}
}
class Pig implements Runnable
{
int n=0;
public Pig(int n){
this.n=n;
}
@Override
public void run() {
int times=0;
// TODO Auto-generated method stub
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
times++;
System.out.println("hello "+times);
if(times==n){
break;
}
}
}
}
class Bird implements Runnable
{
int n;
public Bird(int n){
this.n=n;
}
@Override
public void run() {
// TODO Auto-generated method stub
int times=0;
int res=0;
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
times++;
res += times;
System.out.println("第"+times+"次,求得结果res ="+res);
if(times==n){
System.out.println("res的最后结果是:"+res);
break;
}
}
}
}
五、继承Thread VS 实现Runnable接口 两种方法的异同
本质上来看没有什么区别,因为Thread类也是实现了Runnable()接口。一定要说有什么区别,有以下几条:
1、启动方式不一样
继承Thread,从栗子A中可以看到,启动方式是:创建对象,通过对象调用start()函数。
Cat cat = new Cat();
//启动线程 ,会调用run()函数
cat.start();
实现Runbable,从栗子B中可以看到,首先创建对象,在创建Thread()对象,然后再调用start()函数。
//创建dog对象
Dog dog = new Dog();
//注意 启动时无法直接调用start 函数
//先创建Thread函数 ,再启动
Thread t = new Thread(dog);
t.start();
2、实现Runnable接口可以实现资源共享,而继承Thread类是不能的。
实现Runnable接口,如下。通过new出两个Thread,启动两次,而且他们同时启动了dog对象,资源得到共享。
Dog dog = new Dog();
//注意 启动时无法直接调用start 函数
//先创建Thread函数 ,再启动
Thread t = new Thread(dog);
Thread t2 = new Thread(dog);
t.start();
t2.start();
而继承Thread()类则无法实现共享。
3、可扩展性不同一个类继承了Thred类以后,该类就无法继承别的类;但实现Runnable()接口则不同,还可以继承其他的类,或者实现其他的接口。
所以,实现Runnable()接口的好处比继承Thread()多很多的。