室友打了一把王者,我就学会了Java创建线程的三种方式
Java创建线程的三种方式
Java中创建线程的方式有以下三种:
- 继承Thread类实现
run()方法 - 实现Runable接口
run()方法 - 实现Callable接口
call()方法
对于很多初学者来说,多线程的概念很抽象,不好理解,比如:
- 进程是操作系统分配资源的最小单位,一个进程可以包含多个线程。每个进程都有独立的代码和数据空间(程序上下文),并且通过进程间通信机制(IPC)进行通信。每个进程在内存中拥有独立的地址空间,一个进程崩溃或异常不会影响其他进程的正常运行。
- 线程是一种轻量级的进程,也可称为执行路径。线程是进程内的一个实体,更确切的说,线程是CPU调度的最小单位。同一个进程内的多个线程共享代码和数据空间,每个线程还有自己的栈空间和寄存器上下文,并且可以通过共享内存或互斥量等方式与其他线程进行通信,因此线程之间的开销远低于进程。
很抽象,对不对?打个比喻,你在打一把王者:
进程可以比作是你开的这一把游戏
线程可以比作是你所选的英雄或者是游戏中的水晶野怪等之类的。
带着这个比喻来理解进程和线程的一些关系,一个进程可以有多个线程就叫多线程。是不是感觉非常好理解了?
❤1、线程在进程下进行
(单独的英雄角色、野怪、小兵肯定不能运行)
❤2、进程之间不会相互影响,主线程结束将会导致整个进程结束
(两把游戏之间不会有联系和影响。你的水晶被推掉,你这把游戏就结束了)
❤3、不同的进程数据很难共享
(两把游戏之间很难有联系,有联系的情况比如上把的敌人这把又匹配到了)
❤4、同进程下的不同线程之间数据很容易共享
(你开的那一把游戏,你可以看到每个玩家的状态——生死,也可以看到每个玩家的出装等等)
❤5、进程使用内存地址可以限定使用量
(开的房间模式,决定了你可以设置有多少人进,当房间满了后,其他人就进不去了,除非有人退出房间,其他人才能进)
具体实现
搞清楚上面这些概念之后,我们来看一下多线程创建的三种方式:
继承Thread类
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"补刀==》 "+ i);
}
}
}
测试用例
public static void main(String[] args) {
MyThread a = new MyThread();
MyThread b = new MyThread();
//给两个线程设置名字,调用的是父类Thread的方法
a.setName("张三");
b.setName("李四");
a.start();
b.start();
}

继承Runable接口
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "补刀==》 " + i);
}
}
}
测试方法:
public static void main(String[] args) {
MyRunnable a = new MyRunnable();
MyRunnable b = new MyRunnable();
Thread t1 = new Thread(a, "张三");
Thread t2 = new Thread(b, "李四");
t1.start();
t2.start();
}

实现callable接口重写call()方法,可以通过FutureTask获取任务执行的返回值。
public class MyCallAble implements Callable {
@Override
public Object call() throws Exception {
return Thread.currentThread().getName();
}
}
测试代码:
public static void main(String[] args) {
//创建异步任务
FutureTask<String> task=new FutureTask<String>(new MyCallAble());
//启动线程
new Thread(task,"我是张三").start();
try {
//等待执行完成,并获取返回结果
String result=task.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}

总结
1、为什么要重写run方法?
因为run方法是用来封装被线程执行的代码,线程启动(调用start()方法后),会执行run()方法中的代码。
2、run()方法和start()方法有什么区别?
run():封装线程执行的代码,直接调用相当于调用普通方法。
start():启动线程,然后由JVM 调用此线程的 run() 方法。
3、通过继承 Thread 的方法和实现 Runnable 接口的方式创建多线程,哪个好?
实现Runable接口好,原因有两个:
①、避免了Java单继承的局限性
②、适合多个相同的程序代码去处理同一资源的情况,把线程、代码和数据有效的分离,更符合面向对象的设计思想。