多线程
我们之前,学习的程序在没有语句跳转的前提下,都是由上自下依次执行,那现在向要设计一个程序,边打游戏,边听歌,如何设计?
要解决上述问题,就需要使用多进程或者多线程来解决。
1.线程:
主线程:执行main方法的线程
单线程程序:java中只有一个线程执行从main方法开始,从上到下依次执行,jvm执行main方法,main方法就会进入到栈内存,jvm就会找到操作系统开辟一条main方法通向cpu的执行路径,cpu就可以通过这个路径来执行main方法,而这个路径有一个名字,叫main线程
2.并发与并行
并发:指两个或者多个事件在同一时间段内发生
并行:指两个或者多个事件在同一时刻发生(同时发生)
在操作系统中,安装了多个程序,并发指的是在一段事件内宏观上有多个程序同时运行。在单cpu系统中,每一时刻只能有一道程序执行,即微观上这些程序都是分时交替运行的,只不过给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。
而在多个cpu系统中,则这些可以并发执行的程序便可以分配到多个处理器上(cpu)实现多任务并行执行,即利用每个处理器来处理一个可以并发的执行程序,这样多个程序便可以同时执行。目前电脑市场上说的多核cpu便是多核处理器,核越多,并行处理的程序越多,就能大大的提高电脑的运行效率。
注意: 单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个cpu上并发运行。同理,线程也是一样,从宏观的角度上理解线程是并行运行的,但是从微观角度上是串行运行的,即一个线程一个线程的去执行,当系统只有一个cpu时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。
3.进程与线程
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程,进程也是程序的一个执行过程,是系统运行程序的基本单位,系统运行一个程序即是一个进程从创建、运行到消亡的过程。
线程:线程是进程中一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程,一个进程中可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程。
4.线程调度:
分时调度:所有的线程轮流使用cpu,平均分配每个线程占用cpu的时间。
抢占式调度:优先让优先级高的线程使用cpu,如果线程的优先级相同,那么会随机选择一个(线程的随机性)。
java使用的是抢占式调度。
5.创建线程类
java 使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或者其子类的实例,每个线程的作用是完成一定的任务,实际上就是执行一段程序流,即一段顺序执行的代码。java使用线程执行体来代表这段程序流,java中通过继承Thread类可以创建并启动多线程。
步骤:
1.定义Thread类的子类,重写该类的run()方法,该run方法的方法体就代表了线程需要完成的任务,因此把run方法称为该线程的线程执行体。
2. 创建Thread子类的实例,即创建了线程对象 2. 调用线程对象的start() 方法,来启动线程,执行run方法。
代码示例:
自定义线程对象
public class MyThread extends Thread{
//定义指定线程名字的构造方法
public MyThread(String name){
super(name);
}
/*
重写run方法
*/
@Override
public void run(){
for(int i=0;i<10;i++){
System.out.println(getName()+"正在执行"+i);
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
//创建自定义线程对象 并给出名字
MyThread mt = new MyThread("新的线程");
//开启线程
mt.start();
//在main方法中执行一个for循环
for(int i=0;i<10;i++){
System.out.println("main线程正在执行"+i);
}
}
}
多线程原理
通过上述代码,我们发现在测试类中仅执行一次for循环,但是控制台中却出现了2个for循环的打印,多出来的就是另一个线程帮助我们完成的。
分析:
程序启动运行main的时候,java虚拟机启动一个进程,主线程在main()调用的时候被创建,随着调用mt对象的start方法,另一个线程也启动了,这样整个应用就在多线程下运行。
通过此张图我们可以很清晰的看到多线程的执行流程,那么为什么可以完成并发执行呢?
多线程执行时,到底在内存中是如何运行的?
多线程执行时,在栈内存中,其实每个执行线程都有一片自己所属的栈内存空间,进行方法的压栈和弹栈。
图示:
当执行线程的任务结束了,线程自动在栈内存中释放了,但是当所有的执行线程都结束了,那么进程就结束了。
Thread类 (实现线程的方式一)
java.lang.Thread类,api中定义了有关线程的一些方法,具体如下:
构造方法:
- public Thread() 分配一个新的线程对象
- public Thread(String name) 分配一个指定名字的新的线程对象
- public Thread(Runnable target) 分配一个带有指定目标的新的线程对象
- public Thread(Runnable target,String name) 分配一个带有指定目标的线程对象并指定其名字
常用方法:
- public String getName() 获取当前线程的名字
- public void start() 导致此线程开始执行,java虚拟机调用此线程的run方法
- public void run() 此线程要执行的任务在此处定义
- public static void sleep(long mills) 使当前在执行的线程以指定的毫秒数暂停(暂时停止执行)
- public static Thread currentThread() 返回当前正在执行的线程的对象引用
实现Runnable(实现线程方式二)
java.lang.Runnable 也是非常常见的一种实现线程的方式,我们只需要重写run方法即可。
归根结底,我们的线程对象必须是Thread类型,而我们第二种实现Runnable接口的方式去实现线程,也是要构建Thread类型的对象
如何实现?创建Runnable接口 实现类,把该实现类的对象作为Thread类型的构造方法中的参数Thread (Runnable target)
实现步骤:
1.定义Runnable接口的实现类,并重写run方法,该run方法的方法体才是真正的线程执行体
2.创建Runnable接口实现类的对象,并以此示例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
3.调用线程对象的strat方法来启动线程。
代码示例:
自定义Runnable接口实现类:
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
测试类:
public class Demo {
public static void main(String[] args) {
//创建自定义实现类对象 用来作为参数构建Thread线程对象
MyRunnable mr = new MyRunnable();
//真正的创建线程对象
Thread t = new Thread(mr, "张三");
//开启线程
t.start();
for(int i=0;i<10;i++){
System.out.println("李四"+i);
}
}
}
通过实现runnable接口,使得该类具有了多线程类的特征,run方法是多线程程序的一个执行目标,所有的多线程代码都在run方法里面,Thread类实际上也是实现了Runnable接口的类。(把Thread类作为线程类的标杆)
在启动多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target)创建Thread类型对象,然后调用Thread对象的start方法来运行多线程的代码。
实际上所有的多线程代码都是通过运行Thread的start方法来运行的,因此,不管是继承Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread对象的构造方法来创建Thread类型独享,并用Thread对象来控制线程的,熟悉Thread类的常用方法是进行多线程程序编程的基础。
注意:
Runnable类型对象仅仅作为Thread对象的target,runnable实现类里面包含的run方法仅作为线程执行体,而实际线程对象依然是Thread实例,只是该Thread线程负责执行target里面的run方法。
Thread和Runnable区别:
如果一个类继承Thread,则不适合资源共享,但是如果实现了Runnable接口的话,则很容易实现资源共享。
总结: 实现Runnable接口比继承Thread类具有的优势
1.适合多个相同的程序代码的线程去共享同一个资源。
2.可以避免java中单继承的局限性
3.增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
4.线程池只能放入实现runnable接口或callable接口的线程类,不能直接放入继承Thread的类。
思考:在java中,每次程序运行至少要运行几个线程?
至少要执行2个线程,一个是main线程,另一个是java垃圾回收机制。
匿名内部类方式实现线程的创建
使用线程的匿名内部类方式创建,可以方便的实现每个先测过执行不同的线程任务操作。
使用匿名内部类的方式实现runnable接口,重写runnable接口中的run方法:
代码示例:
public class NoNameInnerClassThread {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("李四"+i);
}
}
}).start();
for (int i=0;i<10;i++){
System.out.println("张三"+i);
}
}
}