一、实现线程的两种方式
1、继承Thread
当一个类继承Thread类后,就可以在该类中覆盖run()方法,将实现该线程功能的代码写在run()中,调用start()方法执行线程。
2、实现Runnable接口
实现Runnable接口,会创建一个Thread对象,将Runnable对象和Thread对象关联。
使用Runnable接口启动线程的步骤:
(1)建立Runnable对象
(2)使用参数为Runnable的构造方法构造Thread
(3)调用start()方法启动线程
3、使用线程池对线程进行管理。
3.1、Executors
通过Executors创建线程池
3.2、ExecutorService
ExecutorService扩展了Executor并添加了一些生命周期管理的方法。一个Executor的生命周期有三种状态,运行 ,关闭 ,终止。Executor创建时处于运行状态。当调用ExecutorService.shutdown()后,处于关闭状态,isShutdown()方法返回true。这时,不应该再想Executor中添加任务,所有已添加的任务执行完毕后,Executor处于终止状态,isTerminated()返回true。
如果Executor处于关闭状态,往Executor提交任务会抛出unchecked exception RejectedExecutionException。
3.3 使用Callable,Future返回结果
Future<V>代表一个异步执行的操作,通过get()方法可以获得操作的结果,如果异步操作还没有完成,则,get()会使当前线程阻塞。FutureTask<V>实现了Future<V>和Runable<V>。Callable代表一个有返回值得操作。
ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。
二、线程的生命周期
线程包含7种状态:出生、就绪、运行、等待、阻塞、休眠和死亡。
出生:线程呗创建时处于的状态,在用户使用该线程实例调用start()方法之前,都处于出生状态。
三、操作线程的方法
1、线程休眠
Thread.sleep()方法,通常在run()方法内的循环中使用。
2、线程的加入
线程A正在执行,这时插入线程B,并让B先执行,可以用Thread.join()。
当某个线程使用join()方式加入到另一个线程时,另一个线程会等该线程执行完毕后再执行。
见代码 TestJoin类,
测试效果是,上一个进度条先执行,第二个进度条join后,会优先把第二个进度条执行完毕后,再接着执行第一个进度条
3、线程的中断
提倡在run()方法中使用无限循环,用一个boolean标记来控制循环的停止。
如果线程使用了sleep(),wait()方法进入了就绪状态,可以使用Thread.interrupt()使线程离开run(),同时结束线程。
示例见类TestStop类,调用interrupt()方法中断线程。
进度条执行到50%的时候,线程被中断
4、线程的礼让
Thread类中,使用yield()方法进行礼让。该方法使同样优先级的线程有进入可执行状态的机会。对于支持多任务的操作系统来说,不需要调用yield()方法,系统会为线程分配CPU时间片来执行。
三、线程同步
1、使用synchronized实现同步
java提供一种同步机制,可以有效防止资源冲突,使用synchronized关键字。
以售票为例,多个人模拟多个线程,同时购票,当余票>0时售票。不使用同步块时,会出现余票为负也售票的现象。
见代码TestSynch类
@Override
public void run() {
while (true) {
if(ticketNum>0) {
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "获得第" + --ticketNum + "张票");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
加了同步块的结果。
@Override
public void run() {
while (true) {
synchronized ("") {
if(ticketNum>0){
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "获得第" + --ticketNum + "张票");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
以生产者-消费者为例,见包nubia.pxc.demo.synchron目录下
2、使用Lock实现同步
lock接口允许读写分离操作,允许多个读线程和只有一个写线程。相比synchronized,Lock接口具有更好的性能。使用try-catch的时候,一定要在finally语句中释放锁。
public void printJob(Object document){
queueLock.lock();//获取锁
try {
Long duration = (long)(Math.random()*10000);
System.out.println(Thread.currentThread().getName()+"打印队列:"+(duration/1000)+"秒");
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
queueLock.unlock();//释放锁
}
}
使用tryLock(),可以对获取到锁的boolean值进行处理。
Lock lock = ...;
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
} else {
// perform alternative actions
}
3、使用ReadWriteLock实现读写锁同步
ReadWriteLock有两个锁,一个读操作锁,一个写操作锁。使用读操作锁时允许多个线程同时访问,但是使用写操作时只允许一个线程运行。在一个线程执行写操作的时候,其他线程不能执行读操作。
见代码:nubia.pxc.demo.lock.ReadWriteLock目录下