Java 进程和线程 ---- (上)

目录

Java 进程和线程

基础概念

启动进程的方法

僵尸进程和孤儿进程

多线程

线程的编程4种实现方法

1、继承Thread

2、实现Runnable接口

3、使用Callable和Future接口创建线程

泛型简述

4、使用线程池创建线程

线程进程的五个阶段

线程的优先级

Thread 方法


Java 进程和线程

基础概念

程序是为完成特定任务、用某种语言编写的一组指令的集合。指一段静态的代码,是一个静态的概念

进程(Process)具有一定独立功能程序的运行过程,是系统进行资源分配和调度的一个独立单位,重点在系统调度和单独的单位,也就是说进程是可以独立运行的一段程序。

  • 进程是程序的一次执行过程,通常是一个可执行程序在内存中的一个完整副本,每个进程都有自己的数据段、栈段和代码段,是一段完整的程序,在内存中占据较大的空间,是系统进行调度和资源分配的一个独立单位。是一个动态的概念。
  • 多进程是指操作系统能同时运行多个任务(程序) 。
线程是进程中的一个独立执行线索,是进程中的一个实体,是 CPU 调度和分派的基本单位,是比进程更小的 能独立运行的基本单位,线程自己基本上不拥有系统资源。在运行时,只是暂用一些计数器、寄存器和栈 (栈帧)。
  • 线程是进程中的一个实体,用来描述进程的执行,它负责执行包括在进程的地址空间中的代码。创建一个进程时,它的第一个线程称为主线程,它由系统自动生成。
  • 多线程是指在同一程序中有多个顺序流在执行。
每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含 1--n 个线程。(进程是资源分配的最小单位)
同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器 PC ,线程切换开销小。(线程是 cpu 调度的最小单位)

进程的特征:

  1. 独立性:进程是系统中独立存在的的实体。一个用户进程不可以直接访问其他进程的地址空间。
  2. 动态性:进程具有自己的生命周期和各种不同的状态
  3. 并发性:多个进程能在单个处理器上并发执行,多个进程之间不会互相影响。
并行:多个 cpu (中央处理器)实例或者多台机器同时执行一段处理逻辑(指令),是真正的同时。
并发:同一时刻只有一条指令执行,通过 cpu 调度算法,让用户看上去同时执行,实际上从 cpu 操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用 TPS (每秒钟处理的事务数)或者 QPS (每秒钟处理的请求数)来反应这个系统的处理能力。
 

启动进程的方法

 
package xiancheng;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;


public class D06 {
	public static void main(String[] args) throws IOException {
		//启动进程方法一
		ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "ipconfig/all"); // 用于构建进程的对象
		Process p = pb.start();// 启动进程
		BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));// 获取进程的输出内容
		String temp = null;
		while ((temp = br.readLine()) != null)
			System.out.println(temp);
     	//启动进程方法二
		String cmd = "cmd " + "/c " + "ipconfig/all";
		Process process = Runtime.getRuntime().exec(cmd);
		BufferedReader br1 = new BufferedReader(new InputStreamReader(process.getInputStream()));
		String temp1 = null;
		while ((temp1 = br1.readLine()) != null)
			System.out.println(temp1);
	}
}

僵尸进程和孤儿进程

 
       僵尸进程是当子进程父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出 ,子进程被 init 接管,子进程退出后 init 会回收其占用的相关资源 --- 是对系统资源 的浪费,必须解决;
       孤儿进程是一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被 init 进程 ( 进程号为 1) 所收养,并由 init 进程对它们完成状态收集工作。 --- 没有什么危害。
 
主线程
 
       线程是进程中的一个实体,用来描述进程的执行,它负责执行包括在进程的地址空间中的代码。 创建一个进程时,它的第一个线程称为主线程,它由系统自动生成 它是产生其他子线程的线程。
       通常它是最后完成执行,因为它执行各种关闭动作。注意这里不绝对。 
 
进程中线程之间的关系
 
线程不像进程,一个进程中的线程之间是没有父子之分的,都是平级关系。即线程都是一样的 , 退出了一个不会影响另外一个。但是所谓的 " 主线程 "main ,其入口代码是类似这样的方式调用 main 的: exit(main(...)) main 执行完 之后会调用 exit() exit 会让整个进程 over 终止,那所有线程自然都会退出。
 
进程和线程的关系
 
1 、一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(主线程)。
2 、资源分配给进程,同一进程的所有线程共享该进程的所有资源。
3 、线程在执行过程中需要协作同步。
4 CPU 分给线程,即真正在处理机上运行的是线程。
5 、线程是指进程内的一个执行单元,也是进程内的可调度实体,两者都是动态的概念。
 
package xiancheng;

public class D07 {
	public static void main(String[] args) {
		Thread a = new Thread() {// 写个内部类默认继承Thread
			public void run() {
				// Thread.currentThread()获取当前正在运行的线程对象
				// .getName()获取线程对象的标识名称
				System.out.println(Thread.currentThread().getName());
			}

		};
		a.start();// 启动子线程
		for(int i =0;i<15;i++)
		System.out.println(Thread.currentThread().getName()+i);// 输出的是主线程的

	}
}
多次运行会发现执行顺序不一定相同,这就是多线程运行过程的不可重现性(执行的顺序不能保证),运行结果应该是一致的
 
线程和进程的区别 [ 面试 ]
 
调度:线程是 CPU 调度和分配的基本单位,进程是系统资源分配和调度的基本单位。
并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行,一个进程至少有一个线 程(单进程单线程),一个线程必须隶属于某个进程。
拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。进程和线程最大的区别在于:进程是由操作系统来控制的,而线程是由进程来控制的。 线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小, 多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响 。
 

多线程

多线程方式是指在一个程序中存在多个线程,每一个线程执行一个独立的任务,多个线程可以并发执行在 Java 中,一个应用程序可以包含多个线程,每个线程执行特定的任务,并可与其他线程并发执行, 多线程使系统的空转时间最少,提高 CPU 利用率,多线程编程环境用方便的模型隐藏 CPU 在任务间切换的细节
  • 吞吐量,充分利用cpu资源,减少CPU空转时间。
  • 伸缩性,通过CPU核数来提升性能。
在许多情况中可以显式地使用线程以提高程序的性能、响应速度或组织。要用到多线程的主要是需要处理大量的 IO操作时或处理的情况需要花大量的时间等等,比如:读写文件、视频图像的采集、处理、显示、保存等。
线程的工作场景主要有两条:
1 、并发操作,避免阻塞和更有效利用资源。典型的例子有:在长时间工作的程序中使用工作线程避免界面失去响应。在网络下载程序中,使用多个线程提高对网络的使用效率,更快下载文件。
2 、并行,线程是处理器调度的最小单位。如果你的计算机配置了多个处理器或者内核,那么可以同时利用多个处理器同时计算,加快问题解决的速度。
 
多线程机制会提高程序的运行效率吗?

不一定,如果针对密集型计算的应用使用单线程避免多线程中的切换反而会提高代码的运行效率。资源限制的挑战,在并发编程时需要考虑到资源上的限制。如果受制于资源,整体程序的速度肯定会慢下来。

  • 使用多线程并不能增加CPU的处理能力,也不一定会提升CPU的吞吐量
  • 基于Internet的应用有必要使用多线程
 
解决的方法有以下几点 :
  • 对于硬件资源的限制,可以使用集群来跑。
  • 对于软件资源上的限制,可以复用资源,比如复用数据库连接
  • 根据资源的限制,灵活的去调整并发度。
基于线程的多任务处理的优点
  • 基于线程所需的开销更少
  • 在多任务中,各个进程需要分配它们自己独立的地址空间
  • 多个线程可共享相同的地址空间并且共同分享同一个进程
  • 进程间调用涉及的开销比线程间通信多
  • 线程间的切换成本比进程间切换成本低
基于多线程编程的缺点
  • 设计更复杂、上下文切换的开销、增加资源消耗

 

Java 与多线程
 
Java 语言的一个重要功能特点就是内置对多线程的支持,它使得编程人员可以很方便地开发出具有多线程功能,能同时处理多个任务的功能强大的应用程序 Java 的所有类都是在多线程的思想下定义的, Java 利用线程使整个系统成为异步。
  • 每个Java程序都有一个隐含的主线程 application main 方法
  • Applet小程序【application let】,主线程指挥浏览器加载并执行java小程序
为什么使用线程
  • 减轻编写交互频繁、涉及面多的程序的困难
  • 程序的吞吐量会得到改善
  • 由多个处理器的系统,可以并发运行不同的线程。(否则,任何时刻只有一个线程在运行)

线程的编程4种实现方法

1、继承Thread

Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。

public class Thread implements Runnable
@FunctionalInterface // 函数式接口,其中包含一个抽象方法 run
public interface Runnable {
     public abstract void run ();
}
启动线程的唯一方法就是通过 Thread 类的 start() 实例方法,不能直接调用 run() 方法。 start() 方法是一个 native方法(java调用非java代码的接口 ),它将启动一个新线程,并执行run() 方法。这种方式实现多线程很简单,通过自己的类直接 extend Thread ,并 复写 run() 方法,就可以启动新线程并执行自己定义的 run() 方法。
 
package xiancheng;

public class D04 extends Thread {
	private int i;
	public void run() {
		for (; i < 50; i++) {
			System.out.println(getName() + " " + i);
		}
	}

	
	public static void main(String[] args) {//主线程
		for (int i = 0; i < 50; i++) {
			System.out.println(Thread.currentThread().getName() + " " + i);
			if (i == 20) {
				new D04().start();//开启一个线程
			}
			if (i == 30) {
				new D04().start();//开启一个线程
			}
		}
	}
}

2、实现Runnable接口

@FunctionalInterface // 函数式接口,简化定义方式为 lambda 表达式
public interface Runnable {
    public abstract void run ();
}
package xiancheng;

public class D03 implements Runnable {
	private int i;

	public void run() {

		for (; i < 30; i++) {
			System.out.println(Thread.currentThread().getName() + " " + i);
		}

	}

	public static void main(String[] args) {
		for (int i = 0; i < 30; i++) {
			System.out.println(Thread.currentThread().getName() + " " + i);
			if (i == 20) {
				D03 s = new D03();
				new Thread(s, "新线程1").start();
				new Thread(s, "新线程2").start();
			}
		}

	}
}

可以使用lambda表示式进行定义,或者使用匿名内部类进行定义

package xiancheng;

public class D08 {
	public static void main(String[] args) {
		// 匿名内部类的写法
		Thread a = new Thread(new Runnable() {
			public void run() {
				for (int i = 0; i < 20; i++) {
					System.out.println(Thread.currentThread().getName() + "--" + i);
				}
			}
		});
		// lambda表达式的写法---要求接口必须是函数式接口
		Thread b = new Thread(() -> {
			for (int i = 0; i < 20; i++) {
				System.out.println(Thread.currentThread().getName() + "--" + i);
			}
		});
		a.start();//进入就绪态,调用run方法进入运行态
        b.start();
	}
}

3、使用Callable和Future接口创建线程

具体是创建 Callable 接口的实现类,并实现 call() 方法。并使用 FutureTask 来包装 Callable 实现类的对象,且以此 FutureTask 对象作为 Thread 对象的 target 来创建线程 接口定义, Callable 接口用于定义线程的执行逻辑。
@FuntionalInterface // 属于函数式接口,所以可以直接使用 lambda 表达式进行定义
public interface Callable < V > { //<> 写法叫做泛型,用于定义返回的数据类型
       V call () throws Exception ;
}

1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。

2. 创建 Callable 实现类的实例,使用 FutureTask 实现类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。

3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。

4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

Callable接口用于定义线程的执行逻辑
@FuntionalInterface // 属于函数式接口,所以可以直接使用 lambda 表达式进行定义
public interface Callable < V > { //<> 写法叫做泛型,用于定义返回的数据类型
     V call () throws Exception ;
}

泛型简述

Java泛型设计原则:只要在编译时期没有出现警告,那么运行时期就不会出现ClassCastException异常

泛型:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型;

泛型类/接口就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来,这样的话,用户明确了什么类型,该类就代表着什么类型,用户在使用的时候就不用担心强转的问题,运行时转换异常的问题了。

在类或者接口上定义的泛型,在类的方法中也可以使用!

package xiancheng;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;//public class FutureTask implements RunnableFuture {}
import java.util.concurrent.RunnableFuture;

public class D09 implements Callable {// 实现泛型接口,在实现时候确定

	public Integer call() throws Exception {
		int i = 0;
		for (; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + " " + i);
		}
		return i;
	}

	public static void main(String[] args) throws InterruptedException, ExecutionException {

		FutureTask ft = new FutureTask<>(new D09());

		for (int i = 0; i < 50; i++) {
			System.out.println(Thread.currentThread().getName() + " " + i);
			if (i == 20) {
				new Thread(ft, "新线程").start();// 启动一个子线程
			}
		}
		System.out.println(ft.get());//获取对应的线程对象的执行结果。
	}
}
注意 :FutureTask 实现了 Future Runnable 接口,所以 new Thread(futureTask), 当执行 thread.start() 方法时会自动调用 Callable 接口实现中的 call 方法。当调用 futureTask.get() 方法时可以获取对应的线程对象的执行结果。
 

4、使用线程池创建线程

享元模式
享元模式 Flyweight Pattern 主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
优点: 大大减少对象的创建,降低系统内存的使用,以提高程序的执行效率。
缺点: 提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部 状态的变化而变化,否则会造成系统的混乱。
 
使用 ExecutorService Callable Future 实现有返回结果的线程 , 连接池的具体实现实际上是依赖于ThreadPoolExecutor。
 

线程进程的五个阶段

线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
  • 新建状态:

    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 就绪状态:

    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  • 运行状态:

    如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态:

    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

  • 死亡状态:

    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

线程的优先级

每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。

Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。

Thread 定义了其中 3 个常数:
(1) MAX_PRIORITY, 最大优先级(值为10);
(2)MIN_PRIORITY,最小优先级(值为1);
(3)NORM_PRIORITY,默认优先级,(值为5),注意是Java主线程默认的优先级是 5 。

默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。

具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

package xiancheng;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.sun.corba.se.impl.orbutil.closure.Future;

public class D10 {
	public static void main(String[] args) {
		// 创建一个固定大小的连接池,经常使用少量线程以应对波峰请求
		ExecutorService es = Executors.newFixedThreadPool(3);// 固定线程池
		// 为了使用返回结果所以使用Callable
		Future[] fs = new Future[10];
		for (int i = 0; i < fs.length; i++) {
			Callable caller = new MyCallable(i * 1000 + 1, (i + 1) * 1000);
		//使用线程池执行任务并获取Future对象
			fs[i] = (Future) es.submit(caller);
		}
		int res= 0;
		for(int i = 0;i {
	private int begin;
	private int end;

	public MyCallable(int begin, int end) {
		this.begin = begin;
		this.end = end;

	}

	public Integer call() throws Exception {
		System.out.println(Thread.currentThread() + "----" + begin + ".." + end);
		int res = 0;
		for (int i = begin; i <= end; i++)
			res += 1;
		return res;
	}

}

Thread 方法

Java 进程和线程 ---- (上)_第1张图片

Thread类的静态方法
 
Java 进程和线程 ---- (上)_第2张图片
 

 

你可能感兴趣的:(多线程,编程语言,java)