目录
Java 进程和线程
基础概念
程序是为完成特定任务、用某种语言编写的一组指令的集合。指一段静态的代码,是一个静态的概念。
进程(Process)具有一定独立功能程序的运行过程,是系统进行资源分配和调度的一个独立单位,重点在系统调度和单独的单位,也就是说进程是可以独立运行的一段程序。
- 进程是程序的一次执行过程,通常是一个可执行程序在内存中的一个完整副本,每个进程都有自己的数据段、栈段和代码段,是一段完整的程序,在内存中占据较大的空间,是系统进行调度和资源分配的一个独立单位。是一个动态的概念。
- 多进程是指操作系统能同时运行多个任务(程序) 。
- 线程是进程中的一个实体,用来描述进程的执行,它负责执行包括在进程的地址空间中的代码。创建一个进程时,它的第一个线程称为主线程,它由系统自动生成。
- 多线程是指在同一程序中有多个顺序流在执行。
进程的特征:
- 独立性:进程是系统中独立存在的的实体。一个用户进程不可以直接访问其他进程的地址空间。
- 动态性:进程具有自己的生命周期和各种不同的状态
- 并发性:多个进程能在单个处理器上并发执行,多个进程之间不会互相影响。
启动进程的方法
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);
}
}
僵尸进程和孤儿进程
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资源,减少CPU空转时间。
- 伸缩性,通过CPU核数来提升性能。
不一定,如果针对密集型计算的应用使用单线程避免多线程中的切换反而会提高代码的运行效率。资源限制的挑战,在并发编程时需要考虑到资源上的限制。如果受制于资源,整体程序的速度肯定会慢下来。
- 使用多线程并不能增加CPU的处理能力,也不一定会提升CPU的吞吐量
- 基于Internet的应用有必要使用多线程
- 对于硬件资源的限制,可以使用集群来跑。
- 对于软件资源上的限制,可以复用资源,比如复用数据库连接
- 根据资源的限制,灵活的去调整并发度。
- 基于线程所需的开销更少
- 在多任务中,各个进程需要分配它们自己独立的地址空间
- 多个线程可共享相同的地址空间并且共同分享同一个进程
- 进程间调用涉及的开销比线程间通信多
- 线程间的切换成本比进程间切换成本低
- 设计更复杂、上下文切换的开销、增加资源消耗
- 每个Java程序都有一个隐含的主线程 application main 方法
- Applet小程序【application let】,主线程指挥浏览器加载并执行java小程序
- 减轻编写交互频繁、涉及面多的程序的困难
- 程序的吞吐量会得到改善
- 由多个处理器的系统,可以并发运行不同的线程。(否则,任何时刻只有一个线程在运行)
线程的编程4种实现方法
1、继承Thread
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。
public class Thread implements Runnable@FunctionalInterface // 函数式接口,其中包含一个抽象方法 runpublic interface Runnable {public abstract void 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接口创建线程
@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() 方法来获得子线程执行结束后的返回值。
@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<V> implements RunnableFuture<V> {}
import java.util.concurrent.RunnableFuture;
public class D09 implements Callable<Integer> {// 实现泛型接口,在实现时候确定
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<Integer> 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());//获取对应的线程对象的执行结果。
}
}
4、使用线程池创建线程
线程进程的五个阶段
- 新建状态:
使用 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<Integer> caller = new MyCallable(i * 1000 + 1, (i + 1) * 1000);
//使用线程池执行任务并获取Future对象
fs[i] = (Future) es.submit(caller);
}
int res= 0;
for(int i = 0;i<fs.length;i++) {
Object obj = fs[i];//?
if(obj!=null&&obj instanceof Integer)
res+=(Integer)obj;
}
es.shutdown();//关闭线程池
System.out.println("Main:"+res);
}
}
class MyCallable implements Callable<Integer> {
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 方法
![](https://i-blog.csdnimg.cn/blog_migrate/d9588311503f71eb5222985df7a18a59.png)