java多线程入门篇
Java多线程入门篇(二)线程组线程优先级和线程状态
Java多线程入门篇(三)Java线程间的通信(锁,等待/通知,信号量,管道)
进程线程的基本概念
进程的产生
最初的计算机只可以接收一些特定的指令,用户输入一个指令计算机就可以执行一个指令,但是当用户思考和输入时计算机就会等待,所以计算机的大多数时间都是在等待。
于是就出现了批处理操作系统,就是将一系列指令形成一个清单,一次性交给计算机。这样就可以在一定程度上提高计算机的执行效率。但是此时还是串行执行的,内存中只有一个程序在运行。
慢慢的人们就不满足此时的速度,希望内存中可以同时运行多个程序,所以就提出了进程的概念。
进程就是应用程序在内存中分配的空间,也就是正在运行的程序。进程之间互不影响。
cpu是采取时间片轮转的方式运行进程的。cpu会给每个进程分配一个时间段,称作时间片,如果在时间结束时进程还在运行,则暂停这个进程的运行,并且cpu分配给另一个进程(这个过程叫做上下文切换)。
进程让操作系统的并发成为了可能。
线程的提出
经过上面的改进可以同时运行多个程序了,但是人们的要求进一步提高了,那就是一个程序中可以同时做多个事情,也就是进程中能不能有子任务。比如:在qq音乐中边听歌边找歌。按照以前的方式显然在听歌结束之前无法去找歌。
于是就提出了线程的概念,让一个线程去执行一个子任务,这样一个进程就包含了多个线程。至此进程和线程极大的提高了计算机的性能。进程让操作系统的并发性成为了可能,线程让进程的内部的并发性成为了可能。
上下文切换
指cpu从一个进程切换到另一个进程(或线程)。上下文是指某一个时间点cpu寄存器和程序计数器的内容。
cpu通过为每个线程分配cpu时间片来实现多线程机制。cpu通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。
但是,在切换前会保存上一个任务的状态,以便下次切换回到这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。
问题:
进程也可以解决并发为什么还要线程?
进程确实可以解决并发,但是,使用线程有以下好处:
1:进程之间的通信比较复杂,而同一个进程的线程之间因为他们是共享空间的所以他们通信比较简单。
2:进程是重量级的,而线程是轻量级的。所以多线程的方式系统开销较小。
进程与线程的区别是什么?
1:进程单独占有一定内存空间,所以进程之间内存隔离,通信复杂但同步简单,而线程则通信简单但同步复杂。(同步:让一些进程或者线程按照某个顺序执行)
2:进程之间是互不影响的,也不影响主程序的稳定性,稳定性强。而一个线程的崩溃就可能导致整个程序的崩溃,稳定性差。
3:进程是重量级的,创建和销毁都会需要大量系统开销,比如保存寄存器和栈信息,申请和回收内存空间。而线程是轻量级的,只需要保存寄存器和栈信息。
入门的类和接口
Thread类和Runnable接口
实现自己的线程类的方式:
1:继承Thread类并重写run方法。
public class myThread extends Thread{
@Override
public void run(){
System.out.println("我的线程执行");
}
public static void main(String[] args) {
Thread my = new myThread();
my.start();
}
}
2:实现Runnable接口的run方法。
public class myThread1 {
public static class demo implements Runnable{
@Override
public void run(){
System.out.println("实现Runnable的线程类");
}
}
public static void main(String[] args) {
new demo().run();
}
}
Callable、Future与FutureTask
上面两种创建方式都是无返回值的,但是有时候我们可能是需要他有返回值的,后面可能会根据返回值进行某些操作,此时Thread和Runnable就无法满足我们的需求了。
jdk提供了Callable接口与Future类为我们解决了这个问题,这也是所谓的”异步模型“。
Callable接口
Callable与Runnable类似,同样是只有一个抽象方法的函数式接口。区别是它的方法有返回值,而且支持泛型。
Callable一般是配合线程池工具ExecutorService来使用,它可以使用submit方法来让一个Callable接口执行。它会返回一个Future,我们后续的程序可以通过这个Future的get方法得到结果。
public class CallableDemo implements Callable<Integer> {
@Override
public Integer call()throws Exception{
//模拟计算需要1秒
System.out.println("进行计算");
Thread.sleep(1000);
System.out.println("完成");
return 2;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService exector = Executors.newCachedThreadPool();
CallableDemo demo = new CallableDemo();
Future<Integer> result = exector.submit(demo);
System.out.println(result.get());
}
}
Future接口
Future接口只有几个简单的方法:
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
cancel方法是试图取消一个线程的执行。
注意是试图取消,并不一定能取消成功。因为任务可能已经完成,已取消或者一些其它的原因。
所以有时候,为了让任务有取消的功能,就使用Callable代替Runnable。
FutureTask类
上面介绍了Future接口,这个接口一个实现类叫FutureTask。它是实现地RunnableFuture接口的,而RunnableFuture接口同时继承了Runnable接口和Future接口。
那他有什么用呢?因为Future只是一个接口,它里面的cancel,get,isDone等方法要自己实现都是很困难的所以jdk提供了FutureTask来供我们使用。
public class Demo implements Callable<Integer> {
@Override
public Integer call()throws Exception{
Thread.sleep(1000);
return 2;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newCachedThreadPool();
FutureTask<Integer> futureTask = new FutureTask<>(new Demo());
executor.submit(futureTask);
System.out.println(futureTask.get());
}
}
下一篇文章将讲的内容:
线程组和线程优先级,
Java线程的状态及转换,
Java线程间的通信