【基本概念】
线程,就是进程中的执行路径,一个进程中至少有一个线程,可以有多个线程,称为多线程
开启多个线程是为了同时运行多部分代码,使CPU更精明地工作提高CPU尤其是多核CPU的利用率。应用程序的执行是CPU在做快速的随机切换完成的,所以同时开启的线程太多会导致单个线程的执行效率低下
Java虚拟机允许应用程序并发运行多个执行线程,多线程的堆空间是共享的,栈空间是独立的,
finalize()方法,Object类的方法,运行垃圾回收器时会被调用
gc() System类的方法,运行垃圾回收器,在方法中调用finalize()方法
class A extends Object
{
public void finalize()
{
System.out.println("A");
}
}
class Demo
{
public static void main(String[] args )
{
new A();
new A();
new A();
System.gc();//
System.out.println("Hello World!");
}
}
运行结果:
两次运行结果的不一致证明:
1,主函数线程的结束并不意味着虚拟机的结束
2,jvm虚拟机中有多条线程(主线程、垃圾回收线程)
3,CPU多线程的执行具有随机性
单线程的局限性,当前任务执行不完就无法执行下一条任务,一旦进程发生等待或者死锁,程序就无法向下执行,多线程的出现解决了这一问题,随机分配CPU资源,当前线程执行不动,就执行另一条线程,提高程序响应
【创建线程对象】
Thread() 是java中用于创建线程对象的类,拥有多个构造函数,可以确定的是,每创建一个线程对象就new一次Thread类的构造函数
创建线程的目的是为了开启一条执行路径,多任务并发执行指定的代码,所以需要继承和覆盖才能将指定代码定义在run方法中。jvm创建的主线程定义在主函数中;自定义的线程定义在Thread类的run方法中
Thread类用于描述线程,而线程是需要任务的,所以Thread类也是对任务的描述,这个任务通过Thread类中的run方法来体现,run方法就是封装自定义线程任务的函数,方法中定义就是线程要运行的任务代码。
创建线程的两种方法,
继承Thread类
步骤:
①继承thread类,重写run()方法
②创建子类线程对象
③创建空间并启动线程
start()的功能:1)使线程开始执行 2)虚拟机调用run方法
【细节分析】
调用Thread中run和start的区别
run方法仅仅是封装了要执行的任务,是线程体,并不能启动线程,如果而如果直接用Run方法,这只是主线程在调用一个含线程对象的方法而已,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,这样就没有达到写线程的目的。
start()方法会启动一个线程,开辟一条新的栈内存空间;此时线程处于就绪状态,并没有运行;在方法体中会调用Thread类的run方法,然后run方法被子类覆盖,最终才开始执行自定义任务
代码2调用run方法的运行结果:
获取线程名称的操作
getName(),获取线程对象的名称,只是获取对象的名字
currentThread()获取当前线程对象
currentThread().getName() 在一起才是获取当前线程对象的名字
Thread特有方法;如果要获得任何当前线程的名称,需要在前面加Thread;即Thread.currentThread().getName()
代码2
class A extends Thread
{
String name;
A(String name)//
{
super(name);//Thread中有含String参数的构造函数,直接覆盖就可以了,给线程命名
}
public void run()//覆盖Thread中的run方法
{
for (int x=0;x<5 ;x++ )
{
System.out.println(x+"...name: "+currentThread().getName());
}
}
}
class Demo
{
public static void main(String[] args )
{
A a1 =new A("肥皂");
A a2 =new A("xiaoqiang");
a1.start();
a2.start();
System.out.println("Hello World!"+"......."+Thread.currentThread().getName());
}
}
运行结果:多线程运行的随机性;主函数线程名称为 main
栈内存的特性是先进后出,弹栈压栈的过程就像是步枪的弹夹,在单线程中主函数先进栈,函数内方法后进栈,方法执行完毕后弹栈,主函数继续执行,最下面的hello world 输出语句在最后执行。多线程hello world先输出了,说明开辟线程就是开辟了一个新的执行路径,即新的栈内存空间
多线程中的异常:单条线程的异常并不影响其他线程的执行,只有进程中所有线程都终止了,JVM虚拟机进程才会退出
线程之间的运行互不影响
创建线程的第二种方法:实现Runnable()接口
场景:子类只能继承一个父类,多继承的局限性。通过Runnable接口来解决。
而Runnable接口只有一个抽象的run()方法,子类实现run方法只是封装了任务,并没有创建线程对象的能力,线程的创建还需要Thread类的构造函数来完成
Thread()类中构造函数有参数列表为Runnable接口类型,即Thread(Runnable target);target指run方法被调用的对象
步骤:
①要执行的任务所在类实现Runnable接口。
②创建该类的对象。
③将该对象作为Thread()类的参数传入,用来创建线程对象
④调用start()方法启动线程
代码3
class A implements Runnable
{
public void run()
{
show();
}
public void show()
{
for (int x=0;x<5 ;x++ )
{
System.out.println(x+"...name: "+Thread.currentThread().getName());
}
}
}
class Demo
{
public static void main(String[] args)
{
A a= new A();
Thread t1 = new Thread(a);//A实现了Runnable接口,所以a是属于Runnable接口类型的参数
Thread t2 = new Thread(a);//明确线程要执行的任务
t1.start();
t2.start();
}
}
个人总结:【Thread和Runnable的细节】
两种方法任务对象的传递过程:
继承Thread 方法创建线程的 任务传递过程比较简单,就是将任务直接封装在线程内,通过run方法的覆盖来执行。下面重点说一下Runnable接口方法
首先要认识Thread类,JDK源码中Thread实现了Runnable接口,及其run方法,并在成员中有Runnable接口类型的引用
Thread类中有多个构造函数,它们的参数列表也各不相同,其中有一个构造函数Thread(Runnable target),将封装了任务的对象作为值来传入,然后通过Thread的构造函数“二次封装”,实现创建线程对象的目的创建的线程对象就是为任务对象服务的。
这两种方法最大的区别是:
继承Thread方法每创建一次线程对象和任务对象是同一个对象,每创建一次线程就创建一次任务,只能一条线程执行一个任务,违背了多线程的初衷
实现Runnable接口的线程对象和任务对象是分开的,可以创建多个线程对象为同一个任务服务。所以Runnable方法比较常用
代码4
class Thread implements Runnable//Thread类实现了Runnable接口
{
private Runnable target;//Runnable 接口类型的引用作为参数
Thread ()//空参构造函数
{}
Thread (Runnable target)//实参构造函数
{
this.target=target;//将任务对象转化为线程对象
}
public void run()//无论哪种情况,Thread中run方法都会被覆盖
{
if(target!=null)
target.run();//任务对象作为线程对象来调用本类方法
}
public void start()
{
//先开辟线程
run();
}
}
class Sub2 implements Runnable//重点看任务对象的传递过程
{
public void run()
{
System.out.println("Hello Runnable");
}
}
// 主函数中
// Sub2 s = new Sub2();
// Thread t1 = new Thread(s);
// Thread t2 = new Thread(s);
// t1.start();
// t2.start();
class Sub extends Thread//继承的传递过程比较简单,就是简单的覆盖
{
public void run()
{
System.out.println("Hello Thread");
}
}
// 主函数中
// Sub s1 = new Sub();
// Sub s2 = new Sub();
// s1.start()
// s2.start()
线程的状态
图解:
new() 初始化,在堆内存创建对象空间
start() 就绪状态,开辟执行路径,即新的栈内存空间
sleep(time)休眠Thread类的静态方法,会抛出异常
wait() 等待
notify() 唤醒
stop() 线程终止后,只能被回收,不能被挂起
临时阻塞状态:具备执行资格,还不具备执行权,单核CPU执行多线程任务时,一次只能执行一个,这是其他“同时”执行的线程就处于临时阻塞状态