上一篇:并发基础概述:https://blog.csdn.net/pcwl1206/article/details/84833911
目 录:
1、什么是线程
线程(Thread)是一个对象(Object)。现代操作系统调度的最小单元就是线程。Java 线程是 Java 进程内允许多个同时进行的任务。该进程内并发的任务称为为线程(Thread),一个进程里至少一个线程。
在一个进程里可以创建多个线程,这些线程都拥有各自的程序计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。
1.1 多线程
Java 程序采用多线程方式来支持大量的并发请求处理,程序如果在多线程方式执行下,其复杂度远高于单线程串行执行。那么多线程指的就是这个程序(一个进程)运行时产生了不止一个线程。
- 为什么要使用多线程?
1、适合多核处理器:一个线程运行在一个处理器核心上,那么多线程可以分配到多个处理器核心上,更好地利用多核处理器;
2、更快的响应时间:将数据一致性不强的操作使用多线程技术(或者消息队列)加快代码逻辑处理,缩短响应时间;
3、更好的编程模型
- 并发与并行的区别:
1、并发:类似单个 CPU ,通过 CPU 调度算法等,处理多个任务的能力;
2、并行:类似多个 CPU ,同时并且处理相同多个任务的能力;
2、线程的创建
Java创建线程对象有两种方法:
1、继承Thread类创建线程对象;
2、实现Runnable接口创建线程对象。
2.1、Thread和Runnable简介
- Runnable:
Runnable是一个接口,该接口中只包含了一个run()方法,它的定义如下:
public interface Runnable{
public abstract void run();
}
Runnable的作用是实现多线程。我们可以定义一个类A实现Runnable接口;然后,通过new Thread(new A())等方式新建线程。
- Thread
Thread是一个类,它本身就实现了Runnable接口。它的声明如下:
public class Thread implements Runnable{
// ...
}
Thread的作用是:实现多线程。
2.2、Thread和Runnable的异同点
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runnable接口的话,很容易实现资源共享。
实现Runnable接口比继承Thread类具有以下优势:
1、可以避免Java中的单继承问题,Runnable的可扩展性更好;
2、Runnable还可以用于“资源共享”。即,多个线程都是基于某一个Runnable对象建立的,它们会共享Runnable对象上的资源。
2.3、Thread和Runnable的多线程示例
- Thread的多线程示例
public class MyThread extends Thread {
private int ticket = 10;
public void run(){
for(int i = 0; i < 20; i++){
if(this.ticket > 0){
System.out.println(this.getName() + " 卖票:ticket" + this.ticket--);
}
}
}
public static void main(String[] args) {
// 启动三个线程,每个线程各卖10张票
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
}
}
运行结果:
结果说明:
1、MyThread继承于Thread,它是自定义线程。每个MyThread都会卖出10张票。
2、 主线程main创建并启动3个MyThread子线程。每个子线程都各自卖出了10张票。
- Runnable的多线程示例
public class MyThread implements Runnable {
private int ticket = 10;
public void run(){
for(int i = 0; i < 20; i++){
if(this.ticket > 0){
System.out.println(Thread.currentThread().getName() + " 卖票:ticket" + this.ticket--);
}
}
}
public static void main(String[] args) {
MyThread mt = new MyThread();
// 启动三个线程,每个线程各卖10张票
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
Thread t3 = new Thread(mt);
t1.start();
t2.start();
t3.start();
}
}
运行结果:
结果说明:
1、 和上面“MyThread继承于Thread”不同,这里的MyThread实现了Runnable接口。
2、 主线程main创建并启动3个子线程,而且这3个子线程都是基于“mt这个Runnable对象”而创建的。运行结果是这3个子线程一共卖出了10张票。这说明它们是共享了MyThread接口的。
- 需要注意的几个点:
1、main方法也是一个线程。在Java中所有的线程都是同时启动的,至于哪个先执行、什么时候执行,完全看谁先得到CPU资源了;
2、在Java中,每次程序至少会启动2个线程。一个是main线程,另一个是垃圾收集线程。因为每当使用Java命令执行一个类的时候,实际上都会启动一个Jvm,每一个Jvm实际上就是在操作系统中启动了一个进程。
3、Run方法和Start方法的区别
1、start():它的作用是启动一个新线程,新线程启动后会执行相应的run()方法;start()方法不能被重复调用;
2、run():和普通的成员方法一样,可以被重复调用。单独调用run()的话,会在当前线程中执行run()方法,而不会启动新线程。
public class MyThread extends Thread{
public MyThread(String name){
super(name);
}
public void run(){
System.out.println(Thread.currentThread().getName() + " is running");
}
public static void main(String[] args) {
Thread myThread = new MyThread("myThread");
System.out.println(Thread.currentThread().getName() + " call myThread.run()");
myThread.run();
System.out.println(Thread.currentThread().getName() + " call myThread.start()");
myThread.start();
}
}
运行结果:
结果说明:
1、Thread.currentThread().getName()是用于获取“当前线程”的名字。当前线程是指正在cpu中调度执行的线程。
2、 myThread.run()是在“主线程main”中调用的,该run()方法直接运行在“主线程main”上。
3、myThread.start()会启动“线程mythread”,“线程mythread”启动之后,会调用run()方法;此时的run()方法是运行在“线程myThread”上。
- Thread.java中start()方法的源码:
public synchronized void start() {
// 如果线程不是"就绪状态",则抛出异常!
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 将线程添加到ThreadGroup中
group.add(this);
boolean started = false;
try {
// 通过start0()启动线程
start0();
// 设置started标记
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
说明:start()实际上是通过本地方法start0()启动线程的。而start0()会新运行一个线程,新线程会调用run()方法。
private native void start0();
- Thread.java中run()方法的源码码如下:
public void run() {
if (target != null) {
target.run();
}
}
说明:target是一个Runnable对象。run()就是直接调用Thread线程的Runnable成员的run()方法,并不会新建一个线程。
- 线程的运行
在运行上面两种创建线程的代码后,JVM 执行了 main 函数线程,然后在主线程中执行创建了新的线程。正常情况下,所有线程执行到运行结束为止。除非某个线程中调用了 System.exit(1) 则被终止。
在实际开发中,一个请求到响应式是一个线程。但在这个线程中可以使用线程池创建新的线程,去执行任务。
4、线程的状态
首先推荐一篇文章:Java线程到底有多少种状态
本人也查看了Thread的源码,源码中定义了6种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。
新建 MyThread类,打印线程对象属性,代码如下:
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("MyThread的线程实例正在执行任务");
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
System.out.print("MyThread的线程对象 \n"
+ "线程唯一标识符:" + thread.getId() + "\n"
+ "线程名称:" + thread.getName() + "\n"
+ "线程状态:" + thread.getState() + "\n"
+ "线程优先级:" + thread.getPriority());
}
}
运行结果:
线程是一个对象,它有唯一标识符 ID、名称、状态、优先级等属性。线程只能修改其优先级和名称等属性 ,无法修改 ID 、状态。ID 是 JVM 分配的,名字默认也为 Thread-XX,XX是一组数字。线程初始状态为 NEW。
线程优先级(priority)的范围是 1 到 10 ,其中 1 是最低优先级,10 是最高优先级,默认的优先级是5。不推荐改变线程的优先级,如果业务需要,自然可以修改线程优先级到最高,或者最低。可以通过:setPriority(int)方法来修改优先级。但是需要说明的是操作系统可能不会理会你设置的优先级,因此,程序的正确性不能依赖线程的优先级高低。
线程的状态实现通过 Thread.State 常量类实现,有 6 种线程状态:new(新建)、runnnable(可运行)、blocked(阻塞)、waiting(等待)、time waiting (定时等待)和 terminated(终止)。
状态名称 | 说明 |
---|---|
NEW | 初始状态,线程被构建,但是还没有调用start()方法 |
RUNNABLE | 运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称为“运行中” |
BLOCKED | 阻塞状态,表示线程阻塞与锁 |
WAITING | 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断) |
TIME_WAITING | 超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的 |
TERMINATED | 终止状态,表示当前线程已经执行完毕 |
状态转换图如下
线程状态流程大致如下:
1、新建了一个线程对象,进入new状态。例如,Thread thread = new Thread();
2、runnable叫“就绪状态”。线程新建后,其他线程(比如main线程)调用了该对象的start()方法,从而来启动该线程。Runnable状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权;
3、runnable叫”可运行状态“的线程获得了CPU时间片(timeslice),执行代码。需要注意的是,线程只能从就绪状态进入到运行状态;
4、如果线程执行sleep、wait、join方法或者IO阻塞将会进入wait状态或者blocked状态;
5、线程执行完毕后,线程被线程队列移除。最后为terminated状态。
上一篇:并发基础概述:https://blog.csdn.net/pcwl1206/article/details/84833911
参考及推荐:
1、并发基础与Java多线程:https://blog.csdn.net/a724888/article/details/60867044
2、Java多线程系列目录:https://www.cnblogs.com/skywang12345/p/java_threads_category.html