前言必读
一、概念讲解
1.进程和线程的概念
进程:一个执行的应用程序
线程:一个应用程序内的具体执行不同模块
2.进程和进程之间的关系
进程和进程内存独立不共享
3.线程和线程之间的关系
(1)Java中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程
(2) 一个线程一个栈(每个栈之间独立执行,互不干扰,即多线程并发),栈内存独立。堆内存和方法区内存共享
(3)多线程的目的:提高程序的处理效率
(4)单核CPU能做到多线程并发吗:不能(cpu切换处理很快,超过肉眼速度,给人能多线程的假象)
4.并发和并行的概念
并发:一个cpu去执行不同的线程任务(哪里有任务就去哪里执行)
并行:许多个cpu一起去执行不同线程任务(对一个cpu说,好兄弟,我们一起来齐心协力干吧)
5.线程对象的生命周期:
(1)新建状态(new):
程序使用new关键字创建了一个进程后,该线程就是新建状态,内存由JVM分配,并初始化成员变量的值。
(2)就绪状态(runnable):
线程对象调用start方法后,该线程就变成了就绪状态 ,JVM会为其创建方法栈和程序计数器,等待调度运行。
(3)运行状态(running)
处于就绪的线程获得了cpu,开始执行run方法的线程
(4)阻塞状态(blocked)
指线程因为某种原因放弃了cpu的使用权,暂停运行,直到线程通过就绪状态再进入运行状态。阻塞的情况有三种。
(4.1)等待阻塞(o.wait):等待队列。运行的线程执行o.wait方法,JVM会把该线程放入等待队列
(4.2)同步阻塞(lock):锁池。运行的线程在获取对象的同步锁时,若该同步锁被其他线程占用,JVM会把该线程放入锁池中
(4.3)其他阻塞(sleep/join):运行的线程执行sleep、join或者IO请求时,JVM会把该线程设为阻塞状态。当sleep、join等待线程超时或终止或者IO处理完毕时,该线程重新进入就绪状态。
(5)线程死亡(dead)
线程结束就是死亡状态。线程死亡分为三种
(5.1)正常结束:run或call方法执行完成
(5.2)异常结束:线程抛出一个未捕获的Exception或者Error
(5.3)强制结束:通过调用stop方法来结束线程,容易导致线程进入死锁,不推荐使用
二、创建线程的方式
方式一:编写一个类,继承java.lang.Thread类
优缺点:
优点:编码简单
缺点:继承存在单继承的局限性,线程类继承Thread后,不能继承其他类,不利于扩展
步骤:
1.main方法外定义一个子线程继承Thread类。
2.重写run方法。(线程具体要执行的事情写在run方法里面)
3.new出一个Thread子线程对象—–要在main方法里面。(子线程要写在主线程方法之前)
4.调用start方法启动线程(调用这个才代表多线程启动,调用run只是单线程)
代码例子:
package bao;
public class Test {
public static void main(String[] args) {
//4.创建Thread子线程的对象(要创建在主线程之前,要不然失去了多线程的意义)
Thread t = new MyThread();
//5.启动线程(执行的是run方法)
t.start();
//6.创建一个main方法里面的主线程进行比较
for (int i = 0; i < 5; i++) {
System.out.println("主线程执行输出:"+i);
}
}
}
//1.定义一个子线程继承Thread类
class MyThread extends Thread{
//2.重写run方法:在里面定义子线程要做什么 快捷键:run+回车键
@Override
public void run() {
super.run();
//3.子线程要做的事情 例子:要看执行循环多少次
for (int i = 0; i < 5; i++) {
System.out.println("子线程执行输出:"+i);
}
}
}
运行结果:
子线程执行输出:0
主线程执行输出:0
子线程执行输出:1
主线程执行输出:1
子线程执行输出:2
主线程执行输出:2
子线程执行输出:3
主线程执行输出:3
子线程执行输出:4
主线程执行输出:4
方式二: 编写一个类,实现java.lang.Runnable接口
优缺点:
优点:线程任务类只是实现接口,还可以继续继承类和实现接口,可扩展性强,容易实现资源共享
缺点:编程多一层对象包装,如果线程有执行结果是不能直接返回的
步骤:
1.main方法外定义一个子线程MyRunnable实现Runnable接口
2.重写run方法。(线程具体要执行的事情写在run方法里面)
3.new出一个MyRunnable对象(然后传给Thread对象)
4.new出一个Thread子线程对象—–要在main方法里面。(子线程要写在主线程方法之前)
5.调用start方法启动线程(调用这个才代表多线程启动,调用run只是单线程)
ps:这个创建线程方法和第一个创建线程方法就第一步不一样。还有在new子对象出来之前要创建一个MyRunnable对象,把这个值传入子线程对象里面。其他的都一样步骤
代码例子:
package bao;
public class Test {
public static void main(String[] args) {
//4.创建MyRunnable对象,并传给Thread子线程
Runnable target = new MyRunnable();
//5.创建Thread子线程对象
Thread t = new Thread(target);
//6.启动线程
t.start();
//7.创建一个main方法里面的主线程进行比较
for (int i = 0; i < 5; i++) {
System.out.println("主线程输出:"+i);
}
}
}
//1.定义一个线程类,实现Runnable接口
class MyRunnable implements Runnable{
//2.重写run方法。run方法里面定义线程要执行的代码。快捷键:run+回车键
@Override
public void run() {
//3.定义线程要执行的任务
for (int i = 0; i < 5; i++) {
System.out.println("子线程输出:"+i);
}
}
}
运行结果:
子线程输出:0
子线程输出:1
主线程输出:0
子线程输出:2
主线程输出:1
子线程输出:3
主线程输出:2
子线程输出:4
主线程输出:3
主线程输出:4
方式三: 编写一个类,实现Callable接口
优缺点:
优点: 在方式二的优点上,还能获取线程执行结果(上面两个就不能)
缺点:编程麻烦
步骤:
1.定义一个MyCallable线程类,实现Callable接口
2.重写Call方法(线程具体要执行的事情写在Call方法里面)
3.创建MyCallable任务对象,值交给下一步
4.创建FutureTask,值交给下一步
5.创建Thread线程对象
6.调用start方法启动线程
7.get方法输出线程执行结果
代码例子:
package bao;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//4.创建MyCallable任务对象,值交给下一步
Callable<String> call = new MyCallable(100);
//5.创建FutureTask,值交给下一步
FutureTask<String> f1 = new FutureTask<>(call);
//6.创建Thread线程对象
Thread t = new Thread(f1);
//7.启动线程
t.start();
//输出结果
System.out.println(f1.get());//遇到异常抛出或者捕捉处理就好了
}
}
//1.定义一个MyCallable线程类,实现Callable接口
class MyCallable implements Callable<String>{ //填写String就返回类型结果
//3.2写在这里方便阅读
private int n;
public MyCallable(int n) {
this.n=n;
}
//2.重写Call方法,并在里面写子线程要执行的任务 快捷键:call+回车键
@Override
public String call() throws Exception {
//return null; 需要返回值这个就要注释掉
//3.1定义线程要执行的任务 例子为求和
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i;
}
return "子线程的执行结果为"+sum;
}
}
运行结果:
子线程的执行结果为4950
三、常用API(其实也不常用)
1.给线程改名字,为了方便区分线程。
作用:为了更加方便看到是哪个线程在执行
代码例子来自方式一的代码,拿来修改添加
详解:
1.按照这个格式输入
2.这个位置生成name的有参构造器
3.输入线程的名字
运行结果:(输出的基数大了才能看到主子线程交叉运行,比如500)
主线程执行:0
主线程执行:1
主线程执行:2
主线程执行:3
主线程执行:4
子线程:0
子线程:1
子线程:2
子线程:3
子线程:4
2.线程睡眠(延迟执行)
作用:延迟线程的执行结果
代码例子来自方式一的代码,拿来修改添加
运行结果:
子线程输出后,延迟了5秒后才执行主线程
旁白:掌握第一种创建方式,后面的那两种方式也差不多,就是改变接口和创建线程对象之前加了1层或者2层对象而已。还有重写的方法不一样,其他的都一样。根据不同的场景来用。常用的API知识点看看就好了。也不难
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/112690.html