多线程概述(并发编程)
进程
程序是静止的,而运行中的程序就是进程
特征
- 动态性:进程是运行中的程序,要动态的占用内存,CPU和网络等资源
- 独立性:进程和进程之间是相互独立的,彼此有自己独立内存区域
- 并发性:假如CPU是单核,同一时刻内存中只有一个进程在被执行,CPU会依时为每个进程服务,由于切换速度非常快,给我们的感觉就是这些进程在同时执行,这就是并发性
并行:
同一时刻同时有多个进程在执行(多核CPU)
线程
线程属于进程,一个进程可以包含多个线程,这就是多线程。(线程是进程中的一个独立执行单元)线程的创建开销相对于进程来说比较小,线程也支持并发性
作用
- 提高程序效率,线程支持并发性,可以有更多机会得到CPU
- 多线程可以解决很多业务模型
- 大型高并发技术的核心技术
线程创建方式一:继承Thread类
代码实现
package ThreadTest;
/*
* 创建线程的方法一:
* 1. 直接定义类继承Thread
* 2. 重写run()方法
* 3. 创建线程对象
* 4. 调用线程对象的start()方法启动线程
*/
// 定义一个线程类继承Thread类
class MyThread extends Thread{
//重写run()方法
@Override
public void run() {
// 线程的执行方法
for(int i=0;i<5;i++){
System.out.println("子线程输出:"+i);
}
}
}
public class ThreadDemo {
// 这里可以把启动后的ThreadDemo当成一个进程
// main方法是由主线程执行的,理解成main方法就是一个主线程
public static void main(String[] args) {
//创建线程对象
Thread t=new MyThread(); //多态
//调用start()方法启动线程
t.start(); //此时一共存在两个线程,一个是main函数表示的主线程,另一个就是t表示的子线程
for(int i=0;i<5;i++){
System.out.println("主线程输出:"+i);
}
}
}
运行结果
主线程输出:0 主线程输出:1 子线程输出:0 主线程输出:2 子线程输出:1 主线程输出:3 子线程输出:2 主线程输出:4 子线程输出:3 子线程输出:4
可以看到多线程是并发的,两个线程之间的执行顺序是完全随机的,双方都不断争抢CPU执行,并且执行过程始终向前推进
继承Thread类的优点
- 编码相对简单
继承Thread类的缺点
- 自定义线程类继承了Thread类,导致无法再继承其他类,功能单一,不能继续拓展(单继承的局限性)
线程的注意事项
线程的启动必须调用start()方法
否则操作就会被当作普通类处理,按照代码顺序正常执行。 也就是说,如果直接调用自定义线程类的run方法,实际上就变成了普通类执行,此时只有一个主线程在运行
start()方法底层其实是给CPU注册当前线程,并且触发run()方法的执行
一般建议进程中先创建子线程,主线程的任务放在之后(否则程序按顺序执行,先接触到主线程内容,并没有发现后续的子线程启动,导致主线程必然先进行完,整个流程实际上仍然是顺序执行)
线程创建方式二:实现Runnable接口
- 创建一个线程任务类实现Runnable接口
- 重写run()方法
- 创建一个线程任务对象
- 将线程任务对象包装成线程对象
- 调用线程对象的start()方法启动线程
代码实现
package ThreadTest;
public class ThreadDemo4 {
public static void main(String[] args) {
//3. 创建"线程任务"对象(不是线程对象,只是执行线程任务的)
Runnable target=new MyRunnable();
//4. 把线程任务对象包装成线程对象
//Thread t=new Thread(target); 单纯创建线程对象
Thread t=new Thread(target,"TheFirstThread"); //利用线程任务创建线程对象的同时为线程命名
//启动线程
t.start();
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}
//1. 首先,创建线程任务类实现Runnable接口
class MyRunnable implements Runnable {
//2. 重写run()方法
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}
方式二的匿名内部类写法(简化写法)
package ThreadTest;
public class ThreadDemo4 {
public static void main(String[] args) {
Runnable target=new Runnable() {
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
};
Thread t=new Thread(target,"TheFirstThread");
t.start();
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}
方式二实现Runnable接口创建的优点
- 线程任务类只是实现了Runnable接口,所以可以继续继承其他类,而且可以继续实现其他接口,避免了单继承的局限性
- 同一个线程任务对象可以被包装成多个线程对象
- 适合多个相同的程序代码的线程去共享同一个资源
- 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立
- 线程池可以放入实现Runnable或Callable线程任务对象
方式二实现Runnable接口创建的缺点
- 代码较为复杂
- 继承的Runnable接口的run()方法没有返回值,不能直接得到线程执行的结果
注意:Thread类本身也实现了Runnable接口
线程创建方式三:实现Callable接口
- 定义一个线程任务类实现Callable接口,申明线程执行的结果类型
- 重写线程任务类的call方法,这个方法可以直接返回执行结果(解决了Runnable接口的缺点)
- 创建一个Callable的线程任务对象
- 把Callable的线程任务对象包装成一个未来任务对象
- 把未来任务对象包装成线程对象
- 调用现成的start()方法启动线程
代码实现:
package ThreadTest;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo5 {
public static void main(String[] args) {
//3. 创建Callable线程任务对象
Callable<String> call=new MyCallable();
//4. 把Callable任务对象包装成未来任务对象
//未来任务对象:本质就是一个Runnaable对象(FutureTask继承自Runnable)
//未来任务对象可以在线程执行完后得到线程执行结果
FutureTask<String> task=new FutureTask<String>(call);
//5. 把未来任务对象包装成线程对象
Thread t=new Thread(task,"TheFirstThread");
t.start();
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
//6. 最后获取线程执行结果,如果线程没有结果,会让出CPU等待线程执行完毕再来取值
try {
String rs=task.get(); //获取call方法返回的结果(正常/异常)
System.out.println(rs);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//1. 创建一个线程任务类实现Callable接口,申明线程返回的结果类型
class MyCallable implements Callable<String>{
//2. 重写线程任务类的call方法
@Override
public String call() throws Exception {
// 计算1~20的和
int sum=0;
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"==>"+i);
sum+=i;
}
return Thread.currentThread().getName()+"执行的结果是:"+sum;
}
}
运行结果
TheFirstThread==>0 main==>0 TheFirstThread==>1 main==>1 TheFirstThread==>2 main==>2 TheFirstThread==>3 main==>3 TheFirstThread==>4 main==>4 TheFirstThread==>5 main==>5 TheFirstThread==>6 main==>6 TheFirstThread==>7 main==>7 TheFirstThread==>8 main==>8 TheFirstThread==>9 main==>9 TheFirstThread==>10 main==>10 TheFirstThread==>11 main==>11 TheFirstThread==>12 main==>12 TheFirstThread==>13 main==>13 TheFirstThread==>14 main==>14 TheFirstThread==>15 main==>15 TheFirstThread==>16 main==>16 TheFirstThread==>17 main==>17 TheFirstThread==>18 main==>18 TheFirstThread==>19 main==>19 TheFirstThread执行的结果是:190
方法三,继承Callable接口优点
拥有方法二的全部优点,并且能够通过返回值了解线程是否正常运行,得到线程执行的结果
方法三,继承Callable接口缺点
编码复杂
线程常用API
package ThreadTest;
// 定义一个线程类继承Thread类
class MyThread2 extends Thread{
//重写run()方法
@Override
public void run() {
// 线程的执行方法
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+" : 子线程输出 :"+i);
}
}
}
public class ThreadDemo2 {
// 进程中一共包括两个子线程t1,t2与一个主线程
public static void main(String[] args) {
//创建线程对象
Thread t1=new MyThread2();
t1.setName("TheFirstThread"); //为线程命名
t1.start();
System.out.println(t1.getName()); //获取子线程的名字
Thread t2=new MyThread2();
t2.setName("TheSecondThread"); //为线程命名
t2.start();
System.out.println(t2.getName()); //获取子线程的名字
//获取主线程名称的方法
//利用currentThread()方法,可以获取当前所在的线程的对象
//(注意,这里说的是在哪个线程内部,而不是正在执行哪个线程,三个线程随机执行,但此时都在主线程内)
Thread m =Thread.currentThread();
m.setName("MainThread");
System.out.println(m.getName()); //从而就可获取到主线程名称
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+" :"+i);
}
}
}
运行结果
TheFirstThread
TheSecondThread
MainThread
MainThread :0
TheSecondThread : 子线程输出 :0
TheFirstThread : 子线程输出 :0
TheSecondThread : 子线程输出 :1
MainThread :1
TheSecondThread : 子线程输出 :2
TheFirstThread : 子线程输出 :1
TheSecondThread : 子线程输出 :3
MainThread :2
TheSecondThread : 子线程输出 :4
TheFirstThread : 子线程输出 :2
TheFirstThread : 子线程输出 :3
TheFirstThread : 子线程输出 :4
MainThread :3
MainThread :4
线程休眠方法:sleep()
让当前线程休眠指定时间(毫秒)后继续执行
package ThreadTest;
// 定义一个线程类继承Thread类
class MyThread3 extends Thread{
//重写run()方法
@Override
public void run() {
// 线程的执行方法
for(int i=0;i<5;i++){
try {
Thread.sleep(1000); //使子进程休眠1000ms再运行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" : 子线程输出 :"+i);
}
}
}
public class ThreadDemo3 {
public static void main(String[] args) {
//创建线程对象
Thread t1=new MyThread3();
t1.start();
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+" :"+i);
}
}
}
通过Thread类的含参构造器为线程命名
Thread t1=new MyThread("TheFirstThread");
// 需要重写自定义线程的方法(直接调用父类有参构造器即可)
public MyThread(String name){
super(name); //调用父类有参构造器并初始化线程对象的名称
}