进程与线程
过程
在操作系统中,每个独立的执行的程序都是一个进程,计算机上都是安装的多任务操作系统,能同时执行多个应用程序。
在多任务操作系统中,逻辑上它们是同时运行的,但实际上并非如此。所有应用程序均由CPU执行。对于CPU来说,一次只能运行一个程序。操作系统给每个进程分配有限的CPU占用时间,并且占用时间在同一个时间线上。
线
每个运行的程序都是一个进程,在一个程序中还可以有多个可执行的单元,每个单元称为线程。每个进程中至少存在一个线程。
当Java程序启动时,会生成一个进程,并创建一个线程来执行main函数的代码。代码会按照调用的顺序从上到下执行,称为单线程程序。如果想让程序多端的代码交替运行,就需要创建多个线程。
线程和进程由CPU执行,逻辑上在同一时间线上同时执行。
线程创建
Java中提供了两种多线程的实现方式,一种是继承java.lang
包下的Thread
类,重写run()
方法,在run方法类实现线程代码;另一种方法是实现java.lang.Runable
接口,重写run()
方法。
继承线程
//继承Thread
import java.lang.Thread;
public class App {
public static void main(String[] args) {
MyThread myThread=new MyThread();
myThread.run();
for (int i=0;i<10;i++){
System.out.println("main is running");
}
}
static class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("MyThread is running");
}
}
}
}
上面的代码演示了单线程的情况。 run是继承Thread创建的线程。线程结束后主程序结束。
多线程需要程序连续运行才能体现出来,系统分配CPU时间。
//多线程演示用start()方法,run会抢占线程
import java.lang.Thread;
public class App {
public static void main(String[] args) {
MyThread myThread=new MyThread();
myThread.start();
while (true){
System.out.println("main is running");
}
}
static class MyThread extends Thread{
@Override
public void run() {
while (true){
System.out.println("MyThread is running");
}
}
}
}
如图看出两个线程是交替运行,由系统膏分配CPU占用时间。注意:用
start()
开启线程,不能用run()
会抢占线程。
多线程和单线程的区别在于CPU时间的动态利用。单线程独占,多线程系统分配。
实现Runnable接口
Thread创建线程的缺点是必须继承Thread,已经有继承关系的类不能被继承(Java单继承原则)。 Runnable接口的优点是它只实现了Thread的run方法。只需要重写逻辑,将Runnable作为参数传递给Thread即可,不需要继承Thread。
//将Runnable作为参数传到Thread
public class App {
public static void main(String[] args) {
Thread thread=new Thread(new MyRunnable());
thread.start();
Thread thread1=new Thread(new MyRunnableOne());
thread1.start();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
while (true){
System.out.println("Runnable is running");
}
}
}
static class MyRunnableOne implements Runnable{
@Override
public void run() {
while (true){
System.out.println("RunnableOne is running");
}
}
}
}
线程应用
Thread和Runnable都可以实现线程,那么有什么区别呢?
线程是独立的
class MyThreadOne extends Thread{
private int total=100;
@Override
public void run() {
while (total>=1){
//获取当前线程
Thread thread=Thread.currentThread();
//获取线程名
String th_name=thread.getName();
System.out.println(th_name+"正在发售第"+total--+"张票");
}
}
}
public class MainApp {
public static void main(String[] args) {
MyThreadOne myThreadOne1=new MyThreadOne();
MyThreadOne myThreadOne2=new MyThreadOne();
MyThreadOne myThreadOne3=new MyThreadOne();
myThreadOne1.start();
myThreadOne2.start();
myThreadOne3.start();
}
}
代码实现了继承Thread,内部有100张票,三个线程发售。
由结果可以发现线程Thread-0和1,2都各自发售了第100次票,显然他们是独立的。
可运行线程是协作的
package com.company.runnable;
class MyRunnable implements Runnable{
private int total=100;
@Override
public void run() {
while (total>=1){
//获取当前线程
Thread thread=Thread.currentThread();
//获取线程名
String th_name=thread.getName();
System.out.println(th_name+"正在发售第"+total--+"张票");
}
}
}
public class MainApp {
public static void main(String[] args) {
Thread myThreadOne1=new Thread(new MyRunnable());
Thread myThreadOne2=new Thread(new MyRunnable());
Thread myThreadOne3=new Thread(new MyRunnable());
myThreadOne1.start();
myThreadOne2.start();
myThreadOne3.start();
}
}
代码继承Runnable重写run方法,内部100张票
由结果可以看出所有线程共享成员变量。
除了上面案例外由多态特性
也可以理解,通过实例化Thread
创建是不同的对象,对象间是独立的,毫无关联的。而实现Runnable
接口,仅仅是重写了run()
方法,他们共享成员变量。
线程生命周期及状态转换
Java对象中的任何对象都有生命周期,线程也不例外。当Thread对象被创建时,线程的生命周期就开始了。当run抛出异常或阶段关闭时,线程结束。
线程的生命周期分为五个阶段:新建状态(New)、就绪状态(Runnable)、运行状态(Running)、阻塞状态(Blocked)、死亡状态(Termminate)。线程的不同状态表明该线程当前正在运行。活动。
- 新建状态
JVM分配内存,没有任何线程特征。 - 就绪状态
当线程调用start()方法后,线程进入就绪状态,线程位于线程队列中,等待CPU分配。 - 运行状态
获得CPU使用权后执行线程执行体,该程序处于运行状态。系统剥夺CPU使用权后,处于等待状态,等待再次占有CPU,直至程序结束。 - 阻塞状态
运行的线程由于某种原因,被迫让出CPU使用权而进入阻塞状态。此时线程并没有进入等待对列,CPU由其他等待对列中的线程使用,直至解除阻塞状态,进入等待对列,等待系统分配CPU使用权后才能再次执行。
notify()
和·wait()
方法可以使线程进入阻塞状态。sleep()
方法使阻塞特定时间,join()
加入新进程会阻塞原进程,直至新进程运行完毕。
线程调度
程序的多个线程在逻辑上是并发的。一个线程想要执行,就必须获得CPU的所有权。 Java虚拟机按照特定的机制为线程分配CPU的使用权,这种机制称为线程调度。
线程调度有两种模型:分时调度和抢占式调度。分时调度是指按照执行线上同一维度的时间片轮流分配CPU的使用权。抢占式调度按照优先级分配CPU使用权。
线程优先级
Java JVM默认是抢占式调度,优先级决定了CPU分配顺序。
图中大写的常量设置线程优先级。
线程休眠
sleep(long millis)
方法让线程休眠一段时间把CPU让给其他线程。该方法时静态方法,只能控制当前的线程休眠,休眠结束后,该线程进入等待对列。
螺纹优惠
线程让步时正在执行的线程将CPU让给其他线程,yield()
方法。
线程队列跳转
线程可以通过join()
方法在某个线程之前执行。
线程安全
多线程共享资源时由于延迟或其他原因导致的错误。
class MyRunnable implements Runnable{
private int total=5;
@Override
public void run() {
while (total>=1){
//获取当前线程
Thread thread=Thread.currentThread();
//获取线程名
String th_name=thread.getName();
//添加延迟
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(th_name+"正在发售第"+total--+"张票");
}
}
}
public class MainApp {
public static void main(String[] args) {
Thread myThreadOne1=new Thread(new MyRunnable());
Thread myThreadOne2=new Thread(new MyRunnable());
Thread myThreadOne3=new Thread(new MyRunnable());
myThreadOne1.start();
myThreadOne2.start();
myThreadOne3.start();
}
}
代码模拟了10毫秒的延迟,线程执行的进度是不一样的。
由于线程都有10毫秒的休眠,出现了很多重复的情况。
Java提供了同步机制,当多个线程使用同一个共享资源时将执行共享资源的代码放在synchronized
关键字修饰的代码块中,这个代码块被称作同步代码块。
synchronized 返回值类型 方法名([参数列表]){代码块}
synchronized (obj){
while (total>0){
//获取当前线程
Thread thread=Thread.currentThread();
//获取线程名
String th_name=thread.getName();
//添加延迟
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(th_name+"正在发售第"+total--+"张票");
}
}