目录
3.1 public Thread(Runable target)
1.线程与进程
进程可以看成是一段程序的执行过程。线程可以理解为在进程中独立运行的子任务,线程依托与进程而存在,一个进程中可以有多个线程,共用进程的资源,而进程负责向操作系统申请资源。因此先有进程,后有线程,进程之间相互独立。
在上图中将进程定义为资源和指令执行序列的总和,这里可以将指令执行序列理解为程序。下图为进程的大致结构:
注意:1.进程之间虽然是相互独立的。但进程与进程之间可以互相通信,例如使用Socket或这Http。
2.进程拥有某些共享的资源,例如内存、端口等,供其内部的线程使用。
3.进程是个很大的单位,不是轻量级的,因为进程的创建需要操作系统分配资源,会占用内存。
4.虽然线程更轻,但线程上下文切换的时间成本更高。
5.多线程可以提高CPU的利用率。
图1.1 进程与线程
-------------------------------------------------------------------------------------------------------------------------------=
case1:同步:可以理解为排队执行。
case2:异步:不等任务执行完,直接执行下一个任务。(参考操作系统中的多道程序模型)
case2:多线程是异步的
上图为进程状态图,前面说过,线程相当于进程里面独立运行的子任务。线程的结构大致如下图所示:
-------------------------------------------------------------------------------------------------------------------------------
线程ID(线程标识符)。线程唯一的标识,同一个进·程内不同线程的ID不会重复。
线程名称。主要方便用户识别,用户可以指定线程的名字,如果没有指定,系统就会自动分配一个名称。(参考4.4.方法currentThread())
线程优先级。表示线程调度的优先级,优先级越高,获得CPU的执行机会越大。(这里是概率问题,不是优先级高的就一定比优先级低的先执行完)。
线程状态。表示当前线程的执行状态。为新建、就绪、运行、阻塞、结束等状态中的一种。(这个在Java的源码里面是被定义成了一个State的枚举类,如下图:)
************************************************************************************************************
因此,线程与进程的区别主要有以下几点:
(1) 线程是“进程代码段”的一次顺序执行流程。一个进程由一个或多个线程组成,一个进程至少有一个线程。
(2) 线程是CPU调度的最小单位。进程是操作系统分配资源的最小单位。线程的划分尺度小于进程,使得多线程程序的并发性高。
(3)线程是出于高并发的调度诉求从进程内部演进而来的。线程的出现既充分发挥了CPU的计算性能,又弥补了进程调度过于笨重的问题。
(4) 进程之间是相互独立的,但进程内部的各个线程之间并不完全独立。各个线程之间共享进程的方法区内存、堆内存、系统资源(文件句柄、系统信号等)。
(5)切换速度不同:线程上下文切换比进程上下文切换要快得多。所以,有的时候,线程也称为“轻量级进程”。
2.Thread类
一个进程在运行时至少会有一个线程在运行。如下 Java代码示例:
这里调用的是main()函数,在控制台输出的是main,其实就是名为main的线程在执行main函数中的代码,main线程是有JVM创建的。
2.1如何实现多线程?
方法一:继承Thread类
方法二:实现Runable接口
2.2 Thread类概述
如上图,从源码看,Thread类实现了Runable接口,因此,他们具有多态关系,即可以这样创建对象:
Runnable run=new Thread();
但使用 继承Thread这种方式来创建新线程的弊端之一就在这里,由于Java只支持单继承,不支持多继承。所以,Thread不支持多继承。
因此为了支持多继承效果,可以通过实现Runnable接口的方式,来创建新线程。
2.3利用继承Thread类的方式来创建新线程
示例代码如下:
package org.example.threadTest1.node1;
import java.lang.Thread;
public class TestThread1 extends Thread{
@Override
public void run(){
super.run();
System.out.println("Thread1");
}
}
public class Test3 {
public static void main(String[] args) {
TestThread1 thread1=new TestThread1();
thread1.start();
System.out.println("******************************************");
}
}
如上示例:使用继承Thread类这种方式来创建新线程时,应当重写run方法。上面的代码使用start()来启动一个线程(注意:不要调用run()方法),线程启动后,会自动调用线程对象中的run()方法,在run()方法里面可以放线程对象需要执行的任务,也就时线程执行任务的入口。
如上图,是示例测试类的运行结果,但为什么会产生如上运行结果(如果程序顺序执行)?
从运行结果来看,TestThread1类的run方法相对于系统输出”***********************“的执行时间晚,这是为啥呢?因为执行start()方法比较耗时。
运行结果是概率事件, 方法start()执行耗时多的原因是内不执行了多个步骤,步骤如下:
T1:通过JVM告诉系统创建Thread.
T2:操作系统开辟内存,同时,使用Window SDK中的createThread()函数创建Thread的线程对象。
T3:操作系统对Thread对象进行调度,以确定执行时机。
T4:Thread在操作系统中被成功执行。
线程执行的顺序具有随机性。多线程执行的随机性是因为每个线程被分配一个时间片,线程在获得时间片(时间片:CPU分配给各个程序的时间)后就执行任务。
注意:CPU在不同的线程上切换时是需要耗时的(这是一个异步过程),因此,并不是创建的线程越多,软件运行效率就越快。相反,线程数过多反而会降低软件的执行效率。
2.4为啥调用start()方法,而不是run()方法?
注意前面的描述,线程的执行过程是异步,随机的。如果调用run()方法而不是start()方法这样就不是异步执行的了,而是同步执行,那么线程对象就不会交给线程规划器来处理,而是有main()方法来直接调用run()方法,也就是说必须要等到run()方法中的代码执行完毕后才可以执行后面的代码。
因此,执行start()方法的顺序不代表是执行run()方法的顺序,方法run()是随机调用的。
3.实现Runable()接口
试想一下这种情况:
如果想创建的线程类如果已经有一个父类了该怎么办?
这时自然就不能选择继承Thread()类这种方式,因为Java不支持多继承。
此时就需要实现Runable接口来解决问题。
如上所述,使用继承Thread类的方法来开发多线程应用是有局限的,因为Java只支持单继承不支持多继承。
因此,为了改变这种限制,更推荐使用实现Runable接口这种方式来实现多线程。
如下是通过实现Runable接口来实现多线程的示例代码:
首先是A业务类:
public class AServer {
public void a_server_method(){
System.out.println("执行A业务的方法!");
}
}
然后是B业务类,它继承自A业务类,这时,如果想在B业务类中实现多线程,就不能使用继承Thread类这种方式来实现,此时让他实现Runable接口,这样可以间接实现多继承的效果。如下:
public class BServer1 extends AServer implements Runnable{
public void b_server_method(){
System.out.println("执行B业务的方法!");
}
@Override
public void run() {
b_server_method();
}
}
测试类与运行结果:
另外,使用实现Runable接口的方式还可以把“线程”与"任务"分离,实现解耦的效果。参考:
Java多线程实践2-Thread类与Runable接口-CSDN博客
Thread代表进程,Runable表示可运行的任务,Runable里面包含Thread线程要执行的代码,这样处理可以实现多个Thread共用一个任务,也可以一个任务被多个线程共用。
3.1 public Thread(Runable target)
这是Thread类的关键源码:
4.线程交互浅入
自定义线程类中的实例变量针对其他线程有共享和不共享之分。
4.1不共享数据的情况
如下示例代码:
public class TestThread3 extends Thread{
private int count=5;
public TestThread3(String name){
super();
this.setName(name); //将此设置为线程的名称
}
@Override
public void run() {
super.run();
while (count>0){
count--;
System.out.println("由"+this.currentThread().getName()
+"计算: count="+count);
}
}
}
其运行测试类代码如下:
public class RunTest {
public static void main(String[] args) {
TestThread3 run1=new TestThread3("A");
TestThread3 run2=new TestThread3("B");
TestThread3 run3=new TestThread3("C");
run1.start();
run2.start();
run3.start();
}
}
其运行结果如下图所示:
由此运行结果推断,每个线程都有各自的count变量,自己对自己的count的值进行减少。这种情况就是变量不共享的情况。此示例不存在多个线程访问同一个实例变量的情况。
如果要实现多个线程访问一个实例变量的情况该怎么做呢?
4.2共享数据的情况
该情况就是多个线程可以访问同一个变量,如在实现投票业务,多个线程需要处理同一个人的票数;还有那种上商品秒杀业务中,需要处理统计商品商量。
下面代码示例演示共享数据的这种情况:
public class TestThread4 extends Thread{
private int count=5;
@Override
public void run() {
super.run();
count--;
System.out.println("由"+this.currentThread().getName()+"计算:count"+count);
}
}
运行测试函数:
public class RunTest2 {
public static void main(String[] args) {
TestThread4 thread4=new TestThread4();
Thread a=new Thread(thread4,"A");
Thread b=new Thread(thread4,"B");
Thread c=new Thread(thread4,"C");
Thread d=new Thread(thread4,"D");
Thread e=new Thread(thread4,"E");
a.start();
b.start();
c.start();
d.start();
e.start();
}
}
运行结果:
这里先埋一个坑,什么叫线程安全?什么叫非线程安全?
4.4.方法currentThread()
本方法返回代码端正在被哪个线程调用。具体实例见上面的示例中的代码。
4.5.方法isAlive()
本方法如字面意思,用于判断线程对象是否存活。参考:
为什么启动一个线程不直接调用run(),而要调用start()启动?_线程的启动是调run吗_司马万的博客-CSDN博客
该方法由native关键字修饰,所以,这个方法具体实现是用其他语言写的。
示例代码如下:
public class Test5Thread extends Thread {
@Override
public void run() {
System.out.println("本线程是/否存活"+this.isAlive());
}
}
测试函数:
public class RunTest3 {
public static void main(String[] args) {
Test5Thread thread=new Test5Thread();
System.out.println("begin(线程启动前)=="+thread.isAlive());
thread.start();
System.out.println("after(线程启动后)=="+thread.isAlive());
}
}
运行结果如下:
本方法的作用是测试线程是否处于活动状态。那么什么是活动状态(注意:这个和运行状态是有区别的,可以与软件的生命周期进行对比)?及线程已经启动,但线程并未终止。要获取线程当前的确切状态,这需要调用getState()方法。
4.6 sleep()方法
本方法的作用,顾名思义,就是在指定毫秒数内让当前(正在执行的线程)线程休眠(暂停执行),这个线程是指this.currentThread()返回的线程。
示例代码如下:
public class TestThread6 extends Thread{
@Override
public void run() {
super.run();
System.out.println("run threadName="+this.currentThread().getName()+"begin");
try {
Thread.sleep(3000);
System.out.println("run threadName="+this.currentThread().getName()+"end");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
测试类1(注意,这里调用的是run()方法,根据运行结果可以看出,此时是同步(顺序)运行的):
测试类2:(这里调用的是start()方法)