线程简介:
要想说线程,首先必须得聊聊进程,因为线程是依赖于进程存在的。
进程概述:
进程就是正在运行的程序,是执行程序的一次执行过程,一个动态的概念,是系统进行资源分配和调用的独立单位;每一个进程都有他独立的内存空间和系统资源;
线程概述:
在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。是程序使用CPU的基本单位。
多线程的意义:
- 多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。
程序在运行的时候,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到 CPU的执行权的概率应该比单线程程序抢到的概率要大.那么也就是说,CPU在多线程程序中执行的时间要比单线程多,所以就提高了程序的使用率.但是即使是多线程程序,那么他们 中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机性.
- 单进程的计算机只能做一件事,而随着现在的技术发展,已经不能满足人们的需求;
例如:人们现在一边玩游戏(游戏进程)一边听歌(音乐进程),这就是两个进程,但对于单核计算机来讲,游戏进程和音乐进程不能同时运行,CPU在一个时间点上只能做一件事情,计算机在不断地频繁切换,而且速度是非常的快,所以,感觉是在同时进行,而多线程就可以提高CPU的使用率;
进程和线程的区别
进程:应用程序的执行实例,有独立的内存空间和系统资源
线程:CPU调度和分派的基本单位,进程中执行运算的最小单位,可完成一个独立的顺序控制流程
进程和线程的关系
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
(3)处理机分给线程,即真正在处理机上运行的是线程。
(4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
多线程的并行与并发:
- 线程的并行是指在逻辑上的同时发生,在某一时间内同时运行多个程序;
- 线程的并发是指在物理上的同时发生,在某一时间点同时运行多个程序;
并发 : 指应用能够交替执行不同的任务, 其实并发有点类似于多线程的原理, 多线程并非是如果你开两个线程同时执行多个任务, 执行, 就是在你几乎不可能察觉到的速度不断去切换这两个任务, 已达到"同时执行效果", 其实并不是的, 只是计算机的速度太快, 我们无法察觉到而已. 就类似于你, 吃一口饭喝一口水, 以正常速度来看, 完全能够看的出来, 当你把这个过程以n倍速度执行时..可以想象一下.
并行 : 指应用能够同时执行不同的任务, 例:吃饭的时候可以边吃饭边打电话, 这两件事情可以同时执行;
线程的优先级
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
优先级只是意味着获得调度的概率低,而不是优先级低就不会被调用,都是有CPU的调度有关。可以使用 getPriority() . setPriority(int xxx) 的方式改变或获取优先级。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
线程方法
说明 | 解释 |
---|---|
setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒数内使当前正在执行的线程休眠 |
void join () | 等待线程终止 |
static void yield() | 暂停当前正在执行的线程而去执行其他的线程 |
void interrupt () | 线程中断 |
Boolean isAlive() | 测试线程是否处于活动状态 |
守护(daemon)线程
- 线程有守护线程和用户线程;
- 虚拟机必须要保证用户线程执行完成,而不需要等待守护线程执行完毕;
线程的创建:
由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。
而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。
但是Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。
但是呢?Java可以去调用C/C++写好的程序来实现多线程程序。
由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,
然后提供一些类供我们使用。我们就可以实现多线程程序了。
三种创建方式:
Thread class | 继承Thread类 |
Runnable接口 | 实现Runnable接口 |
Callable接口 | 实现Callable接口 |
方式一、Thread
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
run()和start()方法的区别
在 Java 当中,线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。
- 第一是创建状态。在生成线程对象,并没有调用该对象的 start 方法,这是线程处于创建状态。
- 第二是就绪状态。当调用了线程对象的 start 方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
- 第三是运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行 run 函数当中的代码。
- 第四是阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait 等方法都可以导致线程阻塞。
- 第五是死亡状态。如果一个线程的 run 方法执行结束或者调用 stop 方法后,该线程就会死亡。对于已经死亡的线程,无法再使用 start 方法令其进入就绪。
为什么要重写run方法?
- 我们可以在写其他的方法,那么其他方法中封装的代码不一定都会被我们线程执行的。
- 那么也就是run方法中封装应该是必须被线程执行的代码.
run方法中的代码的书写原则: 一般是比较耗时的代码
实例:
package com.zhang.demo1.thread;
//继承thread类
public class thread1 extends Thread {
//重写run方法
@Override
public void run() {
try {
//线程休眠
thread1.sleep(6000);
//线程执行体
for (int i = 0; i < 15; i++) {
System.out.println("线程执行体"+i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//主线程
public static void main(String[] args) {
//创建线程对象
Thread thread = new thread1();
//start() 方法用来 启动线程;
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程"+i);
}
}
}
以上代码编译运行的结果如下:
主线程0
主线程1
主线程2
主线程3
主线程4
主线程5
主线程6
主线程7
主线程8
主线程9
线程执行体0
线程执行体1
线程执行体2
线程执行体3
线程执行体4
线程执行体5
线程执行体6
线程执行体7
线程执行体8
线程执行体9
线程执行体10
线程执行体11
线程执行体12
线程执行体13
线程执行体14
方式二、实现Runnable接口
- 实现接口
- 重写run方法
- 创建runbale接口实现类的对象。
- 启动线程。需要创建一个线程Thread对象 ,然后把runbale接口实现类的对象丢到构造参数里 ,
- 调用start方式启动;
实例:
package com.zhang.demo1.thread;
//实现runnable接口
//1.实现接口
//2.重写run方法
//3.创建runbale接口实现类的对象。
//启动线程。需要创建一个线程Thread对象 , 然后把runbale接口实现类的对象丢到构造参数里 , 调用start方式启动;
public class Thread2 implements Runnable {
@Override
public void run() {
//线程执行体
for (int i = 0; i < 15; i++) {
System.out.println("线程执行体"+i);
}
}
public static void main(String[] args) {
//重点就是将runbale接口实现类的对象放入Thread构造器中
Thread2 thread2 = new Thread2();
new Thread(thread2).run();
for (int i = 0; i < 10; i++) {
System.out.println("主线程"+i);
}
}
}
以上代码编译运行的结果如下:
线程执行体0
线程执行体1
线程执行体2
线程执行体3
线程执行体4
线程执行体5
线程执行体6
线程执行体7
线程执行体8
线程执行体9
线程执行体10
线程执行体11
线程执行体12
线程执行体13
线程执行体14
主线程0
主线程1
主线程2
主线程3
主线程4
主线程5
主线程6
主线程7
主线程8
主线程9
方式三、实现Callable接口
- 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值;
- 重写Callable方法,需要抛出异常;
- 创建目标对象;
- 创建执行服务:ExecutorService ser=Executors.newFixedThreadPool(1);
- 提交执行:Future< Boolean> result=ser.submit(t1);
- 获取结果:Boolean r1=result1.get();
- 关闭服务:ser.shutdownNow();
创建线程的三种方法的比较:
- 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
- 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。