1. 多线程编程基础
1.1 线程的概念
如果是同一个应用程序要并行处理多件任务,就不必建立多个进程,而是在一个进程之中建立多个线程。创建线程比创建进程开销要小得多,线程之间的协作和数据交换也比较容易。如果程序中有需要同时并行执行的多个代码段,并且这些代码段在逻辑上可以同时运行,那么就可以建立多个线程,并发执行。
在Java中创建多线程有两种方法,继承自Thread类和实现Runnable接口。
1.2 Thread类
从Thread类派生一个子类并创建这个子类的对象,就可以产生一个新的线程,在这个子类中重写Thread类的run方法,在run方法中写入需要在新线程中执行的语句段。这个子类的对象需要调用start方法来启动,新线程将自动进入run方法。原线程将同时继续往下执行。Thread类直接继承了Object类,并实现了Runnable接口(?)。Thread类位于java.lang包中,因此不用import。
练习1:在新线程中完成计算某个整数的阶乘
public class FactorialThreadTester {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("main thread starts");
FactorialThread thread = new FactorialThreadTester().new FactorialThread(10);
thread.start();
System.out.println("main thread ends");
}
class FactorialThread extends Thread{
private int num;
public FactorialThread(int num)
{
this.num=num;
}
@Override
public void run() {
int i=num;
int result=1;
System.out.println("new thread started");
while(i>0)
{
result=result*i;
i=i-1;
}
System.out.println("The factorial of "+num+" is "+result);
System.out.println("end thread ends");
}
}
}
Thread类的主要方法
名称
说明
public Thread()
构造一个新的线程对象,默认名为Thread-n,n是从0开始递增的整数
public Thread(Runnable target)
构造一个新的线程对象,以一个实现Runnable接口的类的对象为参数。默认名为Thread-n,n是从0开始递增的对象
public Thread(String name)
构造一个新的线程对象,并同时指定线程名
public static Thread currentThread()
返回当前正在运行的线程对象
public static void yield()
使当前线程对象暂停,允许别的线程开始运行
public static void sleep(long millis)
使当前线程暂停运行指定毫秒数,但此线程并不失去已获得的锁旗标
public void start()
启动线程,JVM将调用此线程的run方法,结果是将同时运行两个线程,当前线程和执行run的方法的线程
public void run()
Thread的子类应重写此方法,内容应为该线程应执行的任务
public final void stop()
停止线程运行,释放该线程占用的对象锁旗标
public void interrupt()
打断此线程
public final void join()
在当前线程中加入调用join方法的线程A,直到线程A死亡才能继续执行当前线程
public final void join(long millis)
在当前线程中加入调用join方法的线程A,直到到达参数指定毫秒级或线程A死亡才能继续执行当前线程
public final void setPriority(int newPriority)
设置线程优先级
public final void setDaemon(Boolean on)
设置是否为后台线程,如果当前运行线程均为后台线程则JVM停止运行。这个方法必须在start()方法前使用
public final void checkAcess()
判断当前线程是否有权力修改调用此方法的线程
public void setName(String name)
更改本线程的名称为指定参数
public final boolean isAlive()
测试线程是否处于活动状态,如果线程被启动并且没有死亡则返回true
1.3 Runnable接口
Runnable接口实际上只有一个run()方法,实现Runnable接口的类的对象可以用来创建线程,这是start方法启动此线程就会在此线程上运行run()方法。
使用Runnable接口的好处不仅在于简介解决了多继承问题,与Thread类相比,Runnable接口能适合于多个线程处理同一资源
1.4 Callable泛型接口
Callable与Runnable的功能大致相似,Callable中有一个call()函数,但是call()函数有返回值,而Runnable的run()函数不能将结果返回给客户程序。Callable的声明如下 :
public interface Callable {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
call()函数返回的类型就是客户程序传递进来的V类型
Callable可以返回执行结果,和Future、FutureTask配合可以用来获取异步执行的结果
通过实现Callable接口来创建Thread线程:
步骤1:创建实现Callable接口的类SomeCallable(略);
步骤2:创建一个类对象:
Callable oneCallable = new SomeCallable();
步骤3:由Callable创建一个FutureTask对象:
FutureTask oneTask = new FutureTask(oneCallable);
注释:FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。
步骤4:由FutureTask创建一个Thread对象:
Thread oneThread = new Thread(oneTask);
步骤5:启动线程:
oneThread.start();
至此,一个线程就创建完成了。
1.4 线程间的数据共享
当多个线程的执行代码来自同一个类的run方法时,即成它们共享相同的代码;当共享访问相同的对象时,即它们共享相同的数据。
1.5 多线程的同步控制
利用对象的“锁旗标”可以实现线程间的互斥操作。
关键字synchronized可以实现与一个和锁旗标的交互
synchronized(对象){代码段}
synchronized的作用是:首先判断对象的锁旗标是否存在,如果在就获得锁旗标,然后就可以执行紧随其后的代码段;如果对象的锁旗标不在(已被其他线程拿走),就进入等待状态,直到获得锁旗标。
synchronized修饰的对象包括:
1、 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2、修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3、修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象。对静态方法加锁,实际上是对类加锁;
4、修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。
还可以定义整个方法在同步控制下执行,只需要在方法定义前加上synchronized关键字即可
1.6 线程之间的通信
java.lang.Object类的wait、notify等方法为线程间的通信提供了有效手段
方法
作用
public final void wait()
如果一个正在执行同步代码(synchronized)的线程A执行了wait调用(在对象x上),该线程暂停执行而进入对象x的等待池,并释放已获得的对象x的锁旗标。线程A要一直等到其他线程在对象x上调用notify或notifyAll方法,才能够再重新获得对象x的锁旗标后继续执行(从wait语句后继续执行)
public void notify()
唤醒正在等待该对象锁旗标的第一个线程
public void notifyAll()
唤醒正在等待该对象锁旗标的所有线程,具有最高优先级的线程首先被唤醒并执行
1.7 后台线程
如果对某个线程对象在启动(调用start方法)之前调用了setDaemon(true)方法,这个线程就变成了后台线程。
2. 线程的生命周期
2.1 线程的状态
thread state
线程启动并进入就绪状态、运行状态、死亡状态、阻塞状态
2.2 死锁问题
3. 线程的优先级
java线程优先级范围为Thread.MIN_PRIORITY(常数1)和Thread.MAX_PRIORITY(常数10)之间,默认为Thread.NORM_PRIORITY(常数5)。
yield()方法
Thread.yield()方法作用是:
暂停当前正在执行的线程对象(及放弃当前拥有的cpu资源),并执行其他线程。yield()做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
结论:
yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
4. 线程池
1、创建和销毁线程花费的事件及消耗的系统资源可能比实际处理所占用的时间及资源多得多。
“线程池”技术通过对多个任务重用线程,线程创建的开销就被分摊到了多个任务上,而且由于在请求 到达时线程已经存在,所以消除了线程创建所带来的延迟。
2、一个比较简单的线程池至少应包含线程池管理器、工作线程、任务队列、任务接口等部分:
1)线程池管理器:作用是创建、销毁并管理线程池,将工作线程放入线程池中
2)工作线程:一个可以循环执行任务的线程, 在没有任务时等待;
3)任务列表:提供一种缓冲机制,将没有处理的任务放在任务列队中
4)任务接口:每个任务必须实现的接口,主要用来规定任务的入口、任务执行完后的收尾工作、任务的执行状态等,工作线程通过该接口调度任务的执行。
4.1 线程池的概念
线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
4.2 线程池的工作机制
1、 在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程。
2、一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
4.3 使用线程池的原因
多线程运行时间,系统不断的启动和关闭新线程,成本非常高,会过渡消耗系统资源,以及过渡切换线程的危险,从而可能导致系统资源的崩溃。这时,线程池就是最好的选择了。