文章目录:
2.5 使用Callable接口实现多线程(不再举例......)
1.概述
在一个操作系统中,每个独立执行的程序都可称之为一个进程,也就是“正在运行的程序”。例如下面这张图中:👇👇👇
在多任务操作系统中,表面上看是支持进程并发执行的,例如可以一边听音乐一边聊天,但实际上这些进程并不是在同一时刻运行的。在计算机中,所有应用程序都是由CPU执行的,对于一个CPU而言,在某个时间点只能运行一个程序,也就是说只能执行一个进程,操作系统会为每个进程分配一段有限的CPU使用时间,CPU在这段时间中执行某个进程,然后会在下一段时间切换到另一个进程中去执行。由于CPU运行速度非常快,能在极短的时间内在不同的进程之间进行切换,所以给人以同时执行多个程序
的感觉。
定义:在多任务操作系统中,每个运行的程序都是一个进程,用来执行不同的任务,而在一个进程中还可以有多个执行单元同时运行,来同时完成一个或多个程序任务,这些执行单元可以看做程序执行的一条条线索,被称为线程。
注意:操作系统中的每一个进程中都至少存在一个线程,当一个Java程序启动时,就会产生一个进程,该进程中会默认创建一个线程,在这个线程上会运行main()方法中的代码。
1.2.1 单线程与多线程
单线程都是按照调用顺序依次往下执行,没有出现多段程序代码交替运行的效果,而多线程程序在运行时,每个线程之间都是独立的,它们可以并发执行。
多线程可以充分利用CUP资源,进一步提升程序执行效率。
多线程看似是同时并发执行的,其实不然,它们和进程一样,也是由CPU控制并轮流执行的,只不过CPU运行速度非常快,故而给人同时执行的感觉。
2.线程的创建
2.1 线程的生命周期
· 新建状态:
使用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新建状态,此时它不能运行,和其他Java对象一样,仅仅由JVM为其分配了内存,没有表现出任何线程的动态特征。它保持这个状态直到程序start()这个线程。
· 就绪状态:
当新建状态的线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
· 运行状态:
如果就绪状态的线程获取CPU资源(获得JVM调度),就可以执行run(),此时线程便处于运行状态(如果存在多个CPU,那么允许多个线程并行运行)。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
· 阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
· 等待阻塞:运行状态中的线程执行wait()方法,使线程进入到等待阻塞状态。
· 同步阻塞:线程在获取synchronized同步锁失败(因为同步锁被其他线程占用)。
· 其他阻塞:通过调用线程的sleep()或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
· 死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态(生命周期结束)。
说明:Thread类是java.lang包下的一个线程类,用来实现Java多线程。
步骤:①创建一个Thread线程类的子类(子线程),同时重写Thread类的run()方法;
②创建该子类的实例对象,并通过调用start()方法启动线程。
下面来举几个例子帮助大家理解:👇👇👇
//MyThread类继承Thread线程类 class MyThread extends Thread { @Override public void run() { for(int i=0;i<10;i++) { System.out.println(getName() + ":" + i); } } } public class Thread01 { public static void main(String[] args) { //创建多线程对象 MyThread th1=new MyThread(); MyThread th2=new MyThread(); MyThread th3=new MyThread(); //设置每个对象的名字 th1.setName("Hello"); th2.setName("World"); th3.setName("JavaProject"); /*调用start()方法启动线程 *其内部调用了run()方法,实现多线程 */ th1.start(); th2.start(); th3.start(); } }
在这里可以看到,我们创建了3个线程,并且分别命名为:Hello、World和JavaProject,但是在运行结果中,并不是按照这样的顺序来执行的,顺序是完全打乱的。
这是因为Java程序属于抢占式调度,这3个线程是抢着执行的,这也就是多线程的体现。而且在你每次运行这段多线程代码的时候,你会发现运行结果并不一样,这是因为每次运行时,这3个线程多会去抢夺CPU资源来执行,就一个字:抢!!!
在上面这两张图中,第一张里面的实例方法是由Thread类的对象调用的,第二张里面的静态方法是直接由Thread类直接调用的!!!
说明:Java只支持类的单继承,如果某个类已经继承了其他父类,就无法再继承Thread类来实现多线程。在这种情况下,就可以考虑通过实现Runnable接口的方式来实现多线程。
步骤:①创建一个Runnable接口的实现类,同时重写接口中的run()方法;
②创建Runnable接口的实现类对象;
③使用Thread有参构造方法创建线程实例,并将Runnable接口的实现类的实例对象作为参数传入;
④调用线程实例的start()方法启动线程。
//MyRunnable类继承实现Runnable接口实现多线程 class MyRunnable implements Runnable { @Override public void run() { for(int i=0;i<10;i++) { //Runnable接口中没有getName()方法 //这里使用Thread线程类中的currentThread()方法进而通过getName()方法获取线程对象 System.out.println(Thread.currentThread().getName() + ":" + i); } } } public class Thread02 { public static void main(String[] args) { //创建一个接口子类对象 MyRunnable mr=new MyRunnable(); //使用Thread线程类的有参构造方法创建线程实例 //将Runnable接口实现类的实例对象作为参数传入 Thread th1=new Thread(mr,"Hello"); Thread th2=new Thread(mr,"World"); Thread th3=new Thread(mr,"JavaProject"); //调用线程实例的start()方法启动线程 th1.start(); th2.start(); th3.start(); } }
注意使用Runnable接口实现多线程的一些方法,在这里的3个线程同样是抢着去运行的!!!
①适合多个相同的程序代码的线程去共享同一个资源。
②可以避免Java中的单继承的局限性。
③增加程序的健壮性,实现解耦(把设置线程和开启线程分开)操作,代码可以被多个线程共享,代码和线程独立。
④线程池只能放入实现Runnable接口或Callable接口的线程,不能直接放入继承Thread类的线程。
2.5 使用Callable接口实现多线程(不再举例......)
说明:通过Thread类和Runnable接口实现多线程时,需要重写run()方法,但是由于该方法没有返回值,因此无法从多个线程中获取返回结果。为了解决这个问题,从JDK 5开始,Java提供了一个新的Callable接口,来满足这种既能创建多线程又可以有返回值的需求。
使用:Callable接口实现多线程是通过Thread类的有参构造方法传入Runnable接口类型的参数来实现多线程,不同的是,这里传入的是Runnable接口的子类FutureTask对象作为参数,而FutureTask对象中则封装带有返回值的Callable接口实现类。
步骤:①创建一个Callable接口的实现类,同时重写Callable接口的call()方法。
②创建Callable接口的实现类对象。
③通过FutureTask线程结果处理类的有参构造方法来封装Callable接口实现类对象。
④使用参数为FutureTask类对象的Thread有参构造方法创建Thread线程实例。
⑤调用线程实例的start()方法启动线程。
3.线程的调度
定义:程序中的多个线程是并发执行的,但并不是同一时刻执行,某个线程若想被执行必须要得到CPU的使用权。Java虚拟机会按照特定的机制为程序中的每个线程分配CPU的使用权,这种机制被称作线程的调度。
①分时调度模型:是指让所有的线程轮流获得CPU的使用权,并且平均分配每个线程占用的CPU时间片。
②抢占式调度模型(JVM默认方法):是指让可运行池中所有就绪状态的线程争抢CPU的使用权,而优先级高的线程获取CPU执行权的概率自然大于优先级低的线程。
在应用程序中,要对线程进行调度,最直接的方式就是设置线程的优先级。优先级越高的线程获得CPU执行的机会越大,而优先级越低的线程获得CPU执行的机会越小。
线程的优先级用1~10之间的整数来表示,数字越大优先级越高。
除了可以直接使用数字表示线程的优先级,还可以使用Thread类中提供的三个静态常量表示线程的优先级。
static int MAX_PRIORITY:表示线程的最高优先级,相当于值10 static int MIN_PRIORITY:表示线程的最低优先级,相当于值1 static int NORM_PRIORITY:表示线程的普通优先级,相当于值5
说明:程序在运行期间,处于就绪状态的每个线程都有自己的优先级,例如main线程具有普通优先级。
使用:可以通过Thread类的setPriority(int newPriority)方法对其进行设置,该方法中的参数newPriority接收的是1~10之间的整数或者Thread类的三个静态常量。
如果想要人为地控制线程执行顺序,使正在执行的线程暂停,将CPU使用权让给其他线程,这时可以使用静态方法sleep(long millis)。
该方法可以让当前正在执行的线程暂停一段时间,进入休眠等待状态,这样其他的线程就可以得到执行的机会。
sleep(long millis) 方法会声明抛出InterruptedException异常,因此在调用该方法时应该捕获异常,或者声明抛出该异常。
线程让步可以通过 yield() 方法来实现,该方法和 sleep(long millis) 方法有点类似,都可以让当前正在运行的线程暂停,区别在于 yield() 方法不会阻塞该线程,它只是将线程转换成就绪状态,让系统的调度器重新调度一次。
当某个线程调用 yield() 方法之后,与当前线程优先级相同或者更高的线程可以获得执行的机会。
在Thread类中也提供了一个 join() 方法来实现线程插队功能。
当在某个线程中调用其他线程的 join() 方法时,调用的线程将被阻塞,直到被 join() 方法加入的线程执行完成后它才会继续运行。
Thread类中还提供了带有时间参数的线程插队方法 join(long millis)。当执行带有时间参数的join(long millis) 进行线程插队时,必须等待插入的线程指定时间过后才会继续执行其他线程。