一、并发和并行
并行:指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。
并发:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。
二、进程和线程
概念
一个程序就是一个进程,而一个程序中的多个任务则被称为线程。
进程是表示资源分配的基本单位,线程是程序执行(CPU调度)的最小单位。
举个例子:
打开你的计算机上的任务管理器,会显示出当前机器的所有进程,QQ,360等,当QQ运行时,就有很多子任务在同时运行。比如,当你边打字发送表情,边好友视频时这些不同的功能都可以同时运行,其中每一项任务都可以理解成“线程”在工作。
区别
- 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
- 资源拥有:同一进程内的线程共享本进程的资源,但是进程之间的资源是独立的。
- 一个进程崩溃后,在保护模式下不会对其他进程产生影响;但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
- 进程切换要比线程切换慢。每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。(进程切换与线程切换的区别参考这篇博文:进程切换与线程切换的区别?)
- 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
- 线程是处理器调度的基本单位,但是进程不是。
- 两者均可并发执行。
三、线程的状态图
线程的5种状态
1. 新建状态(New):
用new语句创建的线程处于新建状态,此时它和其他Java对象一样,仅仅在堆区中被分配了内存。
2. 就绪状态(Runnable):
当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态,Java虚拟机会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待获得CPU的使用权。
3. 运行状态(Running):
处于这个状态的线程占用CPU,执行程序代码。只有处于就绪状态的线程才有机会转到运行状态。4. 阻塞状态(Blocked):
阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU。直到线程重新进入就绪状态,它才有机会转到运行状态。阻塞状态可分为以下3种:
- 位于对象等待池中的阻塞状态(Blocked in object’s wait pool):
当线程处于运行状态时,如果执行了某个对象的wait()方法,Java虚拟机就会把线程放到这个对象的等待池中,这涉及到“线程通信”的内容。
- 位于对象锁池中的阻塞状态(Blocked in object’s lock pool):
当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中,这涉及到“线程同步”的内容。
- 其他阻塞状态(Otherwise Blocked):
当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O请求时,就会进入这个状态。
5. 死亡状态(Dead):
当线程退出run()方法时,就进入死亡状态,该线程结束生命周期。
涉及到的方法:
- sleep(long millis):
属于Thread类,主要的作用是让当前线程停止执行millis毫秒,把cpu让给其他线程执行,但不会释放对象锁和监控的状态,到了指定时间后线程自动苏醒,并返回到可运行状态,不是运行状态。
- wait() notify():
wait() 属于Object类,与sleep()的区别是当前线程会释放锁,进入等待此对象的等待池。比方说,线程A调用Obj.wait(),线程A就会停止运行,而转为等待状态。至于等待多长时间,那就看其他线程是否调用Obj.notify()。
注意:它必须包含在Synchronzied语句中,无论是wait()还是notify()都需要首先获得目标的对象的一个监视器。
先来解释一下 "Synchronzied" :
它是一种同步锁。作用是实现线程间同步。对同步的代码加锁,使得每一次,只能有一线程进入同步块,从而保证线程间的安全性。它修饰的对象有以下几种:
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的部分,进入同步代码前要获得给定对象的锁
- 修饰一个实例方法,进入同步代码前要获得当前实例的锁
- 修饰一个静态方法,进入同步代码前要获得当前类的锁
(具体可参考我的博客:)
- join():
使用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。如:让三个线程T1,T2,T3顺序执行,T3调用T2,T2调用T1。
package zl; public class JoinTest { public static void main(String[] args) { Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("我是t1线程"); } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { thread1.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我是t2线程"); } }); Thread thread3 = new Thread(new Runnable() { @Override public void run() { try { thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我是t3线程"); } }); thread1.start(); thread2.start(); thread3.start(); } }
输出结果:
- yield():
yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU。因为当前线程让出cpu后,还会进行cpu资源的争夺,所以有可能在进入到暂停状态后马上又被执行。 在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
package zl; public class YiledTest implements Runnable { private String name; public YiledTest(String name) { this.name = name; } public void run() { for (int i = 0; i < 10; i++) { System.out.println(name + ":" + i); //t1线程到5的时候暂停,让出cpu,但此时t1和t2都可抢夺cpu,所以结果不确定 if ("t1".equals(name) && i == 5) { System.out.println(name + ":" + i +"......yield............."); Thread.yield(); } } } public static void main(String[] args) throws Exception { Thread t1 = new Thread(new YiledTest("t1")); Thread t2 = new Thread(new YiledTest("t2")); t1.start(); t2.start(); } }
运行结果:
start()方法和run()方法的区别
- 定义:start()方法在java.lang.Thread类中定义;run()方法在java.lang.Runnable接口中定义,必须在实现类中重写。
- 是否创建新线程:当程序调用start()方法时,会创建一个新线程并启动进入可运行状态(runnable)(此时线程要等待CPU调度,不同的JVM有不同的调度算法,线程何时被调度是未知的。因此,start()方法的被调用顺序不能决定线程的执行顺序),线程进入运行状态时(running)执行run()方法。但是如果我们直接调用run()方法,会创建新的线程但不会启动,run()方法将作为当前调用线程本身的常规方法调用执行,并且不会发生多线程。
举例:
public class ThreadTest extends Thread{
@Override
public void run() {
System.out.println("当前线程的名称:"+ Thread.currentThread().getName());
System.out.println("run()");
}
public static void main(String[] args) {
//Thread-0 Thread-0
ThreadTest threadTest = new ThreadTest();
//当调用线程类实例的start()方法时,会创建一个新的线程,默认名称为Thread-0,并启动该线程进入可运行状态,然后调用run()方法,并在其中执行所有内容。
threadTest.start();
}
}
输出结果:
一个线程的创建肯定是由另一个线程完成的,如:main方法中创建线程"Thread-0"
public class ThreadTest extends Thread{
@Override
public void run() {
System.out.println("当前线程的名称:"+ Thread.currentThread().getName());
System.out.println("run()");
}
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
//当调用ThreadTest类的run()方法时,虽然创建了新线程,但并没有启动该进程。在当前线程即主线程main上执行run()方法。因此,没有发生多线程。run()方法是作为正常函数被调用。
threadTest.run();
}
}
输出结果:
参考链接:线程状态转换图及各部分介绍