进程与线程
1.进程:进程是指一个内存中运行的应用程序,比如在Windows系统中,一个运行的exe就是一个进程。
2.线程:进程是负责程序执行的执行单元,线程本身依靠程序进行运行。
3 单线程:程序中只存在一个线程,实际上主方法就是一个主线程。
4 多线程:在一个程序中运行多个任务,目的是更好地使用CPU资源。
线程的生命周期
关于Java中线程的生命周期,首先看一下下面这张较为经典的图:

上图中基本上囊括了Java中多线程各重要知识点。掌握了上图中的各知识点,Java中的多线程也就基本上掌握了。主要包括:
Java线程具有五中基本状态
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
4死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
4种线程实现的方式
1.继承Thread类
在java.lang包中定义, 继承Thread类必须重写run()方法
class MyThread extends Thread{
private static int num = 0;
public MyThread(){
num++;
}
@Override
public void run() {
System.out.println("主动创建的第"+num+"个线程");
}
}
创建好了自己的线程类之后,就可以创建线程对象了,然后通过start()方法去启动线程。注意,不是调用run()方法启动线程,run方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务
public class ThreadTest {
public static void main(String[] args) {
MyThread thread=new MyThread();
thread.start();
}
}
2实现Runnable接口
在Java中创建线程除了继承Thread类之外,还可以通过实现Runnable接口来实现类似的功能。实现Runnable接口必须重写其run方法。
下面是一个例子:
public class Test {
public static void main(String[] args) {
System.out.println("主线程ID:"+Thread.currentThread().getId());
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
class MyRunnable implements Runnable{
public MyRunnable() {
}
@Override
public void run() {
System.out.println("子线程ID:"+Thread.currentThread().getId());
}
}
Runnable的中文意思是“任务”,顾名思义,通过实现Runnable接口,我们定义了一个子任务,然后将子任务交由Thread去执行。注意,这种方式必须将Runnable作为Thread类的参数,然后通过Thread的start方法来创建一个新线程来执行该子任务。如果调用Runnable的run方法的话,是不会创建新线程的,这根普通的方法调用没有任何区别。
事实上,查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。
在Java中,这2种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,但是由于Java只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。
3使用执行器(Executor)创建线程池(thread pool)
从JDK1.5开始,java.util.concurrent包中的执行器(Executor)将为你管理Thread对象,从而简化了并发编程。如果我们的程序需要用到很多生命周期比较短的线程,那么应该使用线程池,线程池中包含了很多空闲线程,而且这些线程的生命周期不需要我们操心。另一个使用线程池的原因是:如果你的代码需要大量的线程,那么最好使用一个线程池来规定总线程数的上线,防止虚拟机崩溃。这样可以限制最大的并发数量。
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
(1) newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(index);
}
});
}
}
}
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
(2) newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。示例代码如下:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。
定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors();
(3) newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);
}
}
表示延迟3秒执行。
定期执行示例代码如下:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);
}
}
表示延迟1秒后每3秒执行一次。
(4) newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
结果依次输出,相当于顺序执行各个任务。
你可以使用JDK自带的监控工具来监控我们创建的线程数量,运行一个不终止的线程,创建指定量的线程,来观察:
工具目录:C:\Program Files\Java\jdk1.8.0_06\bin\jconsole.exe
运行程序做稍微修改:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
try {
while(true) {
System.out.println(index);
Thread.sleep(10 * 1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4.使用Callable与Future创建线程并获取返回值
我们使用Runnable封装了一个异步运行的任务,我们可以把它想象成一个没有参数和返回值的异步方法,Callable与Runnable相似,但是Callable具有返回值,可以从线程中返回数据。
Callable
我们从jdk中找到了Callable<V>的源代码,去掉一些无用的部分:
package java.util.concurrent;
public interface Callable<V> {
V call() throws Exception;
}
可以看出Callable接口只是将run方法换成了call方法,其他并没有太多的改动。
Future
Future类负责保存异步计算的结果。可以启动一个计算,将Future对象交给某个线程,然后我们去做其他的事情,Future对象的所有者在结果计算好之后就可以调用get方法获得它。我们在上面线程池的submit方法中也提到过。
V get() throws InterruptedException,ExecutionException
如有必要,等待计算完成,然后通过get方法获取其结果。
FutureTask包装器
我们虽然有了Callable和Future类,但是我们仍然需要一种方法将他们结合起来使用。而且还存在的问题是,Callable的出现替代了Runnable。我们需要一种手段让Thread类能够接受Callable做参数。在这里我们使用非常好用的FutureTask包装器。它可以将Callable转换成Futrue和Runnable,因为他同时实现了Runnable和Future<V>两个接口。
代码实现:
public class CallablePool {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService es = Executors.newFixedThreadPool(5);
// 创建一个5个线程大小的线程池
ArrayList<Future<Integer>> results = new ArrayList<Future<Integer>>(5);
// 创建一个Future<Integer>类型的数组
FutureTask<Integer> ft = null;
for (int i = 0; i < 5; i++) {
ft = new FutureTask<Integer>(new ImplCallable(i));
// 将Callable类型转化成FutureTask类型
es.submit(ft);
// 提交线程
results.add(ft);
// 将返回的结果提交,因为FutureTask同时也可变为Future类型,所以这里不需要其他类型转化
}
for (int i = 0; i < 5; i++)
try {
System.out.println(results.get(i).get());
// 打印结果,发现数组中为0到4五个数字,成功。
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class ImplCallable implements Callable<Integer> {
private int index;
ImplCallable(int index) {
this.index = index;
}
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
return index;
}
}
上面采用了线程池的方法来表现Callable和Future的使用方法,如果是简单实用Thread道理也是相同的,我们需要把:
ExecutorService es = Executors.newFixedThreadPool(5);
es.submit(ft);
这两句去掉,在for循环中替换成下面两句。创建Thread实例,开启新的线程。
Thread th = new Thread(ft);
th.start();
网友评论