一、进程与线程的区别
1、进程可以理解成程序的一次执行(即动态的),所以一个程序可以对应一个或多个进程(程序的多次执行),而一个进程往往包含一个或多个线程
2、每个进程有独立的地址空间,包含资源。而线程共享进程的资源,可以把线程理解为轻量的进程
3、线程是处理器调度的基本单位,进程是程序运行的实例
为什么要区分进程和线程
因为进程的执行开销比较大,把进程划分为多个线程能相对减小执行开销,特别是在多线程并发的情况下,涉及到频繁的切换时提高了效率。但由于线程共享进程的资源,一个线程在崩溃后会影响到其他线程,而地址空间独立的进程比线程健壮的多
何时使用多进程,何时使用多线程
对资源的管理和保护要求高,不限制开销和效率时,使用多进程。
要求效率高,频繁切换时,资源的保护管理要求不是很高时,使用多线程。
二、线程的生命周期
线程的五种基本状态
新建状态(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处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完正常死亡,或者因为未补货到的异常猝死,退出了run()方法,该线程结束生命周期。
有必要提一下,CPU选择就绪状态线程进入运行状态时具有随机性(虽然会有优先级之分),处于就绪状态的线程会被随机切换,也就是多线程实现的原理,但CPU处理速度快,时间差很小,感觉起来就是同时执行的。这里又产生了一个新问题:在多线程只是CPU快速的切换线程,并不是真正意义上的同时执行,所以单位时间内的执行效率并没有提高,为什么说多线程并发提高了效率(针对单个CPU)?
因为多线程并发节省的不是处理时间,而是等待时间。通过线程开销小切换快的特点,能有不同的执行路径同时在CPU上切换并行,才是最主要的提高效率。对于多核主机,多个CPU并行效率会更高。
三、线程的三种创建方法
1、继承Thread重写run()方法
创建一个MyThrea类,通过继承Thread类,并重写其中的run()方法
public class ThreadTest {
/*
*继承Thread重写run()
*
**/
public static void main(String[] args) {
for(int i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i); //获取当前线程名
if(i == 30) {
Thread mythread1 = new MyThread(); //创建一个新线程
Thread myThread2 = new MyThread();
mythread1.start(); //线程进入就绪状态
myThread2.start();
}
}
}
}
class MyThread extends Thread{
private int i = 0;
@Override
public void run() {
System.out.println("in MyThread run");
for(i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
2、实现Runnable接口并重写run()方法
这里有个问题,Thread类和Runnable接口的run()有什么关联吗,我们来看一下源码
public
class Thread implements Runnable{
@Override
public void run() {
if (target != null) {
target.run();
}
}
...
}
public interface Runnable {
public abstract void run();
}
可以看到Runnable接口中的run方法是一个抽象方法(抽象方法需要被实现),而Thread实现了Runnable接口,但却把run方法的实现交给了target对象。所以通过创建Runnable实例对象也可以创建Thread
public class ThreadTest {
public static void main(String[] args) {
for(int i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if(i == 30) {
Runnable myRunnable = new MyRunnable(); // 创建一个Runnable实现类的对象
Thread thread1 = new Thread(myRunnable);
// 将myRunnable作为Thread target创建新的线程
Thread thread2 = new Thread(myRunnable);
thread1.start();
thread2.start();
}
}
}
}
class MyRunnable implements Runnable{
private int i = 0;
@Override
public void run() {
System.out.println("in MyRunnalble run");
// TODO Auto-generated method stub
for(i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
Runnable实现类也可以是匿名内部类,使用匿名内部类也可以创建线程
public class ThreadTest {
public static void main(String[] args) {
for(int i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if(i == 30) {
new Thread( new Runnable(){ //匿名内部类,实现Runnable类的run方法
@Override
public void run() {
// TODO Auto-generated method stub
}
}
).start();
}
}
}
}
3、使用Callable和Future接口创建线程。具体是创建Callable接口的实现类,并实现clall()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。
public class ThreadTest {
public static void main(String[] args) {
Callable<Integer> myCallable = new MyCallable(); // 创建MyCallable对象
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象
for(int i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if(i == 30) {
Thread thread = new Thread(ft);
thread.start();
}
}
System.out.println("主线程循环for循环执行完毕...");
try {
int sum = ft.get();
System.out.println("sum = " + sum);
} catch (InterruptedException e) { //中端异常处理
e.printStackTrace();
}catch (ExecutionException e) { //线程执行异常处理
e.printStackTrace();
}catch(RejectedExecutionException e) { //提交线程池被拒绝异常处理
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
private int i = 0;
//call方法与run方法不同,有返回值
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
int sum = 0;
for(; i<100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
sum += i;
}
return sum;
}
}
在Callable中的cell方法与run方法类似,但是cell方法具有返回值,这是run方法不具备的,因此使用cell方法创建的线程,还能够获取线程的返回值。而FutureTask类同时具有Runnable和Future接口的属性,既解决了线程的创建,有解决了返回值冲突的问题。
public class FutureTask<V> implements RunnableFuture<V>{
...
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
笔者是个小菜鸡,若有错误欢迎纠正,希望能多获得大家的建议,谢谢!
参考:https://www.cnblogs.com/lwbqqyumidi/p/3804883.html
↑↑↑深受这个博客的影响,有兴趣的可以去理解消化↑↑↑