文章目录
1、进程和线程的认识
进程:
- 进程就是计算机正在进行的一个独立的应用程序,进程是一个动态的概念,必须是进行状态,如果一个应用程序没有启动,那就不是一个进程。
- 进程是程序执行过程中资源分配和管理的基本单位。
- 进程拥有自己的独立的地址空间,每启动一个进程,系统就会分配地址空间。
线程:
- 一个程序运行中可以执行多个任务,任务称之为线程。
- 线程是cpu执行的最小单位。
- 进程可以拥有多个线程,各个线程之间共享程序的内存空间
为什么出现线程?
每个进程有自己独立的地址空间,多并发请求,为每一个请求创建一个进程导致系统开销、用户请求效率低。
2、多线程和多进程的区别和联系
区别:
- 进程是资源分配的最小单位,线程是cpu调度的最小单位
- 每个进程拥有自己独有的数据,线程共享数据
- 线程之间的通信相比于进程之间的通信更有效,更容易
- 线程相比于进程 创建/销毁 开销更小
- 多进程程序更加健壮,多线程程序只要有一个线程挂掉,对其共享资源的其他线程也会产生影响
联系:
6. 进程是相互独立,一个进程下可以有一个或者多个线程
进程和线程的使用场景
- 在程序中,如果需要频繁创建和销毁的使用线程。因为进程创建和销毁开销很大(需要不停的分配资源),但是线程频繁的调用只是改变CPU的执行,开销小。
- 如果需要程序更加的稳定安全时,可以选择进程。如果追求速度,就选择线程。
3、并发和并行的区别
- 并发:指多个线程操作同一个资源,不是同时执行,需要交替执行,单核CPU,因为CPU执行每一个时间片很短,速度太快,看起来是同时执行(张三、李四厨师,使用同一口锅炒菜,交替执行)
- 并行:多核CPU,每个线程来使用一个单独的CPU的资源来运行(张三、李四厨师,一人一口锅,一起炒菜)
并发编程:指允许多个任务在一个时间段内重复执行
的设计结构
相关概念:
- 高并发:设计的程序,能够执行海量的任务同时执行。
- QPS:每秒能够响应的请求数。
- 平均响应时间:并发数 / 平均响应时间 = QPS。
- 并发用户数:系统可以承载的最大用户量。
- 吞吐量:单位时间内能够处理的请求数。
互联网系统架构中,如何提高系统的并发能力?
垂直扩展:
提升单机的处理能力。
- 增强单机的硬件性能:增加CPU的核数、内存升级、磁盘扩容。
- 提升系统的架构能力:使用Cache来提高效率。
水平扩展:
集群、分布式都是水平的扩展方案。
- 集群:多个人做同一事(例如一个餐厅中同时多顾几个厨师同时炒菜)。
- 分布式:一个复杂的事情,拆分成几个简单的步骤,分别找不同的人去完成(1.洗菜 2.切菜 3.炒菜)。
具体操作为:
- 站点层扩容:通过Nginx反向代理,实现高并发的系统,将服务部署在多个服务器上。
- 服务层扩容:通过RPC框架实现远程调用:Dubbo,Spring Clodud,将业务逻辑分拆成不同的RPC Client,每个Clident完成各自的不同的业务,如果并发量比较大,则可以新增加RPC Client。
- 数据层扩容:一台数据库拆分成多态,分库分表,主从复制,读写分离。
4、线程的创建
(1)继承Thread类
class WatchTV extends Thread {
@Override
public void run() {
System.out.println("Watch TV");
}
}
class Eat extends Thread {
@Override
public void run() {
System.out.println("eating");
}
}
public class TestDemo {
public static void main(String[] args) {
Thread watchTV = new WatchTV();
Thread eat = new Eat();
watchTV.start(); //watchTV();
eat.start(); //eat();
}
}
使用继承Thread类创建步骤:
1、自定义类继承Thread(extends Thread),重写run方法
2、实例化自定义的类
3、启动子线程,调用start方法
(2)实现Runnable接口
class WatchTV implements Runnable {
@Override
public void run() {
System.out.println("Watch TV");
}
}
class Eat extends implements Runnable {
@Override
public void run() {
System.out.println("eating");
}
}
public class TestDemo {
public static void main(String[] args) {
Thread watchTV = new Thread(new WatchTV());
Thread eat = new Thread(new Eat());
watchTV.start(); //watchTV();
eat.start(); //eat();
}
}
实现Runable的创建线程步骤
1、自定义实现一个Runable接口的实现类,并实现run方法
2、实例化自定义的Runable实现类
3、创建Thread类实例,将实例化的Runable实例作为参数传递
4、启动子线程,调用Thread实例的start方法
(3)实现Callable接口
class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=0; i<10000; i++){
sum += i;
}
return sum;
}
}
public class TestDemo {
public static void main(String[] args) {
Callable<Integer> callableTask = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(callableTask);
Thread thread = new Thread(task);
thread.start();
//接受线程执行之后的结果
try {
Integer integer = task.get();
System.out.println("result: "+integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
实现Callable接口的创建多线程的步骤:
1、自定义实现Callable接口的实现类,并实现call方法
2、创建自定义类的实例
3、创建一个FutureTask实例,将自定义实例作为参数传入
4、创建Thread类的实例,将FutureTask的实例作为参数传入
5、启动子线程,代用Thead实例的start方法
实现Runable接口和继承Thread类的区别
1、使用继承Thread类(单继承)是不能继承其他类,而Runable方式可以
2、继承Thread类是无法实现资源共享,而实现Runable接口可以实现资源共享
3、实现Runable接口代码更健壮,任务和线程是解耦合的,代码更加清晰,代码和数据是相互独立的
Callable和Runable接口区别
1、Callable类型的任务有返回值,而Runable类型的任务不能有返回值
2、Callable规定的方法是call(),而Runable规定的方法是run()方法
3、call()可以抛出异常,run()方法是不能排除异常的
4、Callable任务可以拿到一个Future对象,Future表示异步计算的结果,他提供了可以检查子线程是否执行完成的方法,可以获取到子线程执行的结果。通过Future对象可以了解和控制子线程的执行。
5、start()方法和run()方法的区别
//start方法剖析
public synchronized void start() {
if(threadStatus!=0)//判断当前线程状态是否为0
throw new IllegalThreadStateException();
group.add(this); //当前线程加入一个线程
boolean started=false;
try{
start0(); //start0是一个native本地方法 调用run执行该线程
started=true;
}finally{
try{
if(!started){
group.threadStartFailed(this);
}
}catch(Throwable ignore){
}
}
- start()方法本身是用来启动一个线程,并将其添加到一个线程组里面,这时线程获取CPU资源之后就会执行所定义的run方法的逻辑。
- 而run()方法本身只是一个普通的方法,并不会启动新的线程。
6、守护线程
API描述为:The java virtual machine exits when the only threads running are all daemon threads。 即:当JVM总没有一个非守护线程时,JVM进程会退出。
守护线程是一种特殊的线程,就和它的名字一样,它是系统的守护者,在后台默默完成一些系统性的服务,比如垃圾回收线程,JIT线程就可以理解为守护线程。
与守护线程相对的是用户线程(非守护线程),用户线程可以认为是系统的工作线程,它会完成这个程序要完成的业务员操作。
如果用户线程全部结束,则意味着这个程序无事可做。守护线程要守护的对象已经不存在了,那么整个应用程序就应该结束。因此,当一个Java应用内只有守护线程时,Java虚拟机自然退出。即:守护线程能够自动结束生命周期,而非守护则不具备这一特点。
例如:
我们可以创建一个子线程,并让他持续进行睡眠,如下:
import java.util.concurrent.TimeUnit;
public class TestDemo {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
try {
while (true) {
TimeUnit.MILLISECONDS.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.start();
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main Thread Finished");
}
}
此时该线程为用户线程,即非守护线程,那么当主线程结束时,该程序并不会自动结束。
此时,如果想当主线程结束时,子线程自动结束,那么就可以将子线程设为守护线程,完整代码如下:
我们可以通过Thread.setDaemon
设置守护线程。不过需要注意的是守护线程必须在start之前设置,否则会报错。
import java.util.concurrent.TimeUnit;
public class TestDemo {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
try {
while (true) {
TimeUnit.MILLISECONDS.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//将子线程变为守护线程
thread.setDaemon(true);//在线程启动之前去调用
thread.start();
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main Thread Finished");
}
}