进程与线程
进程: 运行的程序就称为进程,进程是资源分配的最小单位。每个进程都有自己的一套变量,但多个进程共享计算机的资源。
线程:同一个程序同时执行多个任务,每一个任务都是一个线程,线程是CPU调度和执行的最小单位,线程共享进程的资源,但每个线程也有属于自己的独立资源如栈,程序计数器,线程ID等。
主要区别
线程是轻量级的进程,线程的启动和撤销所花费的开销远远小于进程的启动和撤销花费的开销。进程一旦终止,该进程内的所有线程也随之终止.
一个进程终止,其它的进程不会受到影响,因为进程之间是独立运行的,而一个线程终止时,其它线程可能会受到影响,因为线程的非正常终止可能会引起进程内地址空间的数据发生变化。
eg: 打开浏览器,浏览器一旦启动起来就是一个进程,而在浏览器中可以刷微博,听音乐,逛淘宝,这每一个都是一个线程。这些线程共享浏览器的资源。
线程的状态:
线程总共有五种状态,new之后处于创建状态,start后处于就绪态,说明可已经装备好了可以以随时已经启动,就等着CPU调度了,运行过程中,出现阻塞事件,回到阻塞态,阻塞态结束后回到就绪态,而不会直接恢复运行态。线程执行完毕即来到终止状态(包括正常退出,和非正常退出)。
Java实现多线程
方式1: 继承Thread类,覆写run方法
代码实现:
public class TestThread {
public static void main(String[] args) {
MyThread mt1 = new MyThread("A线程");
MyThread mt2 = new MyThread("B线程");
MyThread mt3 = new MyThread("C线程");
mt1.start();
mt2.start();
mt3.start();
}
}
// 自定义类继承Thread,覆写run方法,写真正的业务逻辑
class MyThread extends Thread{
private Integer tickte = 10; // 10张票
public MyThread(String name){
super(name);
}
@Override
public void run() {
while(this.tickte > 0){
System.out.println(Thread.currentThread().getName() + "售出第" + tickte-- + "号票!!!" );
}
}
}
运行结果:
从运行结果可以看出出现了一票多卖的情况,明明仅有10张票,这是因为在主方法中new了三个Myth read对象,每个对从象都有自己的10张票,如果想让这10张票共享,可以用static 修饰一下。
方式2 : 实现Runnable接口,覆写run方法
线程的启动必须调用Thread类的start方法,但Runnable接口中没有start方法,仅有Thread类中有该方法,因此必须将Runnable对象变成Thread对象进而调用start方法。而Thread类的一个构造方法刚好可以完成这个事。
代码实现:
public class TestRunnable {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new MyRunnable();
new Thread(runnable).start();
new Thread(runnable).start();
System.out.println(((MyRunnable) runnable).getI());
}
}
class MyRunnable implements Runnable{
private int i = 0;
@Override
public void run() {
i++;
}
public int getI(){
return i;
}
}
但该方式不能解决业务方法即run方法有返回值得情况,因此针对此只能使用第三种方法。
3实现Callable接口,覆写Call方法
线程的启动必须调用Thread类的start方法,但Callable接口中也没有start方法,仅有Thread类中有该方法,因此必须将Callable对象变成Thread对象进而调用start方法。但Thread类并没有哪个方法可以达成这个事。但有一个叫FutureTask的类,该类是Runnable接口的间接实现类,该类的构造方法可以将Callable对象变成FutureTask对象,而FutureTask对象又是Runnable对象,可以利用Thread类的构造方法进行转换,进而调用Thread类的start方法,真正启动线程。
而返回值可以通过FutureTask类的get方法获取。
代码实现:
public class TestCallable {
public static void main(String[] args) {
Callable callable = new MyCallable();
FutureTask fu = new FutureTask(callable);
new Thread(fu).start();
try {
int num = (int) fu.get(); // get方法会阻塞主线程,直至等到run方法执行完毕,将返回值取得并返回.
System.out.println(num);
System.out.println("heihei");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for(int i = 0; i < 100000; i++){
sum += i;
}
return sum;
}
}
面试题: 继承Thread和实现Runnable接口的区别
- 因为Java的单继承局限,如果继承了Thread类,就不能再继承其它类了,而实现Runnable接口就没有这个缺陷。
- 难以实现资源共享问题。实现Runnable接口,只是创建了一个实现类对象,将该对象作为参数传给Thread类,因此可以实现数据资源共享。
- 继承Thread类,线程创建调度过程和业务逻辑耦合,后期难以维护。实现Runnable接口启动在Thread类中,业务逻辑在自己定义的实现类中,将线程启动,与线程业务解耦。便于维护。
4 线程池
待续。。。。
不论使用哪种方式来实现多线程,多线程的启动最终都调用的是Thread类的start方法,该方法内部又会调用一个start0方法,start0是一个本地方法,start0会使得操作系统产生一个线程,在线程产生之后又会回调run方法,此时才算线程启动过程结束。
典型的代理模式
使用接口实现多线程是一个典型的代理模式。
结构: 一个接口,两个类。
真实业务类:实现了接口,且覆写了run,或者call方法的类,方法体是真正的业务逻辑。
辅助代理类:Thread类,自定义线程的启动最终都必须调用Thread类的start方法。
因此这是一个典型的代理模式。
以后别人为你代理模式,就不用举其它的例子了。