多线程深入浅出
多线程实现方式
继承Thread类
一般不用这种方法实现,为什么后面会说
-
如何实现
-
- 自定义线程类继承Thread类
-
- 重写run方法,编写线程执行体
-
- 创建线程对象,调用start方法开启线程
public class Demo extends Thread{
//重写run方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程"+i);
}
}
public static void main(String[] args) {
//创建一个线程对象
Demo demo = new Demo();
//调用start方法开启线程
demo.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程"+i);
}
}
}
总结:线程开启后不一定立即执行,由CPU调度执行
实现Runnable接口
推荐使用,可以让一个对象同时被多个线程使用
-
如何实现
-
- 自定义线程类实现Runnable接口
-
- 重写run方法,编写线程执行体
-
- 创建实现类对象和代理类对象,通过代理类调用start方法开启线程
public class Demo implements Runnable{
//重写run方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程"+i);
}
}
public static void main(String[] args) {
//创建接口实现类对象
Demo demo = new Demo();
//创建代理类线程对象,通过线程对象来开启线程--》代理
Thread thread = new Thread(demo);
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程"+i);
}
}
}
其中
Thread thread = new Thread(demo);
thread.start();
也可以写成
new Thread(demo).start();
实现Callable接口
-
如何实现
-
- 自定义线程类实现Callable接口,需要返回值类型
-
- 重写call方法,编写线程执行体,需要抛出异常
-
- 创建实现类对象和代理类对象,通过代理类调用start方法开启线程
-
- 创建执行服务:
ExecutorService executorService = Executors.newFixedThreadPool(1);
- 创建执行服务:
-
- 提交执行:
Future<Boolean> result = executorService.submit(demo);
- 提交执行:
-
- 获取结果:
boolean r = result.get();
- 获取结果:
-
- 关闭服务:
executorService.shutdownNow();
- 关闭服务:
public class Demo implements Callable<Boolean> {
@Override
public Boolean call() {
for (int i = 0; i < 100; i++) {
System.out.println("线程"+i);
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Demo demo = new Demo();
//创建执行服务
ExecutorService executorService = Executors.newFixedThreadPool(1);
//提交执行
Future<Boolean> result = executorService.submit(demo);
for (int i = 0; i < 10; i++) {
System.out.println("主线程"+i);
}
//获取结果
boolean r = result.get();
//关闭服务
executorService.shutdownNow();
}
}
多线程理解
首先理解下面两句话
- 线程对象通过继承Thread类或者直接通过Thread创建的
- 线程要做的事(任务)通过重写Runnable接口的run方法定义的
如果只是创建了线程对象,却没有指定线程任务,那么这个线程是没有意义的,在程序上我们要实现线程对象和线程任务的绑定,就是让创建线程对象的那个类实现Runnable接口
我们看一下Thread类的源码
因为Thread类实现了Runnable接口,所以Thread类具备了执行线程任务的能力
但是线程不一定要执行任务,主要体现在Thread的构造器
有参构造这里传入的var1,就是我们上面说的任务
// 这就是没有任务的一个线程,无参构造
new Thread().start();
// 创建线程时,传入任务,有参构造
new Thread(()->{
for(int i=0; i<10; i++){
System.out.println(i);
}
}).start();
Thread的run()
方法理解
线程对象的线程任务是在run()方法里写的,前面说到线程是否执行由CPU调度决定,当线程开始执行任务时,调用Thread的run()方法。
这里的target就是我们传进来的任务
Thread类定义了一个成员变量private Runnable target;
,用来接收任务
创建对象的时候传入的任务var1
赋值给target
成员变量,线程执行的时候,操作target
看一下源码
执行init方法把var1传进去,给var2,注意这里var1已经变成var2了
调用自己的重载方法,继续把var2往里面传
为什么不直接调用run()
方法,而是调用start()
方法呢?
上面我们说到,线程对象调用start()
方法后,最终会调用run()
方法,那么为什么不直接调用run()
方法呢?
实际上,如果直接调用run()
方法,那么就变成主线程在直接调用run()
方法,也就是说还是主线程在执行方法,并不是多线程执行了,而调用start()
才会真正启动线程
至于为什么调用start()
才会真正启动线程,核心是start()
方法里的start0()
这个start0()
是一个本地方法,由JVM调用
为什么不推荐使用继承Thread的方式实现多线程?
第一,因为OOP只能单继承,有局限性,这是一个原因
第二,通过例子说明
首先,我们使用线程对象,肯定要执行任务,继承Thread类就可以定义自己的任务
public class Demo extends Thread{
//重写run方法
@Override
public void run() {
//要执行的任务
}
}
这样的方式直接把线程任务和线程对象绑定了,将来想让这个线程对象去做别的线程任务,还得修改代码,耦合度太高。
如果是通过实现接口来定义自己的任务呢
public class Demo1 implements Runnable{
//重写run方法
@Override
public void run() {
//吃饭
}
}
public class Demo2 implements Runnable{
//重写run方法
@Override
public void run() {
//打豆豆
}
}
public static void main(String[] args) {
Runnable r1 = new Demo1();
Runnable r2 = new Demo2();
//这时thread执行的是Demo1的任务:吃饭
Thread thread = new Thread(r1);
thread.start();
//想让thread执行别的任务,不需要去改thread代码,直接传入另一个任务即可
//这时thread执行的是Demo2的任务:打豆豆
thread = new Thread(r2);
thread.start();
}
那这种方法有没有缺点呢?
当然有,当任务很多时,要创建很多个类,很麻烦,怎么优化?
- 使用匿名内部类,减少类的定义
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//要执行的任务
}
});
thread.start();
}
- 再优化,关注方法实现,用lambda
public static void main(String[] args) {
Thread thread = new Thread(()->{
//要执行的任务,方法实现
});
thread.start();
}
sleep()
方法让哪个线程休眠?
看下面这段代码
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
for(int i=0; i<10; i++){
System.out.println("我是thread1");
}
});
thread1.start();
try{
thread1.sleep(1000);
}catch (InterruptedException e) {
e.printStackTrace();
}
Thread thread2 = new Thread(()->{
for(int i=0; i<10; i++){
System.out.println("我是thread2");
}
});
thread2.start();
}
第一个问题:这段代码是先输出我是thread1
还是我是thread2
呢?
答案:先输出我是thread1
第二个问题:sleep()
方法是不是让thread1
休眠了?
答案:没有,sleep()
方法让main主线程休眠了1s
这是怎么回事呢?
事实上,并不是哪个线程调用sleep()
方法,它就会休眠,而是看sleep()
写在哪里,与调用者无关!
那怎么样让thread1
休眠呢?把sleep()
写在它的任务执行体里就行
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
try{
Thread.sleep(1000);
}catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0; i<10; i++){
System.out.println("我是thread1");
}
});
thread1.start();
Thread thread2 = new Thread(()->{
for(int i=0; i<10; i++){
System.out.println("我是thread2");
}
});
thread2.start();
}