程序
-
某种语言进行编写的一组指令的有序集合,用于告诉计算机在执行特定任务时应该执行哪些操作
-
程序中有多个进程
-
一个典型的程序包含以下几个要素:
-
输入:程序可能需要接收外部输入,如用户的操作、文件或网络数据等。
-
处理:程序会对输入进行处理和计算。这包括使用算法、方法和数据结构来执行特定的操作。
-
输出:程序可以产生输出结果,如显示信息、保存文件、发送网络请求等。
-
流程控制:程序可以根据条件执行不同的操作,使用循环、条件语句和函数调用等控制结构来控制程序的流程。
-
错误处理:程序可以处理错误和异常情况,以保证程序的正确性和稳定性
-
-
编写一个程序通常需要考虑以下几个步骤:
-
确定需求:了解程序应该完成的任务和功能,明确目标。
-
设计:设计程序的结构、算法和数据结构。这包括确定模块、类、函数等的组织方式,并考虑如何处理输入、执行计算和产生输出。
-
编码:使用选定的编程语言将设计转化为实际的代码。根据需求和设计,编写相应的程序逻辑和语句。
-
调试和测试:执行程序,并检查其行为是否符合预期。通过调试和测试修复错误和问题。
-
部署和维护:将程序部署到目标环境中运行,并随着需求的变化对程序进行维护和更新
-
线程(Thread)
-
是进程中的一个执行单元,它是进程内的一个轻量级控制流。一个进程可以包含多个线程,它们共享进程的资源和上下文
-
线程与进程共享相同的内存空间,因此线程之间可以直接读写相同的数据,共享资源更加方便和高效
-
由于线程较进程更轻量级,线程的切换开销较小,使得并发执行和任务分配更加高效
-
如main函数(就是一个隐藏的线程)
-
如出现多条线程的情况就是多线程
进程(Process)
-
是计算机中正在运行的程序的实例。它包括了程序代码、数据集合和执行环境等。每个进程都有自己的地址空间、资源和状态
-
操作系统通过分配系统资源(如内存、CPU时间等)给不同的进程来实现多任务处理
-
一个进程中有多个线程
-
动态的过程:它本身就有线程存在、消亡的过程
-
进程之间是相互独立的,彼此之间不共享内存空间,通信需要借助特定的机制(如管道、消息队列、共享内存等)进行
进程和线程的区别
-
资源占用:进程是操作系统分配资源的基本单位,每个进程都有自己的地址空间和资源。而线程是进程内的执行单元,它们共享进程的资源。
-
独立性:进程之间是相互独立的,彼此之间不共享内存空间,通信需要借助特定的机制进行。而线程共享进程的内存空间,可以直接读写相同的数据,实现数据共享。
-
切换开销:由于进程之间相互独立,进程切换的开销较大,包括保存和恢复上下文等操作。而线程切换的开销较小,因为线程共享进程的资源和上下文,只需保存和恢复部分线程相关的上下文。
-
并发性:由于线程具有较小的切换开销,因此线程的并发执行更高效。多个线程可以在同一时间内执行不同的任务,提高系统的响应速度和资源利用率。
使用多线程的主要优点如下
-
提高程序的响应性:通过将任务拆分成多个线程,可以并发执行,减少了单线程执行时的等待时间,提高了程序的响应速度。
-
提高资源利用率:多线程允许在同一进程中共享资源,避免了多个进程之间复制资源和通信的开销。
-
简化程序设计:通过多线程,可以将程序拆分成多个独立的执行单元,简化了程序的设计和调试过程。
并发(Concurrency)
-
并发就是两个以上的任务在同一时间触发,但是只能处理其中的一条任务,另外一条任务只能等待,上一条处理完毕之后再进行执行
-
在单处理器系统中,多个任务通过任务切换的方式共享CPU时间片,看起来是同时执行的
-
主要关注的是任务的管理和调度,通过合理分配和切换资源的使用,提高系统的响应性和资源利用率
并行(Parallelism)
-
并行就是两个以上的任务进行同时运行,当其中的 某一条任务进行的同时,另外一条任务也在进行
-
在多处理器或多核处理器系统中,不同的任务可以被同时分配到不同的处理器核心上进行并行执行
-
目标是通过同时执行多个任务,加快程序的运行速度,提高计算能力和吞吐量
并发和并行的区别
-
执行方式:并发强调任务之间的交替执行,多个任务共享单个处理器的时间片。并行则指多个任务同时执行,每个任务使用独立的处理器核心或计算资源。
-
资源需求:并发执行可以通过任务切换的方式,在单个处理器上共享资源,因此资源需求较低。而并行执行需要独立的处理器核心或计算资源,资源需求较高。
-
目标与效果:并发的目标是提高系统的响应性和资源利用率,通过任务切换使得多个任务共享CPU时间片。并行的目标是加快程序的运行速度,提高计算能力和吞吐量。
-
应用场景:并发适用于多任务处理、资源共享、事件驱动等场景,如操作系统中的进程和线程管理。并行适用于需要高性能计算、大规模数据处理、复杂模拟等场景,如科学计算、图形渲染等
扩充
-
线程调度
-
比如:单核CPU 不能同时进行并行处理多个任务,只能在单核CPU上进行并发的执行
-
当程序执行的时候,是一个线程一个线程的去执行,线程会以某种顺序进行执行多个线程,这种情况就叫做线程的调度
-
-
jvm
-
至少启动两条线程(主线程,垃圾回收机制的线程),所以是多线程
创建线程的方式
-
类 Thread
public class Student extends Thread{ @Override public void run(){ System.out.println("就是线程需要进行执行的方法逻辑"); } }
-
接口 Runnable
public class Student implements Runnable{ @Override public void run(){ System.out.println("就是线程需要进行执行的方法逻辑"); } }
-
模拟JVM的多线程
public static void main(String[] args){ HelloWorld01 hello = new HelloWorld01(); hello.getVoid01(); hello.getVoid02(); }
-
实现Runnable 的调用run方法
public void getVoid01(){ Student stu = new Student(); Thread t = new Thread(stu); t.start();//进行启动对于的run方法 }
-
继承Thread 的调用run方法
public void getVoid02(){ Student stu = new Student(); stu.start();//进行启动对于的run方法 }
-
建议尽量使用实现Runnable的方式
Thread和Runnable之间的区别
-
Thread
-
是Java提供的一个具体类,它继承自java.lang.Thread类
-
通过继承Thread类,可以创建一个新的线程,并重写run()方法来定义线程的执行逻辑
-
调用start()方法的时候,会去直接进行查找子类中的run()方法(jvm进行处理)
-
优点:
-
直接进行使用run()方法
-
-
缺点:
-
线程会占用较多的系统资源,包括内存和处理器时间
-
java中只能单继承,如一个类有继承则无法使用
-
-
-
Runnable
-
是一个函数式接口,它定义了一个抽象的run()方法
-
通过实现Runnable接口,可以将一个类声明为可运行的,然后将其实例化并传递给Thread类的构造方法来创建一个线程
-
构造函数中进行存入Runnable的引用的,让成员变量进行赋值
-
start()调用run()方法的时候,内部会进行判断成员变量是否赋值了Runnable的引用,并且不为空
-
当不为空的时候,进行编译调用Runnable中的run()方法
-
优点:
-
接口多个实现
-
更加适合资源共享
-
增加了程序的健壮性
-
在线程池中,能放入Runnable和Callable的类线程,继承的Thread类是不能放入的
-
-
缺点:
-
不能直接使用run()方法,需要进行传入对象,才可以使用
-
-
匿名线程
-
是指在创建线程时不需要为线程命名的一种方式。通常,在创建线程时需要先定义一个Thread对象,并给其指定一个名称。
-
类的匿名方式的创建
public void getVoid01(){ new Thread(){ public void run(){ System.out.println("执行了new Thread()"); } }.start(); }
-
接口的匿名方式的创建
public void getVoid02(){ new Runnable(){ @Override public void run(){ System.out.println("执行了new Runnable()"); } }.start(); }
public void getVoid03(){ for(int i = 0; i < 10; i++){ new Thread(){ public void run(){ System.out.println(this.getName() + ":" + this.getId()); } }.start(); } Thread.currentThread().setName("主线程"); System.out.println(Thread.currentThread().getName()); }
休眠
-
是指让当前执行的线程暂时停止执行一段时间,让其他线程有机会执行
-
可以使用Thread类的sleep()方法来实现线程的休眠
public void getVoid04(){ new Thread(){ public void run(){ for(int i = 0; i < 10; i++){ try{ System.out.println("-----开始休眠-----"); Thread.sleep(3000);//毫秒 System.out.println("-----休眠结束-----"); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(this.getName() + "=" +this.getId()); } } }.start(); }
守护线程(Daemon Thread)
-
与用户线程(User Thread)相对,守护线程并不会阻止程序的终止,而是在所有用户线程结束时自动被终止
-
特点:
-
不阻止程序的终止:当所有的用户线程都结束时,无论守护线程是否执行完毕,JVM都会自动停止程序的运行,并终止守护线程。
-
随父线程终止而终止:当最后一个非守护线程结束时,守护线程会随着父线程的终止而终止
-
-
该不会单独的执行
-
当其他非守护线程结束后,当前守护线程会自动的退出结束
public void getVoid05(){ Thread t1 = new Thread(){ public void run(){ for(int i = 0; i < 10; i++){ this.setName("线程1号"); System.out.println(this.getName() + "=" +this.getId()); } } }; t1.start(); Thread t2 = new Thread(){ public void run(){ for(int i = 0; i < 100; i++){ this.setName("线程2号"); System.out.println(this.getName() + "=" +this.getId()); } } }; t2.setDaemon(true);//设置为守护线程 t2.start(); }
礼让线程(Thread.yield())
-
让出CPU 让其他线程来执行
-
通过调用yield()方法,当前线程暂停执行,并重新进入可运行状态,让其他线程有机会获得执行的机会
-
线程可以设置优先级别
-
t.setPriority();
-
public void getVoid06(){ new Thread(){ public void run(){ for(int i = 0; i < 10; i++){ if(i%5 == 0){ //礼让线程 Thread.yield(); System.out.println("当前进行礼让了" + getName() + ":" + i); } } System.out.println(getName() + ":" + i); } }.start(); }
线程的同步
-
可以在方法上加上synchronized(重量级)
-
也可以在方法里面加上synchronized(轻量级) 推荐使用
public void getVoid08(){ Thread t1 = new Thread(){ public void run(){ while(true){ getVoid08(); } } }; t1.start(); }
//public synchronized void getVoid08() public void getVoid08(){ synchronized(this){ System.out.print("线程1号"); System.out.print("学习"); System.out.print("Java"); System.out,println("\r\n"); } }
死锁
-
两个或多个线程在互相等待对方所持有的资源,导致所有线程无法继续执行,陷入无限等待的状态
-
线程死锁发生的条件包括以下四个必要条件:
-
互斥条件(Mutual Exclusion):至少有一个资源同时只能被一个线程持有,即该资源一次只能被一个线程使用。
-
请求与保持条件(Hold and Wait):线程已经持有了至少一个资源,在请求新的资源时继续保持已有的资源。
-
不可剥夺条件(No Preemption):线程已获得的资源,在未完成使用之前不可被其他线程抢占,只能由线程自己释放。
-
循环等待条件(Circular Wait):存在一个等待循环,每个线程都在等待下一个线程所持有的资源
-
-
死锁是开发中禁止出现的
-
死锁例子:
private static String s1 = "右筷子"; private static String s2 = "左筷子"; public void getVoid09(){ new Thread(){ public void run(){ while(true){ synchronized(s1){ System.out.println(getName() + "获取到" + s1 + ",等待" + s2); synchronized(s2){ System.out.println(getName() + "获取到" + s2 + "开始吃"); } } } } }.start(); }
-
避免线程死锁的方法:
-
避免循环等待:按照确定的顺序获取资源,避免多个线程循环等待对方所持有的资源。
-
使用资源分级:为不同的资源定义优先级,按照一定的顺序获取资源,从而避免循环等待。
-
避免持有不必要的资源:尽量减少资源的占用,避免不必要的阻塞。
-
及时释放资源:使用完资源后,及时释放,使得其他线程可以获取到资源
-
互斥锁(Mutex Lock)
-
是一种同步机制,用于保护共享资源在多线程环境下的互斥访问。
-
它提供了一种简单有效的方式来确保同一时间只有一个线程可以访问被保护的共享资源
-
互斥锁的概念如下:
-
保护共享资源:当多个线程需要访问共享资源时,通过使用互斥锁,能够确保同一时间只有一个线程可以访问该资源,避免出现数据竞争和不确定性的问题。
-
互斥性:互斥锁是一种二进制信号量,默认处于未锁定状态(可获取)。当某个线程获得了互斥锁后,其他线程需要等待,直到该线程释放锁才能继续争夺锁。
-
加锁和释放锁:线程在访问共享资源之前需要先获得互斥锁的锁定(加锁);当线程完成对共享资源的访问后,需要释放互斥锁(解锁),以便其他线程可以获取锁并访问资源。
-
阻塞和非阻塞:当一个线程尝试获取已经被其他线程锁定的互斥锁时,如果支持阻塞模式,那么该线程将被阻塞,直到锁被释放;如果支持非阻塞模式,那么该线程将会立即返回获取锁的结果,以便继续执行其他操作
-
public class HelloWorld{ public static void main(String[] args){ Dog d = new Dog(); new Thread(){ public void run(){ while(true){ try{ d.getVoid01(); }catch(InterruptedException e){ e.printStackTrace(); } } } }.start(); new Thread(){ public void run(){ while(true){ try{ d.getVoid02(); }catch(InterruptedException e){ e.printStackTrace(); } } } }.start(); new Thread(){ public void run(){ while(true){ try{ d.getVoid03(); }catch(InterruptedException e){ e.printStackTrace(); } } } }.start(); } } class Dog{ private ReentrantLock r = new ReentrantLock(); private Condition c1 = r.newCondition(); private Condition c2 = r.newCondition(); private Condition c3 = r.newCondition(); private int number = 1; public void getVoid01() throws Exception{ //获取到锁 r.lock(); if(number!=1){ c1.await(); } System.out.print("我是"); number = 2; c2.signal(); //this.notify(); //随机唤醒某个线程 //释放锁 r.unlock(); } public void getVoid02() throws Exception{ //获取到锁 r.lock(); if(number!=2){ c2.await(); } System.out.print("中国"); number = 3; c3.signal(); //this.notify(); //随机唤醒某个线程 //释放锁 r.unlock(); } public void getVoid03() throws Exception{ //获取到锁 r.lock(); if(number!=3){ c3.await(); } System.out.print("人民"); number = 1; c1.signal(); //this.notify(); //随机唤醒某个线程 //释放锁 r.unlock(); } }
线程的唤醒
-
是指将一个处于等待状态的线程转移到可运行状态,以便它能够继续执行。
-
在多线程编程中,通常会使用线程的唤醒机制来协调线程之间的同步和交互
-
wait()
-
等待方是那些在某种条件下主动放弃CPU控制权并进入等待状态的线程。
-
等待方在执行到某个特定点时通过调用某个同步对象(如锁)的等待方法(如
wait()
)将自己置于等待状态,并释放占有的同步资源
-
-
notify()
-
唤醒方是那些在某个条件满足时通知等待方继续执行的线程。
-
唤醒方在某个条件满足时通过调用某个同步对象的唤醒方法(如
notify()
或notifyAll()
)来唤醒一个或多个处于等待状态的线程
-
-
线程的唤醒过程如下:
-
等待方获得锁:在等待方调用同步对象的等待方法前,需要先获得该对象的锁。如果当前线程未获取到锁,会被阻塞直到获取到锁为止。
-
等待方进入等待状态:等待方调用同步对象的等待方法后,它将放弃CPU控制权并进入等待状态。等待方释放掉占有的同步资源,其他线程可以获得这个锁从而访问相关代码。
-
唤醒方改变条件:唤醒方在某个条件满足时,可以调用同步对象的相应唤醒方法(如
notify()
或notifyAll()
),来通知等待方继续执行。 -
唤醒方释放锁:在唤醒方调用完唤醒方法后,需要释放掉锁,以便其他线程可以获取锁并访问相关代码。
-
等待方重新竞争锁:当等待方被唤醒后,它将尝试重新竞争锁,并且一旦获得锁,将从等待方法返回,继续执行。如果是使用
notifyAll()
方法进行唤醒,则所有处于等待状态的线程都将被唤醒并竞争锁
-
public class HelleWorld02{ public static void main(String[] args) throws Exception{ Object o = new Object(); //o.wait(); //在前面 //o.notify(); //在后面 new Thread(){ public void run(){ System.out.print("发出:" + Thread.currentThread().getName()); synchronized(o){ try{ o.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } } }.start(); TimeUnit.MILLISENCONDS.sleep(2000); new Thread(){ public void run(){ System.out.print("执行:" + Thread.currentThread().getName()); synchronized(o){ try{ o.notify(); }catch(InterruptedException e){ e.printStackTrace(); } } } }.start(); } }
线程池(Thread Pool)
-
是一种管理和复用线程的机制,它可以有效地控制并发执行的线程数量,提高线程的利用率和整体性能。
-
线程池在多线程编程中被广泛应用,特别适用于需要频繁创建和销毁线程的场景
public class HelloWorld03{ public static void main(String[] args){ HelloWorld03 h = new HelloWorld03(); h.getVoid01(); } //自定义线程组 public void getVoid01(){ ThreadGroup group = new ThreadGroup("自定义的线程组"); MyRun m = new MyRun(); Thread t1 = new Thread(group,m,"1号"); Thread t2 = new Thread(group,m,"2号"); System.out.println(t1.getName()); System.out.println(t2.getName()); group.setDaemon(true); } } Class MyRun extends Thread{ @Override public void run(){ for(int i = 0; i < 10; i++){ System.out.println("线程运行"); } } }
-
线程池的概念包含以下几个关键要素:
-
线程池管理器(ThreadPool Manager):线程池管理器负责创建、初始化和管理线程池。它可以根据系统的实际情况动态调整线程池的大小,并提供提交任务、执行任务和关闭线程池等接口。
-
工作线程(Worker Thread):线程池中的工作线程是实际执行任务的线程。线程池管理器会预先创建一定数量的工作线程,并将任务分配给它们执行。当任务执行完毕后,工作线程会返回线程池,并等待新的任务分配。
-
任务队列(Task Queue):线程池通过任务队列来缓存待执行的任务。当有新的任务提交时,线程池会检查工作线程是否处于空闲状态,如果有空闲线程则将任务分配给它处理;如果所有工作线程都处于忙碌状态,则将任务放入队列等待。
-
线程池大小(Pool Size):线程池大小指的是线程池中可同时执行的工作线程数目。线程池大小的设置需要根据系统资源、任务特性和负载情况来合理调整,以达到最佳的性能
-
-
使用线程池的好处包括:
-
降低线程创建和销毁的开销:线程的创建和销毁是比较昂贵的操作,使用线程池可以避免频繁创建和销毁线程,从而减少了系统开销。
-
提高线程的利用率:线程池能够复用已经创建的线程,使得线程可以被多个任务重复利用,提高线程的利用率。
-
控制并发执行的线程数量:线程池可以根据实际情况限制并发执行的线程数量,避免系统资源被过度占用,保证系统的稳定性。
-
提供任务队列和调度机制:线程池通过任务队列和调度机制,可以按照一定的策略来执行任务,例如按先进先出顺序、优先级等方式进行任务调度。
-