在java的历史版本中,有两种创建多线程程序的方法 1. 通过创建Thread类的子类来实现
2. 通过实现Runable接口的类来实现(推荐)
一、通过Thread类实现多线程
- 设计Thread的子类
- 根据工作需要重新设计线程的run方法
n 线程类Thread中提供的run是一个空方法。为此,我们可以继承Thread,然后覆盖(override)其中的run,使得该线程能够完成特定的工作。
- 使用start方法启动线程,将执行权转交到run。
二、通过实现Runnable接口创建线程
n 创建线程最简单的方法就是创建一个实现Runnable 接口的类,这个类仅需实现其中一个run()方法。
n 主要步骤:
1. 创建某个类实现Runnable接口,实现run()方法。
2. 创建Thread对象,用实现Runnable接口的对象作为参数实例化该Thread对象。
3. 调用Thread的start方法。
n Java有两种创建线程的方法,为什么推荐实现Runnable接口的方式创建线程?
n Thread类定义了多种方法可以被派生类重载。对于所有的方法,唯一的必须被重载的是run()方法。这当然是实现Runnable接口所需的同样的方法。
n 很多Java程序员认为类仅在它们需要被加强或修改时被扩展。因此,如果你不重载Thread的其他方法,最好通过实现Runnable 接口,来创建线程。
二、使用Executor框架管理线程
n 虽然可以使用Thread类来显示的创建线程,但推荐的做法是使用Executor接口,让它来管理Runnable对象的执行。
n 通常Executor对象会创建并管理一组执行Runnable对象的线程,这组线程被称为线程池。
采用Executor的优势
n Executor对象能够复用已有的线程,从而消除了为每个任务创建新线程的开销,
n 它能通过优化线程的数量,提高程序性能,保证处理器一直处于忙碌状,而不必创建过多的线程使程序资源耗尽。
步骤:
n 声明一个execute()方法,接收一个Runnable实参,
n Executor会将传递给它的execute方法的每个Runnable对象赋予线程池中的某个可用线程。
n 如果没有可用线程,Executor会创建一个新线程,或者等待某个线程成为可用的,再将其赋予Runnable对象。
ExecutorService接口
n 该接口扩展了Executor接口,并声明了许多方法用于管理Executor的声明周期。
n 通过Executor类中的静态方法,可用创建实现ExecutorService接口的对象。
n 程序示例: ExecutorServiceTest
线程的同步
n 同一进程的多个线程共享同一存储空间,带来了访问冲突这个严重的问题。
n 有时两个或多个线程可能会试图同时访问一个资源,在此情况下,数据可能会变得不一致。
n 例如,一个线程可能尝试从一个文件中读取数据,而另一个线程则尝试在同一文件中修改数据,
n 某个时刻只允许一个线程排他性的访问操作共享对象的代码,当独占对象的线程在操作对象时,其他线程会一直等待,直到独占对象的那个线程完成了对对象的操作,其他对象才被允许处理该对象,
n 这种方法可以实现多个线程的协同工作,解决冲突问题。
实现线程同步
n 一种常见办法是使用java内置的监控器。
n 每个对象都具有一个监控器和一个监控锁,
n 监控器保证在任何时刻,它的对象的监控锁是由唯一一个线程持有的。
n 这样监控器和监控锁才能用于实现“互斥”。
n Java中可用使用关键字synchronized为共享资源加锁。
n synchronized可修饰一个代码块或一个方法,使被修饰对象在任一时刻只能有一个线程访问,从而提供了程序的同步执行功能。
n 两种方式实现同步:
n 使用同步方法:通过在方法声明中加入synchronized关键字来声明synchronized方法
n public synchronized void methodA() { }
n 当一个线程调用一个“互斥”方法时,它试图获得该方法锁。如果方法未锁定,则获得使用权,以独占方式运行方法体,运行完释放该方法的锁。如果方法被锁定,则该线程必须等待,直到方法锁被释放时。
n 注意:
n 对方法run( )无法加锁,不可避免冲突;
n 对构造函数不能加锁,否则出现语法错误。
n synchronized方法虽然可以解决同步的问题,但也存在缺陷,
n 如果一个synchronized方法需要执行很长时间,将会大大影响系统的效率。
n Java语言提供了一种解决办法,就是synchronized块。
1. 使用同步块
2. 可以通过synchronized关键字将一个程序块声明为synchronized块。
synchronized(object) { //要互斥的语句 }
n 当一个线程执行这段代码时,它获得特定对象的所有权,即拥有该对象的锁。此时,如果有第二个线程对同一个对象也要执行这段代码时,它也试图获得该对象的所有权,但因该对象已被锁定,则第二个线程必须等待,直到锁被释放为止。
n 第一个线程执行完代码段后,自动释放锁,接下去第二个线程获得锁并可运行。
n 注意:
n 当维护所需要的同步特性时,要尽可能的缩小同步语句占用的时间,这样就能最小化被阻塞线程的等待时间。
n 线程之间的同步是针对线程间共享可变数据的情况,对于线程间共享不可变数据时,应将数据字段声明成final的。
|
|