今日总结:
多线程编程基础
串行与并发、进程与线程、线程的创建
线程的生命周期、线程的命名、线程的优先级
临界资源问题产生原因和解决方案
同步代码段、同步方法
显式锁Lock和重入锁ReentrantLock
死锁和解决方案
wait_notify_notifyAll和生产者消费者设计模式
基础概念
程序是为完成特定任务、用某种语言编写的一组指令的集合。指一段静态的代码,是一个静态的概念
进程是具有一定独立功能程序的运行过程,是系统进行资源分配和调度的一个独立单位,重点在系统调度和 单独的单位,也就是说进程是可以独立运行的一段程序。
- 进程是程序的一次执行过程,通常是一个可执行程序在内存中的一个完整副本,每个进程都有自己的数据段、栈段和代码段,是一段完整的程序,在内存中占据较大的空间,是系统进行调度和资源分配的一 个独立单位。是一个动态的概念
- 多进程是指操作系统能同时运行多个任务(程序)
线程是进程中的一个独立执行线索,是进程中的一个实体,是CPU调度和分派的基本单位,是比进程更小的 能独立运行的基本单位,线程自己基本上不拥有系统资源。在运行时,只是暂用一些计数器、寄存器和栈 (栈帧)
-
线程是进程中的一个实体,用来描述进程的执行,它负责执行包括在进程的地址空间中的代码。创建一个进程时,它的第一个线程称为主线程,它由系统自动生成
-
多线程是指在同一程序中有多个顺序流在执行
进程
每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。 (进程是资源分配的最小单位)
同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器PC,线程切换开销小。(线程是cpu调度 的最小单位)
启动进程的方法
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止
cmd /c启动后关闭窗口
启动进程方法1
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);
如果使用mac系统则编码为
ProcessBuilder pb=new ProcessBuilder("bash","-c","ifconfig");
练习:动态生成代码并编译执行
File f = new File("T1.java");
if (f.exists()) f.delete();
// 生成代码文件
PrintWriter pw = new PrintWriter(new FileWriter("T1.java"));
pw.println("public class T1{");
pw.println("public static void main(String[] args) throws Exception{");
pw.println("System.outprintln(\"Hello Java!\");");
pw.println("}}");
pw.close();
// 编译T1.java javac T1.java
Process process = Runtime.getRuntime().exec("cmd /c javac T1.java");
boolean runnable = true;
// process.getInputStream()用于获取进程的输出信息,不是报错信息。如果需要获取报错信息则应该使用
process.getErrorStream()
// javac编译通过实际上是没有响应信息,所以这里获取响应信息是不正确的
BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()));
while (true) {
String temp = br.readLine();
if (temp == null) break;
System.out.println(temp);
runnable = false;
}
// 运行T1.class
if (runnable) {
process = Runtime.getRuntime().exec("cmd /c java T1");
br = new BufferedReader(new InputStreamReader(process.getInputStream()));
while (true) {
String temp = br.readLine();
if (temp == null) break;
System.out.println(temp);
}
}
进程的三大特征
独立性
动态性
并发性
僵尸进程和孤儿进程
僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出 ,子进程被init接管,子进程退出后init会回收其占用的相关资源 —是对系统资源的浪费,必须解决
孤儿进程是一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。—没有什么危害
并行与并发
并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS(每秒钟处理的事务数)或者QPS(每秒钟处理的请求数)来反应这个系统的处理能力。
主线程
线程是进程中的一个实体,用来描述进程的执行,它负责执行包括在进程的地址空间中的代码。
创建一个进程时,它的第一个线程称为主线程,它由系统自动生成
- 它是产生其他子线程的线程
- 通常它是最后完成执行,因为它执行各种关闭动作。但是这里不绝对
public class T1{
public static void main(String[] args){
Thread t1=new Thread(){//定义子线程,是主线程启动的线程,是主线程的子线程
public void run(){
//Thread.currentThread()获取当前正在运行的线程对象
//.getName()获取线程对象的标识名称
System.out.println(Thread.currentThread().getName());
}
};
t1.start(); //启动子线程
System.out.println(Thread.currentThread().getName());
}
}
进程中线程之间的关系
线程不像进程,一个进程中的线程之间是没有父子之分的,都是平级关系。即线程都是一样的, 退出了一个不会影响另外一个。但是所谓的"主线程"main,其入口代码是类似这样的方式调用main的:exit(main(…))。main执行完之后 会调用exit()。exit会让整个进程over终止,那所有线程自然都会退出。
进程和线程的关系
1、一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(主线程)。
2、资源分配给进程,同一进程的所有线程共享该进程的所有资源。
3、线程在执行过程中需要协作同步。
4、CPU分给线程,即真正在处理机上运行的是线程。
5、线程是指进程内的一个执行单元,也是进程内的可调度实体,两者都是动态的概念。
线程和进程的区别 ——[注意]
- 调度:线程是CPU调度和分配的基本单位,进程是系统资源分配和调度的基本单位。
- 并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行,一个进程至少有一个线程(单进程单线程),一个线程必须隶属于某个进程。
- 拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
进程和线程最大的区别在于:进程是由操作系统来控制的,而线程是由进程来控制的。
线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小
多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响
多线程
多线程方式是指在一个程序中存在多个线程,每一个线程执行一个独立的任务,多个线程可以并发执行
在Java中,一个应用程序可以包含多个线程,每个线程执行特定的任务,并可与其他线程并发执行
多线程使系统的空转时间最少,提高CPU利用率,多线程编程环境用方便的模型隐藏CPU在任务间切换的细节
- 吞吐量,充分利用cpu资源,减少CPU空转时间
- 伸缩性,通过CPU核数来提升性能
在许多情况中可以显式地使用线程以提高程序的性能、响应速度或组织。要用到多线程的主要是需要处理大量的IO操作时或处理的情况需要花大量的时间等等,比如:读写文件、视频图像的采集、处理、显示、保存等
线程的工作场景主要有两条:
1、并发操作,避免阻塞和更有效利用资源。典型的例子有:在长时间工作的程序中使用工作线程避免界面失去响应。在网络下载程序中,使用多个线程提高对网络的使用效率,更快下载文件。
2、并行,线程是处理器调度的最小单位。如果你的计算机配置了多个处理器或者内核,那么可以同时利用多个处理器同时计算,加快问题解决的速度
多线程机制会提高程序的运行效率吗?
- 不一定,如果针对密集型计算的应用使用单线程避免多线程中的切换反而会提高代码的运行效率。
- 资源限制的挑战,在并发编程时需要考虑到资源上的限制。如果受制于资源,整体程序的速度肯定会慢下来。
解决的方法有以下几点:
-
对于硬件资源的限制,可以使用集群来跑。
-
对于软件资源上的限制,可以复用资源,比如复用数据库连接
-
根据资源的限制,灵活的去调整并发度。
基于线程的多任务处理的优点
基于线程所需的开销更少
- 在多任务中,各个进程需要分配它们自己独立的地址空间
- 多个线程可共享相同的地址空间并且共同分享同一个进程
进程间调用涉及的开销比线程间通信多
线程间的切换成本比进程间切换成本低
基于多线程编程的缺点
设计更复杂、上下文切换的开销、增加资源消耗
什么是线程
线程是比进程更小的执行单位
- 线程——轻量级的进程LWP,系统负担小,主要是CPU的分配
线程不能独立存在,必须存在于进程中
每个线程也有它自身的产生、存在和消亡的过程,也是一个动态的概念
一个线程有它自己的入口和出口,以及一个顺序执行的序列
什么是多线程
多线程是指同时存在几个执行体,按几条不同的执行线索共同工作的情况
- 多线程实现单个进程中的并发计算,JDK1.8提供了针对于并行的支持
- 各线程间共享进程空间的数据,并利用这些共享单元来实现数据交换、实时通信与必要的同步操作
多线程的程序能更好地表述和解决现实世界的具体问题,是计算机应用开发和程序设计的一个必然发展趋势
线程的用处
可以将一个进程按不同的功能划分为多个线程,每个线程有自己的栈段和程序计数器,没有独立的数据段和代码段,线程在自己的栈段内保存一些局部变量,对于一些全局变量,所有的线程都可以共享访问整个进程的数据段
- 多线程好处: 解决了多部分同时运行的问题
- 多线程弊端: 线程太多会有效率降低的问题,一般计算密集型应用不适合多线程
Java与多线程
Java语言的一个重要功能特点就是内置对多线程的支持,它使得编程人员可以很方便地开发出具有多线程功能,能同时处理多个任务的功能强大的应用程序
Java的所有类都是在多线程的思想下定义的,Java利用线程使整个系统成为异步
每个Java程序都有一个隐含的主线程
- application main 方法
- Applet小程序【application let】,主线程指挥浏览器加载并执行java小程序
为什么使用线程
-
减轻编写交互频繁、涉及面多的程序的困难
-
程序的吞吐量会得到改善
-
由多个处理器的系统,可以并发运行不同的线程。(否则,任何时刻只有一个线程在运行)
样例:左手一个动作右手一个动作
- 如果需要控制顺序可以使用信号量Semaphore
public class T2 {
public static void main(String[] args) {
Thread t1 = new LeftThread();
t1.start();// 启动线程必须使用start方法,而不是run
Thread t2 = new Thread(new RightThread());
t2.start();
}
}
class LeftThread extends Thread {
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("左手一个慢动作" + i);
try {
this.sleep(300);// 使当前线程对象停止300毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class RightThread implements Runnable {
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("右手慢动作重播" + i);
try {
Thread.currentThread().sleep(300);// 使当前线程对象停止300毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
多次运行会发现执行顺序不一定相同,这就是多线程运行过程的不可重现性(执行的顺序不能保证),运行结果应该是一致的
笑话程序
对一个大于0的整形数组中的数据进行排序输出
注意事项
使用多线程并不能增加CPU的处理能力,也不一定会提升CPU的吞吐量
基于Internet的应用有必要使用多线程