多线程创建
程序、进程、线程的区别
程序:是为完成特定任务、用某种语言编写的一组指令的集合。
进程:是程序的一次执行过程,或是正在运行的一个程序。是一个动态
的过程,有它自身的产生、存在和消亡的过程(即生命周期)。
举例:运行中的QQ,360杀毒软件。
线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的。
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。
- 一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
举例:360运行时,同时进行病毒查杀,垃圾清理,优化加速等等功能时,每一个功能都是一个线程。
单核CPU和多核CPU的理解
- 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。但是因为CPU时间单元特别短,因此感觉不出来。也就是说看起来是同时进行,其实是在不停切换。
- 如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)。
- 一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
并行与并发
并行:多个CPU同时执行多个任务。
并发:一个CPU(采用时间片)同时执行多个任务。
多线程程序的优点
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
- 提高计算机系统CPU的利用率。
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。
多线程的创建的4种方式
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 线程池
①继承Thread类
步骤:
- 创建一个继承于Thread类的子类
- 重写Thread类的run() --> 将此线程执行的操作声明在run()中
- 创建Thread类的子类的对象
- 通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
//继承Thread类
public class Thread01 extends Thread {
@Override
//重写run方法
public void run() {
//this.getName()也可以
System.out.println(Thread.currentThread().getName()+ "多线程启动啦");
}
}
public class MyTest {
public static void main(String[] args) {
//创建Thread子类对象
Thread01 td = new Thread01();
//通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
td.start();
//td.run();
System.out.println(Thread.currentThread().getName()+"我是主程序");
}
}
结果:
main我是主程序
Thread-0多线程启动啦
注意:
1.我们启动一个线程,必须调用start(),不能调用run()的方式启动线程。
当我们把td.start()注释掉,换成td.run()。结果:
main多线程启动啦
main我是主程序
全都变成主线程执行了,这是因为通过run方法启动线程其实就是调用一个类中的方法,当作普通的方法的方式调用。并没有创建一个线程。只有start方法才会在启动线程的同时,创建线程。
2.如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象的start()。
我们在测试类中多次调用td.start()
结果:
swl.practise.thread.MyTest
Exception in thread "main" java.lang.IllegalThreadStateException
Thread-0多线程启动啦
at java.lang.Thread.start(Thread.java:708)
at swl.practise.thread.MyTest.main(MyTest.java:7)
原因:
Thread类中方法
void run() 将子线程要执行的操作写在方法体中。
void start() 启动线程,并且调用线程的run()方法。
①线程名称
String getName() 返回该线程的名称。
void setName(String name) 设置线程名称,使之与参数 name 相同。
如果不设置名字,线程也会有默认的名称。
②线程优先级
int getPriority() 返回线程的优先级。
void setPriority(int newPriority) 更改线程的优先级。
线程优先级的范围由高到低为1-10,默认优先级为5,高优先级的线程并不是会一定被执行,只是抢占CPU执行权的几率变大。
③守护线程与用户线程
boolean isDaemon() 测试该线程是否为守护线程。
void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
Java平台把操作系统的底层进行了屏蔽,在JVM虚拟平台里面构造出对自己有利的机制,这就是守护线程的由来。Daemon的作用是为其他线程的运行提供服务,比如说GC线程。如果用户线程结束,守护线程也会退出,因为他没什么好服务的了。setDaemon()方法必须在线程启动之前执行。
public final native boolean isAlive() 判断当前线程是否存活。
public static native Thread currentThread(); 获取当前执行的分线程。子线程中可以直接用this关键字,就代表当前线程。
④线程通信
static void sleep(long millis) 线程睡眠指定毫秒,时间结束后,线程结束阻塞状态。
void interrupt() 中断线程。这个之后再看,目前没时间。
static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
void join() 等待该线程终止。
从Object类继承来的方法 void notify() void wait()
②实现Runnable接口
步骤
- 创建一个实现了Runnable接口的类。
- 实现类去实现Runnable中的抽象方法:run()。
- 创建实现类的对象。
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象。
- 通过Thread类的对象调用start()。
public class Thread02 implements Runnable {
@Override
public void run() {
//这里不能this.getName() 因为接口里没有~~~~~
System.out.println(Thread.currentThread().getName() + "启动了");
}
}
public class MyTest {
public static void main(String[] args) {
Thread02 thread02 = new Thread02();
//创建多个子线程,只需要用同一个子类对象就可以了
//可以在创建时直接给线程起名字,继承Thread类也可以,不过需要在子类中提供参数为name的构造器
Thread td1 = new Thread(thread02,"线程一号");
Thread td2 = new Thread(thread02,"线程二号");
td1.start();
td2.start();
}
}
结果
swl.practise.thread.MyTest
线程二号启动了
线程一号启动了
③实现callable接口
步骤
- 创建Callable子类的实例化对象
- 创建FutureTask对象,并将Callable对象传入FutureTask的构造方法中(注意:FutureTask实现了Runnable接口和Future接口)
- 实例化Thread对象,并在构造方法中传入FurureTask对象
- 启动线程
/*
* 实现Callable接口创建子线程,指明范型为返回的数据类型
* */
public class CallDemo implements Callable<String> {
@Override
public String call() throws Exception {
for (int i=0; i < 100 ; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
return "执行完毕";
}
}
public class TestCallable {
public static void main(String[] args) {
CallDemo cl = new CallDemo(); // 实例化Callable子类对象
//要几个线程就要几个FutureTask对象哦
FutureTask<String> ft1 = new FutureTask<String>(cl); // 实例化FutureTask对象,并将Callable子类对象传入FutureTask的构造方法中
FutureTask<String> ft2 = new FutureTask<String>(cl); // 实例化FutureTask对象,并将Callable子类对象传入FutureTask的构造方法中
Thread t1 = new Thread(ft1, "1号线程输出——>"); // 启动线程
Thread t2 = new Thread(ft2, "2号线程输出——>"); // 启动线程
t1.start();
t2.start();
}
}
Callable接口相较于Runnable接口的好处:
1.重写的call()相较于run()可以返回值
2.返回值的类型可以通过泛型的方式指定
3.重写的call()可以throws的方式处理异常
④线程池
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class ThreadPool {
public static void main(String[] args) {
// 1.调用Executors的newFixedThreadPool(),返回指定线程数量的ExecutorService
ThreadPoolExecutor pool = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
//设置管理参数
pool.setMaximumPoolSize(20);
// 2.将Runnable实现类的对象作为形参传递给ExecutorService的execute()方法中,开启线程
// 并执行相关的run()
pool.execute(new MyThread());
pool.execute(new MyThread());
pool.execute(new MyThread());
// 3.结束线程的使用
pool.shutdown();
}
}
使用线程池的好处
1.提高响应速度(减少了创建新线程的时间
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没任务时最多保持多长时间后会终止
面试
继承Thread类和实现Runnable接口对比
实现Runnable的方式相较于继承Thread类的方式更合适一些
① 实现的方式避开了类的单继承性的局限性
② 实现的方式更适合处理多个线程共享数据的情况(继承Thread类如果想实现资源共享,需要给共享的变量加static关键字)
联系:public class Thread implements Runnable
相同点:
① 创建的都是线程类的对象
② 启动线程,都是调用的Thread类中的start()
说一下 runnable 和 callable 有什么区别?
Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;
Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
线程的 run()和 start()有什么区别?
每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。
start()方法来启动一个线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码; 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。
run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。