Java从入门到放弃—线程的三种创建方式之继承Thread类
01 线程的概念
- 在介绍线程前,我们首先知道程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程是程序的一次执行过程,它是系统资源分配的单位,是一个动态的概念。Java 虚拟机允许应用程序并发地运行多个执行线程,因此一个进程可以有多个线程。线程作为CPU调度和执行的单位,它拥有独立的执行路径。
- 在一个进程中,如果开辟了多个线程,线程的运行将由调度器安排调度,调度器与操作系统是紧密相关的,因此执行线程的先后顺序不能人为干预。真正的多线程是指有多个CPU。但是,很多多线程都是模拟出来的,即在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以出现了同时执行的错觉。
- 多线程会带来额外的开销(如CPU调度时间,并发控制开销),线程越多,CPU的开销越大。
- 每个线程都有自己的工作内存,内存控制不当会造成数据不一致。
- 线程开启后不一定立即执行,由CPU调度安排。多线程的运行由调度器安排调度,先后顺序不能人为干预。
02 Thread类
-
在Java中可以通过继承Thread类创建线程。每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。当某个线程中运行的代码创建了一个新
Thread
对象时,该新线程的初始优先级被设定为创建线程的优先级。 -
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()
返回线程的优先级。void
interrupt()
中断线程。static boolean
interrupted()
测试当前线程是否已经中断。boolean
isAlive()
测试线程是否处于活动状态。boolean
isDaemon()
测试该线程是否为守护线程。boolean
isInterrupted()
测试线程是否已经中断。void
join()
将该线程插队执行。void
join(long millis)
等待该线程终止的时间最长为millis
毫秒。void
run()
如果该线程是使用独立的Runnable
运行对象构造的,则调用该Runnable
对象的run
方法;否则,该方法不执行任何操作并返回。void
setDaemon(boolean on)
将该线程标记为守护线程或用户线程。void
setName(String name)
改变线程名称,使之与参数name
相同。void
setPriority(int newPriority)
更改线程的优先级。static void
sleep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。static void
sleep(long millis, int nanos)
在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。void
start()
使该线程开始执行;Java 虚拟机调用该线程的run
方法。
03 创建线程
- 通过继承Thread类建立线程具体步骤:
step1:继承Thread类
step2:重写run()方法
step3:创建继承了Thread类的对象,调用start()方法启动线程 - 代码示例:
public class ThreadTest extends Thread{
@Override
public void run() {
//线程执行体
for (int i = 0; i < 1000; i++) {
System.out.println("线程执行体");
}
}
//主线程
public static void main(String[] args) {
//创建线程对象
ThreadTest threadTest = new ThreadTest();
//调用start方法,线程的启动需要时间,不会立即执行
threadTest.start();
System.out.println("线程正在启动");
for (int i = 0; i < 1100; i++) {
System.out.println("main线程执行体");
}
}
}
- 运行结果:由于线程的启动需要时间,因此运行结果先出现了"线程正在启动",main线程和该线程同步执行,因此两个线程的执行体在运行结果中交替出现。
04 案例
- 利用多线程同时下载图片
public class Test02 extends Thread{
private String url;
private String name;
//通过构造方法保证每个线程下载不同的图片
public Test02(String url,String name){
this.url=url;
this.name=name;
}
@Override
public void run() {
webDownloader webDownloader = new webDownloader();
try {
webDownloader.download(url,name);
System.out.println(name+"下载成功");
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//创建三个线程同时下载不同的图片
Test02 t1 = new Test02("https://imgsa.baidu.com/news/q%3D100/sign=a3f1cc8c6d2762d0863ea0bf90ed0849/b7003af33a87e9503f3780c31e385343fbf2b478.jpg", "1.jpg");
Test02 t2 = new Test02("https://imgsa.baidu.com/news/q%3D100/sign=aa72068963224f4a5199771339f69044/64380cd7912397ddb9cde8ab5782b2b7d1a287ec.jpg", "2.jpg");
Test02 t3 = new Test02("https://imgsa.baidu.com/news/q%3D100/sign=a3f1cc8c6d2762d0863ea0bf90ed0849/b7003af33a87e9503f3780c31e385343fbf2b478.jpg", "3.jpg");
//开启线程
t1.start();
t2.start();
t3.start();
}
}
//下载器
class webDownloader{
//下载方法
public void download(String url,String name) throws MalformedURLException {
try {
//导包后,直接使用FileUtiils下的copyURLToFile从网页上下载图片
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("下载失败");
}
}
}