文章目录
- 前言
- 一、什么是进程与线程?
- 1.进程
- 2.线程
- 3.其他相关概念
- 二、如何创建线程
- 1.继承Thread类,重新run方法
- 2.实现Runnable接口
- 3.通过Callable和Future创建线程
- 4. 继承Thread vs实现Runnable的区别
- 三、用户线程和守护线程
- 守护线程的使用
- 设置成守护线程
- 四、线程常用方法
- 常用方法第一组
- 常用方法第二组
- 五、使用线程的好处
- 六、线程生命周期
前言
在学习多线程之前,先了解什么是进程,什么是线程。
一、什么是进程与线程?
1.进程
- 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
- 进程是程序的一次执行过程,它有自己独立的生命周期,它会在启动程序时产生,运行程序时存在,关闭程序时消亡。
2.线程
1.线程由进程创建的,是进程的一个实体,是具体干活的人。
2.一个进程可以拥有多个线程。线程不独立分配内存,而是共享进程的内存资源,线程可以共享cpu的计算资源。
现在,进程更强调【内存资源的分配】,而线程更强调【计算资源的分配】。因为有了线程的概念,一个进程的线程就不能修改另一个线程的数据,隔离性更好,安全性更好。
我们在使用浏览器播放视频或者下载文件时,我们可以同时播放或者下载多个,那播放两个视频或者下载多个文件就是不同线程在做的工作,否则,你一定是需要等待一个结束了,另一个才能开始。
我们可以在计算机的任务管理器中查看当前计算机的进程和线程数。
3.其他相关概念
1.单线程:同个时刻,只允许执行一个线程
2.多线程:同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个浏览器进程,可以同时下载多个文件
3.并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简
单的说,单核cpu实现的多任务就是并发。
4.并行:同一个时刻,多个任务同时执行。多核cpu可以实现并行。
二、如何创建线程
创建线程主要有三种方式:
1.继承Thread类,重新run方法
步骤:
• 定义类继承Thread;
• 重写Thread类中的run方法;(目的:将自定义代码存储在run方法,让线程运行)
• 调用线程的start方法:(该方法有两步:启动线程,调用run方法)
代码如下(示例):
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
new ThreadTest().start();
while (true) {
System.out.println(Thread.currentThread().getName()+"---1");
Thread.sleep(1000);
}
}
}
class ThreadTest extends Thread{
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName()+"---2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
2.实现Runnable接口
步骤:
• 创建任务: 创建类实现Runnable接口
• 使用Thread 为这个任务分配线程
• 调用线程的start方法
代码如下(示例):
public class RunnableDemo {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 11; i++) {
Thread.sleep(1000);
System.out.println("hi"+i);
if (i == 5) {
T3 t3 = new T3();
Thread thread = new Thread(t3);
thread.start();
System.out.println("子线程先开始");
thread.join();
thread.setDaemon(true);
System.out.println("子线程结束,主线程开始");
}
}
}
}
class T3 implements Runnable {
@Override
public void run() {
int sum = 0;
while (true){
try {
Thread.sleep(1000);
sum++;
System.out.println("hello"+sum +"次");
} catch (InterruptedException e) {
e.printStackTrace();
}
if (sum == 10){
break;
}
}
}
}
3.通过Callable和Future创建线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
代码如下(示例):
public class UseCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println(1);
//保存一个将来的返回值
FutureTask<String> futureTask = new FutureTask<>(new Task());
new Thread(futureTask).start();
System.out.println(3);
System.out.println(4);
String result = futureTask.get();
System.out.println(5);
System.out.println(result);
System.out.println(6);
}
static class Task implements Callable<String> {
public String call() throws Exception {
while (true) {
Thread.sleep(1000);
return Thread.currentThread().getName() + "---2";
}
}
}
}
注:futureTask.get();这是一个阻塞的方法,意思就是,这个方法会一直等,主线程会一直等待,这个线程执行完成之后并有了返回值,才会继续执行。
4. 继承Thread vs实现Runnable的区别
- 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本
质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口 - 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了 单继承的限制
三、用户线程和守护线程
Java提供两种类型的线程:用户线程和守护程序线程
1. 用户线程: 也叫工作线程,当线程的任务执行完或通知方式结束。
2. 守护线程: 一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。
3.常见的守护线程: 垃圾回收机制
守护线程的使用
守护线程对于后台支持任务非常有用,例如垃圾收集,释放未使用对象的内存以及从缓存中删除不需要的条目。大多数JVM线程都是守护线程。在比如qq等等聊天软件,主程序是非守护线程,而所有的聊天窗口是守护线程,当在聊天的过程中,直接关闭聊天应用程序时,聊天窗口也会随之关。包括word中我们在书写文字的时候,还有线程帮我们进行拼写检查,这都是守护线程。
设置成守护线程
要将线程设置为守护线程,我们需要做的就是调用Thread.setDaemon()
代码示例:
thread.setDaemon(true);
任何线程都继承创建它的线程的守护进程状态。由于主线程是用户线程,因此在main方法内创建的任何线程默认为用户线程。
四、线程常用方法
常用方法第一组
- setName //设置线程名称,使之与参数name相同
- getName //返回该线程的名称
- start //使该线程开始执行;Java 虚拟机底层调用该线程的start0方法
- run //调用线程对象 run方法;
- setPriority //更改线程的优先级
- getPriority //获取线程的优先级
- sleep //在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
- interrupt //中断线程
注意事项和细节
- start底层会创建新的线程,调用run,run 就是一个简单的方法调用,不会启动新线程
- 线程优先级的范围
/**
* 线程的最小优先级 .
*/
public static final int MIN_PRIORITY = 1;
/**
* 线程的默认优先级 .
*/
public static final int NORM_PRIORITY = 5;
/**
* 线程的最大优先级 .
*/
public static final int MAX_PRIORITY = 10;
- interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
- sleep:线程的静态方法,使当前线程休眠
常用方法第二组
- yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
- join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
示例代码:
public class Thread03 {
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1();
t1.start();
for (int i = 0; i < 11; i++) {
System.out.println("主线程(小弟)吃" + i + "包子" );
if (i == 5) {
System.out.println("主线程(小弟)让(老大)先吃");
t1.join();
//Thread.yield();
System.out.println("主线程(老大)吃完了,小弟吃");
}
}
}
}
class T2 implements Runnable {
@Override
public void run() {
int sum = 0;
while (true) {
try {
Thread.sleep(1000);
sum++;
System.out.println("hello" + sum + "次");
} catch (InterruptedException e) {
e.printStackTrace();
}
if (sum == 10) {
break;
}
}
}
}
五、使用线程的好处
1.加速,提高CPU的使用率
2.能够异步无干扰进行其他业务操作
代码示例:
public class UseCallable1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//单线程计算
long start = System.currentTimeMillis();
long res = 0L;
for (int i = 0; i < 1000000000; i++) {
res += i;
}
long end = System.currentTimeMillis();
System.out.println("---单线程计算时间" + (end - start));
System.out.println(res);
//多线程计算
start = System.currentTimeMillis();
FutureTask[] futureTasks = new FutureTask[10];
res = 0L;
for (int i = 0; i < 10; i++) {
FutureTask<Long> futureTask = new FutureTask<>(new Task(i*100000000,(i+1)*100000000));
new Thread(futureTask).start();
futureTasks[i] = futureTask;
}
for (int i = 0; i < futureTasks.length; i++) {
long sum = (long) futureTasks[i].get();
res += sum;
}
end = System.currentTimeMillis();
System.out.println("---多线程计算时间" + (end - start));
System.out.println(res);
}
static class Task implements Callable<Long> {
private int from;
private int to;
public Task(int from, int to) {
this.from = from;
this.to = to;
}
@Override
public Long call() throws Exception {
long sum = 0L;
for (int i = from; i < to; i++) {
sum += i;
}
return sum;
}
}
}
六、线程生命周期
生命周期可以通俗地理解为“从摇篮到坟墓”(Cradle-to-Grave)的整个过程。线程的生命周期包括从创建到终结的整个过程。
JDK中用Thread.State枚举表示了线程的几种状态
public enum State {
/**
* 尚未启动的线程处于此状态.
*/
NEW,
/**
* 在Java虚拟机中执行的线程处于此状态.
*/
RUNNABLE,
/**
* 被阻塞等待监视器锁定的线程处于此线程.
*/
BLOCKED,
/**
* 正在等待另一个线程执行特定动作的线程处于此状态.
*/
WAITING,
/**
* 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
*/
TIMED_WAITING,
/**
* 己退出的线程处于此状态.
*/
TERMINATED;
}
其中:RUNNABLE
中有Ready
和Running
两种状态