网站空间服务器排名,中山网站的优化,泉州大型网站设计公司,在线制作图片及图片处理工具美图秀秀多线程是提升程序性能非常重要的一种方式#xff0c;也是Java编程中的一项重要技术。在程序设计中#xff0c;多线程就是指一个应用程序中有多条并发执行的线索#xff0c;每条线索都被称作一个线程#xff0c;它们会交替执行#xff0c;彼此间可以进行通信。
1. 进程与线…多线程是提升程序性能非常重要的一种方式也是Java编程中的一项重要技术。在程序设计中多线程就是指一个应用程序中有多条并发执行的线索每条线索都被称作一个线程它们会交替执行彼此间可以进行通信。
1. 进程与线程
进程是指正在运行的程序是系统进行资源分配和调度的基本单位。为了有效利用系统资源和提高运行效率在一个进程中还可以有若干同时并发运行的线程。每一个进程都至少存在一个线程。
当一个Java程序启动时就会产生一个进程该进程中会默认创建一个线程在这个线程上运行main()方法中的代码这样的程序称为单线程程序。单线程程序中只有一个线程在运行效率相对较低。好比是售票大厅只开设一个售票窗口所有人只能在一个窗口排除买票整个售票过程效率较低如果同时开设多个售票窗口售票则可以提高售票效率。编写的程序也是同样可以通过创建多线程程序来提高程序运行效率。
2. 线程的创建
Java中提供了三种方式来实现多线程。
1Thread类实现多线程
Thread类是java.lang包下的一个线程类可通过继承Thread类的方式来实现多线程其使用方法是先创建一个Thread线程类的子类子线程同时重写Thread类的run()方法然后创建该子类的实例对象并通过调用start()方法启动线程。
【例11-1】通过Thread类实现多线程 运行结果如下图所示。 通过运行结果可以看到两个线程对象交互执行了各自重写的run()方法。并不是按顺序先执行完第一个线程再执行第二个线程。
2Runnable接口实现多线程
虽然可以通过继承Thread类实现多线程但这种使用方式有一定的局限性。因Java只支持单继承如果某个类已经继承了其他类就无法再继承Thread类来实现多线程。这时可通过实现Runnable接口的方式来实现多线程。
①使用实现Runnable接口的方式来实现多线程的主要过程如下。
②创建一个Runnable接口的实现类重写接口中的run()方法。
③创建Runnable接口的实现类对象。
④使用Thread有参构造方法创建线程实例并将Runnable接口的实现类的对象作为参数传入。
⑤调用线程实例的start()方法启动线程。
【例11-2】通过实现Runnable接口的方式来实现多线程 运行结果如下图所示。 Callable接口实现多线程
通过Thread类和Runnable接口实现多线程时需要重写run()方法但是由于该方法没有返回值因此无法从多个线程中获取返回结果。为了解决这个问题从JDK 5开始Java提供了一个新的Callable接口来满足这种既能创建多线程又可以有返回值的需求。
Callable接口实现多线程是通过Thread类的有参构造方法传入Runnable接口类型的参数来实现多线程不同的是这里传入的是Runnable接口的子类FutureTask对象作为参数而FutureTask对象中则封装带有返回值的Callable接口实现类。
通过Callable接口实现多线程的过程如下。
①创建一个Callable接口的实现类同时重写Callable接口的call()方法
②创建Callable接口的实现类对象
③通过FutureTask线程结果处理类的有参构造方法来封装Callable接口实现类对象
④使用参数为FutureTask类对象的Thread有参构造方法创建Thread线程实例
⑤调用线程实例的start()方法启动线程。
【例11-3】通过Callable接口实现多线程 运行结果如下图所示。 Callable接口方式实现的多线程是通过FutureTask类来封装和管理返回结果的该类的直接父接口是RunnableFuture。FutureTask类的继承关系如下图所示。 FutureTask本质是Runnable接口和Future接口的实现类而Future则是用来管理线程执行返回结果的。其中Future接口中有5个方法来对线程结果进行管理如表1所示。
表1 Future接口的方法 方法声明 功能描述 boolean cancel(boolean mayInterruptIfRunning) 用于取消任务参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务如果设置true则表示可以取消正在执行的任务 boolean isCancelled() 判断任务是否被取消成功如果在任务正常完成前被取消成功则返回 true boolean isDone() 判断任务是否已经完成若任务完成则返回true V get() 用于获取执行结果这个方法会发生阻塞一直等到任务执行完毕才返回执行结果 V get(long timeout, TimeUnit unit) 用于在指定时间内获取执行结果如果在指定时间内还没获取到结果就直接返回null 3. 线程的生命周期
在Java中任何对象都有生命周期线程也不例外它也有自己的生命周期。当Thread对象创建完成时线程的生命周期便开始了。当线程任务中代码正常执行完毕或者线程抛出一个未捕获的异常Exception或者错误Error时线程的生命周期便会结束。线程的整个生命周期分为6个状态分别是NEW新建状态、RUNNABLE(可运行状态)、BLOCKED(阻塞状态)、WAITING(等待状态)、TIMED_WAITING定时等待状态和TERMINATED(终止状态)。线程的不同状态表明了线程当前正在进行的活动。程序中通过一些操作可以使线程在不同状态之间转换如下图所示。 1NEW新建状态
创建一个线程对象后该线程对象就处于新建状态此时它不能运行和其他Java对象一样仅仅由JVM为其分配了内存没有表现出任何线程的动态特征。
2RUNNABLE可运行状态
新建状态的线程调用start()方法就会进入可运行状态。在RUNNABLE状态内部又可细分成两种状态READY就绪状态和RUNNING运行状态并且线程可以在这两个状态之间相互转换。
RUNNABLE内部状态转换
①就绪状态线程对象调用start()方法之后等待JVM的调度此时线程并没有运行
②运行状态线程对象获得JVM调度如果存在多个CPU那么允许多个线程并行运行。
3BLOCKED阻塞状态
运行状态的线程因为某些原因失去CPU的执行权会进入阻塞状态。阻塞状态的线程只能先进入就绪状态不能直接进入运行状态。
线程一般会在以下两种情况时进入阻塞状态
①当线程A运行过程中试图获取同步锁时却被线程B获取
②当线程运行过程中发出IO请求时。
4WAITING等待状态
当运行状态的线程调用了无时间参数限制的方法后如wait()、join()等方法就会转换为等待状态。
处于等待状态中的线程不能立即争夺CPU使用权必须等待其他线程执行特定的操作后才有机会争夺CPU使用权。例如调用wait()方法而处于等待状态中的线程必须等待其他线程调用notify()或者notifyAll()方法唤醒当前等待中的线程调用join()方法而处于等待状态中的线程必须等待其他加入的线程终止。
5TIMED_WAITING定时等待状态
当运行状态中的线程调用了有时间参数限制的方法如sleep(long millis)、wait(long timeout)、join(long millis)等方法就会转换为定时等待状态。
处于定时等待状态中的线程不能立即争夺CPU使用权必须等待其他相关线程执行完特定的操作或者限时时间结束后才有机会再次争夺CPU使用权。例如调用了wait(long timeout) 方法而处于等待状态中的线程需要通过其他线程调用notify()或者notifyAll()方法唤醒当前等待中的线程或者等待限时时间结束后也可以进行状态转换。
6TERMINATED终止状态
当线程的run()方法、call()方法正常执行完毕或者线程抛出一个未捕获的异常Exception、错误Error线程就进入终止状态。一旦进入终止状态线程将不再拥有运行的资格也不能再转换到其他状态生命周期结束。
4. 线程的调度
程序中的多个线程是并发执行的但并不是同一时刻执行某个线程若想被执行必须要得到CPU的使用权。Java虚拟机会按照特定的机制为程序中的每个线程分配CPU的使用权这种机制被称作线程的调度。
线程调度有两种模型分别是分时调度模型和抢占式调度模型。分时调度是指让所有的线程轮流获得CPU的使用权并且平均分配每个线程占用的CPU时间片。抢占式调度是指让可运行池中所有就绪状态的线程争抢CPU的使用权而优先级高的线程获取CPU执行权的概率大于优先级低的线程。
Java虚拟机默认采用抢占式调度模型多数情况下不需要去关心它在某些特定的需求下需要改变这种模式时可由程序来控制 CPU的调度。
1设置线程的优先级
在程序中如果要对线程进行调度最直接的方式就是设置线程的优先级。优先级越高的线程获得CPU执行的机会越大而优先级越低的线程获得CPU执行的机会越小。
线程的优先级用1~10之间的整数来表示数字越大优先级越高。除了可以直接使用数字表示线程的优先级还可以使用Thread类中提供的三个静态常量如表2所示表示线程的优先级。
表2 Thread类的优先级常量 Thread类的静态常量 功能描述 static int MAX_PRIORITY 表示线程的最高优先级相当于值10 static int MIN_PRIORITY 表示线程的最低优先级相当于值1 static int NORM_PRIORIY 表示线程的普通优先级相当于值5
程序在运行期间处于就绪状态的每个线程都有自己的优先级例如main线程具有普通优先级。可以通过Thread类的setPriority(int newPriority)方法对其进行设置该方法中的参数newPriority接收的是1~10之间的整数或者Thread类的三个静态常量。
【例11-4】线程优先级的设置 运行结果如下图所示。 说明
虽然Java提供了10个线程优先级但是这些优先级需要操作系统的支持不同的操作系统对优先级的支持是不一样的不能很好地和Java中线程优先级一一对应。
2 线程休眠
如果想要人为地控制线程执行顺序使正在执行的线程暂停将CPU使用权让给其他线程这时可以使用静态方法sleep(long millis)。该方法可以让当前正在执行的线程暂停一段时间进入休眠等待状态这样其他的线程就可以得到执行的机会。sleep(long millis)方法会声明抛出InterruptedException异常因此在调用该方法时应该捕获异常或者声明抛出该异常。
【例11-5】线程休眠 运行结果如下图所示。 5. 多线程同步
多线程的并发执行可以提高程序的效率但是当多个线程去访问同一个资源时也会引发一些安全问题。如下例中的多线程售票程序。
【例11-6】多线程售票 运行结果如下图所示。 由运行结果可以看到同一张票被出售了多次这种现象是不应该出现的。出现这种问题的原因在于多个线程同时处理共享资源所导致的。为了解决这样的问题只需要保证某个资源在同一时刻只能被一个线程访问即可也即线程的同步Java中提供了几种不同的线程同步机制。
1同步代码块
当多个线程使用同一个共享资源时可以将处理共享资源的代码放置在一个使用synchronized关键字修饰的代码块中这段代码块就被称为同步代码块。使用格式如下。 synchronized(lock){ // 操作共享资源代码块 ... }
述代码中lock是一个锁对象可以是任意类型的对象但多个线程共享的锁对象必须是相同的。锁对象的创建代码不能放到run()方法中否则每个线程运行到run()方法都会创建一个新对象这样每个线程都会有一个不同的锁。
【例11-7】利用同步代码块实现线程同步 运行结果如下图所示。 同步代码块的原理
①当线程执行同步代码块时首先会检查lock锁对象的标志位
②默认情况下标志位为1此时线程会执行Synchronized同步代码块同时将锁对象的标志位置为0
③当一个新的线程执行到这段同步代码块时由于锁对象的标志位为0新线程会发生阻塞等待当前线程执行完同步代码块后
④锁对象的标志位被置为1新线程才能进入同步代码块执行其中的代码这样循环往复直到共享资源被处理完为止。
2同步方法
当把共享资源的操作放在同步代码块中时便为这些操作加了同步锁。同样也可以在方法前面使用synchronized关键字来修饰被修饰的方法称为同步方法可实现和同步代码块同样的功能同步方法使用格式如下所示。
[修饰符] synchronized 返回值类型方法名([参数1,……]){
//方法体
}
被synchronized修饰的方法在某一时刻只允许一个线程访问访问该方法的其他线程都会发生阻塞直到当前线程访问完毕后其他线程才有机会执行。
【例11-8】使用同步方法实现线程同步 运行结果如下图所示。