一、概念
进程:操作系统当中正在执行的一个程序。例如正在运行一个QQ。
线程:进程之内多任务的执行单位。例如迅雷当中正在下载的多个电影。
JVM当中:栈(Stack)内存是线程独立的,堆(Heap)内存是线程共享的。
(1)Java程序运行的时候至少有两个线程:
1)主线程(main方法执行的所在线程)
2)垃圾回收线程(gc线程)
(2)Java当中的多线程采用抢占式调度:
多个线程之间无规则来回抢CPU,谁抢到了谁执行,谁没抢到活该,下次再抢。
无法精确控制多个线程之间来回切换的规律。
二、多线程原理
阅读以下程序:
// 自定义实现类
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("小强-" + i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// main方法
public class Demo01Thread {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
for (int i = 0; i < 100; i++) {
System.out.println("旺财-" + i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
程序流程图如下:
线程开启的内存图如下:
三、如何实现多线程的程序
- 继承类:java.lang.Thread类。
Thread类代表线程类,一个Thread对象就是一个线程,也就是一个任务执行的单位。 - 实现Runnable接口。
注:线程是操作系统当中极其宝贵的系统资源,一定要节省使用。
四、使用步骤
(一)继承Thread类
1. 定义一个类,继承Thread类。
2. 覆盖重写其中的run方法,用来指定线程任务内容。
public void run() {…}
3. 创建对象,调用.start()方法启动线程。
注:不要自己调用run,否则没有多线程的效果。
tips:
1. 可以通过下面的方法来获取当前线程的名称:
String name = Thread.currentThread().getName();
线程的命名如果没有指定,那么将会默认使用Thread-0、Thread-1进行依次命名。
2. 在run()方法中如果出现了异常只能通过try-catch来捕捉,这是因为Thread并未继承任何异常类,子类对异常的处理范围又必须小于父类,所以无法throws异常。
// 自定义线程类,继承Thread
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("小强-" + i);
}
}
}
// main方法
public class Demo01Thread {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}
}
(二)实现Runnable接口
1. 定义一个类实现Runnable接口。
2. 覆盖重写run()方法,指定任务内容。
3. 创建Runnable对象作为Thread的构造参数。
4. 调用start()方法,启动线程。
// 自定义实现类,实现Runnable接口
public class MyRunnableImpl implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 1; i <= 100; i++) {
System.out.println(name + " : " + i);
}
}
}
public class Demo04Runnable {
public static void main(String[] args) {
Runnable task = new MyRunnableImpl(); // 创建了Runnable实现类的对象。
new Thread(task).start(); // 没有定义线程名称,自动分配名称
new Thread(task, "我的线程").start(); // 指定了自定义的线程名称,用指定的。
}
}
注:推荐使用接口形式,实现Runnable接口比继承Thread类所具有的优势:
1. 适合多个相同的程序代码的线程去共享同一个资源。
2. 可以避免java中的单继承的局限性,但实现接口就没有限制。
3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
4. 线程池只能放入实现Runable或Callable接口线程,不能直接放入继承Thread的类。
5. Thread中功能很多,但Runnable中只有一个run()方法,使用起来更加简单。
五、使用匿名内部类实现线程的创建
// 使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法:
public class NoNameInnerClassThread {
public static void main(String[] args) {
// new Runnable(){
// public void run(){
// for (int i = 0; i < 20; i++) {
// System.out.println("张宇:"+i);
// }
// }
// }; //‐‐‐这个整体 相当于new MyRunnable()
Runnable r = new Runnable(){
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println("张宇:"+i);
}
}
};
new Thread(r).start();
for (int i = 0; i < 20; i++) {
System.out.println("费玉清:"+i);
}
}
}
匿名类的三种使用场景:
1. 可以适用于接口,覆盖重写抽象方法。
2. 也可以适用于抽象类,覆盖重写抽象方法。
3. 也可以适用于普通类,只要不是final的,就能覆盖重写。