1、基本概念
1.进程:正在进行中的程序,指程序在内存中开辟了一块空间;进程持有资源(共享内存、共享文件)和线程,具有动态性。
2.线程:负责程序执行的一条执行路径,该路径也被称为执行单元。进程的执行实际上是线程在执行,一个进程至少会有一个线程,当一个进程中有多个线程时,就是多线程程序,这些线程共享进程资源。
3.多线程意义:最主要的目的是实现同时执行的不同功能的效果,虽然不一定能提高效率,但可以合理的利用cpu资源。
4.任务:每个线程需要执行的代码被称为任务代码,且都有其存储位置。例如下面代码示例中:
主线程的任务代码存储在main()方法中
垃圾回收线程的任务代码存储在finalize()方法中
线程是随着任务代码的执行才存在的,且随着任务代码的结束而消失。
2、线程示例
通过利用垃圾回收机制证明JVM虚拟机是多线程程序
//创建该类用于产生垃圾
class garbage {
/*
* 考虑到所有的对象都可以被当做垃圾回收
* 因此垃圾回收机制的代码应该在所有类的父类的当中
* 即Object类中的finalize()方法
* 重写该方法便于验证是否执行
*/
public void finalize() {
System.out.println("垃圾回收线程抢占CPU成功!");
}
}
public class Demo1 {
public static void main(String[] args) {
/*
* main()方法是程序的入口
* 因此以下代码都属于主线程部分
*/
/*
* 创建多个匿名对象
* 匿名对象在创建后就直接成为了垃圾
* 因此可触发垃圾回收机制
*/
new garbage();
new garbage();
new garbage();
new garbage();
new garbage();
/*
* 因为垃圾量少且垃圾回收机制的优先级本身就低
* 因此使用System类中的强制垃圾回收方法gc()
* 这样主线程运行至此就会启动垃圾回收线程
* 执行finalize()方法
*/
System.gc();// 垃圾回收线程启动,此时同时存在2个线程
System.out.println("主线程抢占CPU成功!");
}
}
结果如下:
由于线程之间在争抢cpu,因此多线程程序的执行结果是不确定的,这就是多线程程序的随机性。
3、Thread类
在Java中,线程同样实现了面向对象,那就是Thread类,使用该类创建新执行线程有两种方法:
1.将类声明为Thread的子类。该子类重写Thread类的run()方法。接下来可以分配并启动该子类的实例。如果直接创建Thread类的对象并用对象调用start()方法,将不会有任何结果,因为任务代码要求必须写在run()方法中,而Thread类中的run()方法没有任何功能。
该方法不建议使用,仅了解即可!
// 实现两位学生同时回答问题的效果
class Student extends Thread {
// 创建Student类继承Thread类
private String name;
public Student() {
super();
}
public Student(String name) {
super();
this.name = name;
}
public void run() {
/*
* 之前讲过
* 每个线程需要执行的代码被称为任务代码,且都有其存储位置
* 此处重写run()方法
* 就是创建任务代码
* 因此run()方法也就是任务代码的存储位置
*/
for (int i = 1; i <= 10; i++) {
System.out.println(name + "回答了第" + i + "个问题");
/*
* currentThread()是一个静态方法
* 返回值是当前正在执行的线程的对象
* 再调用对象的getName()方法
* 显示当前线程的名称
*/
// System.out.println(Thread.currentThread().getName()+"回答了第"+i+"个问题");
}
}
}
public class Demo2 {
public static void main(String[] args) {
/*
* 由于Student类继承了Thread类
* 每次创建Student子类对象时
* 就相当于新创建了一个线程
*/
Student s1 = new Student("Tom");
/*
* 主线程运行至创建s2对象
* 此时整个程序只有2个线程
* 主线程以及垃圾回收线程
* 两个子线程目前仅仅是创建
* 尚未启动,因此不会抢占cpu
*/
Student s2 = new Student("Mike");
/*
* run()方法只是普通的方法调用
* 而start()方法可以启动线程
* 主线程的任务代码存储在main()方法中
* 子线程的任务代码存储在run()方法中
*/
s1.start();// 主线程执行至此,同时存在3个线程
s2.start();// 主线程执行至此,同时存在4个线程
System.out.println("提问!");
// 显示主线程名称
// System.out.println(Thread.currentThread().getName()+"提问!");
}
}
注意:
线程与线程之间在内存中是相互独立的,例如当一个线程发生异常时,其他线程不受影响,会继续执行。每个线程在栈中都有一块内存,当线程执行完自己的任务代码,就从栈中出栈,当所有线程都结束了,整个进程才会结束。
结果如下:
虽然主线程在执行最后一行输出代码之前已经启动了子线程,但子线程并未成功争抢到cpu,因此结果是主线程持续抢占cpu打印了“提问!”,之后才是子线程开始互相争抢cpu,这就表示子线程并不是随着启动就能占有cpu。
显示线程名称的结果如下:
主线程名字:main
子线程名字:Thread-编号,编号从0开始
2.声明实现Runnable接口的类,并重写run()方法,该类只是为了描述线程任务,实现了线程任务的面向对象,从而实现了线程任务和线程对象的分离,使线程执行的任务更加灵活,只要是实现了Runnable接口的类的对象,都可以作为参数传给Thread类的构造方法并启动线程;而且该类实现了接口的同时,还可以继承其他父类。
创建线程常使用此方法!
//实现四个窗口同时买票
class Ticket implements Runnable {
// 创建Ticket类实现了Runnable接口
// 定义总共50张票
private int num = 50;
// 重写Runnable接口中的run()方法,省略循环
public void run() {
if (num > 0) {
System.out.println("窗口" + Thread.currentThread().getName() + "卖了第" + num-- + "张票");
}
}
}
public class Demo3 {
public static void main(String[] args) {
// 创建实现了Runnable接口的Ticket类的对象
Ticket t = new Ticket();
/*
* 创建Thread类的对象,也就是创建了线程
* 把实现了Runnable接口的Ticket类的对象作为参数传递给Thread类的构造方法
*/
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
4、线程的生命周期
版权声明:欢迎转载,欢迎扩散,但转载时请标明作者以及原文出处,谢谢合作! ↓↓↓