一.了解线程之前先了解一下进程和线程的联系
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1-n个线程。
线程:同一个线程共享数据代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
多进程:操作系统能同时运行多个任务。
多线程:同一个程序中有多个顺序流在执行。
线程属于某个进程,进程中的多个线程共享进程的内存。“同时”执行是人的感觉,在线程之间实际上轮换执行。
二.线程的状态
1.新建状态(New):新创建了一个线程对象。
2.就绪状态(Runnable):线程对象创建后,其他线程调用该对象的start()方法。该线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3.运行状态(Running):就绪状态的线程获取CPU,执行程序代码。
4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU的使用权,暂时停止运行。知道线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一),等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
(二),同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三),其他阻塞:运行的线程执行sleep()或join()方法,或者发出I/O请求时,JVM会把该线程设置为阻塞状态。当sleep()状态超时,join()等待线程终止或者
超时,或者I/O处理完毕时,线程重新转入就绪状态。
5.死亡状态(Dead):线程执行完了或者因异常退出了run()方法,改线程结束生命周期。
三.线程的创建和启动
定义 实例化 启动
1.扩展java.lang.Thread类 —— 直接new即可 新 对象的start()方法
2.实现java.lang.Runnable接口 —— 用Thread的构造方法 用Thread构造器构造一个新对象,然后调用start()方法
public class ThreadDemo1 implements Runnable{
private String name;
public ThreadDemo1(String name) {
super();
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name+":"+i);;
}
}
public static void main(String[] args) {
ThreadDemo1 threadDemo1=new ThreadDemo1("张三");
ThreadDemo1 threadDemo2=new ThreadDemo1("李四");
Thread t1=new Thread(threadDemo1);
Thread t2=new Thread(threadDemo2);
t1.start();
t2.start();
}
}
执行结果:
2.扩展Thread类实现多线程
public class ThreadDemo2 extends Thread{
private String name;
public ThreadDemo2(String name) {
this.name = name;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name+":"+i);
}
}
public static void main(String[] args) {
ThreadDemo2 threadDemo1 =new ThreadDemo2("Jerry");
ThreadDemo2 threadDemo2 =new ThreadDemo2("Tom");
threadDemo1.start();
threadDemo2.start();
}
}
执行结果:
四、两种方式的比较:
采用继承Rhread类方式:
(1) 优点:编写简单,如果需要访问当前线程无需使用Rhread.currentThread()方法,直接使用this,即可获得当前线程。
(2) 缺点:因为线程类已经继承Thread类,所以不能再继承其他的父类(单继承)
采用实现Runnable接口方式:
(1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同的线程来处理同一份资源的情况,从而可以讲CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
(2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。