进程与线程概念

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAeG1oLXN4aC0xMzE0,size_20,color_FFFFFF,t_70,g_se,x_16一、进程与线程

 

进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。

 

线程:是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的 资源。

 

虽然系统是把资源分给进程,但是CPU很特殊,是被分配到线程的,所以线程是CPU分配的基本单位。

 

 

 

二者关系:

 

一个进程中有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈区域。

 

 

 

 程序计数器:是一块内存区域,用来记录线程当前要执行的指令地址 。

 

栈:用于存储该线程的局部变量,这些局部变量是该线程私有的,除此之外还用来存放线程的调用栈祯。

 

堆:是一个进程中最大的一块内存,堆是被进程中的所有线程共享的。

 

方法区:则用来存放 NM 加载的类、常量及静态变量等信息,也是线程共享的 。

 

 

 

二者区别:

 

进程:有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响。

 

线程:是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉。

 

 

 

1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

 

2) 线程的划分尺度小于进程,使得多线程程序的并发性高。

 

3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

 

4) 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

 

5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别

 

 

 

回到顶部

二、并发与并行

并发:是指同一个时间段内多个任务同时都在执行,并且都没有执行结束。并发任务强调在一个时间段内同时执行,而一个时间段由多个单位时间累积而成,所以说并发的多个任务在单位时间内不一定同时在执行 。

 

并行:是说在单位时间内多个任务同时在执行 。

 

在多线程编程实践中,线程的个数往往多于CPU的个数,所以一般都称多线程并发编程而不是多线程并行编程。

 

 

 

并发过程中常见的问题:

 

1、线程安全问题:

 

 

 

多个线程同时操作共享变量1时,会出现线程1更新共享变量1的值,但是其他线程获取到的是共享变量没有被更新之前的值。就会导致数据不准确问题。

 

 

 

2、共享内存不可见性问题

 

 

 

Java内存模型(处理共享变量)

 

Java 内存模型规定,将所有的变量都存放在主内存中,当线程使用变量时,会把主内存里面的变量复制到自己的工作空间或者叫作工作内存,线程读写变量时操作的是自己工作内存中的变量 。(如上图所示)

 

 

 

(实际工作的java内存模型)

 

上图中所示是一个双核 CPU 系统架构,每个核有自己的控制器和运算器,其中控制器包含一组寄存器和操作控制器,运算器执行算术逻辅运算。CPU的每个核都有自己的一级缓存,在有些架构里面还有一个所有CPU都共享的二级缓存。 那么Java内存模型里面的工作内存,就对应这里的 Ll或者 L2 缓存或者 CPU 的寄存器

 

1、线程A首先获取共享变量X的值,由于两级Cache都没有命中,所以加载主内存中X的值,假如为0。然后把X=0的值缓存到两级缓存,线程A修改X的值为1,然后将其写入两级Cache,并且刷新到主内存。线程A操作完毕后,线程A所在的CPU的两级Cache内和主内存里面的X的值都是l。

 

2、线程B获取X的值,首先一级缓存没有命中,然后看二级缓存,二级缓存命中了,所以返回X=1;到这里一切都是正常的,因为这时候主内存中也是X=l。然后线程B修改X的值为2,并将其存放到线程2所在的一级Cache和共享二级Cache中,最后更新主内存中X的值为2,到这里一切都是好的。

 

3、线程A这次又需要修改X的值,获取时一级缓存命中,并且X=l这里问题就出现了,明明线程B已经把X的值修改为2,为何线程A获取的还是l呢?这就是共享变量的内存不可见问题,也就是线程B写入的值对线程A不可见。

 

 

 

synchronized 的内存语义:

 

这个内存语义就可以解决共享变量内存可见性问题。进入synchronized块的内存语义是把在synchronized块内使用到的变量从线程的工作内存中清除,这样在synchronized块内使用到该变量时就不会从线程的工作内存中获取,而是直接从主内存中获取。退出synchronized块的内存语义是把在synchronized块内对共享变量的修改刷新到主内存。会造成上下文切换的开销,独占锁,降低并发性

 

 

 

Volatile的理解:

 

该关键字可以确保对一个变量的更新对其他线程马上可见。当一个变量被声明为volatile时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。当其他线程读取该共享变量时-,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。volatile的内存语义和synchronized有相似之处,具体来说就是,当线程写入了volatile变量值时就等价于线程退出synchronized同步块(把写入工作内存的变量值同步到主内存),读取volatile变量值时就相当于进入同步块(先清空本地内存变量值,再从主内存获取最新值)。不能保证原子性

 

 

 

回到顶部

三、创建线程

1、继承Thread类

 

重写run方法:使用继承方式的好处是,在run()方法内获取当前线程直接使用this就可以了,无须使用Thread.currentThread()方法;不好的地方是Java不支持多继承,如果继承了Thread类,那么就不能再继承其他类。另外任务与代码没有分离,当多个线程执行一样的任务时需要多份任务代码。

 

复制代码

public class ThreadRuning extends Thread{

 

    public ThreadRuning(String name){  

//重写构造,可以对线程添加名字

        super(name);

    }

    @Override

    public void run() {

        while(true){

            System.out.println("good time");

//在run方法里,this代表当前线程

            System.out.println(this);

        }

    }

    public static void main(String[] args){

        ThreadRuning threadRuning = new ThreadRuning("1111");

        threadRuning.start();

    }

}

复制代码

2、实现Runable接口

 

实现run方法:解决继承Thread的缺点,没有返回值

 

public class RunableTest implements Runnable {

    @Override

    public void run() {

        while (true) {

            System.out.println("good time");

        }

    }

    public static void main(String[] args) {

        RunableTest runableTest1 = new RunableTest();

        RunableTest runableTest2 = new RunableTest();

        new Thread(runableTest1).start();

        new Thread(runableTest1).start();

        new Thread(runableTest2).start();

    }

}

3、实现Callable接口

 

实现call方法:

 

 

public class CallTest implements Callable {

    @Override

    public Object call() throws Exception {

        return "hello world";

    }

 

    public static void main(String[] args){

        FutureTask<String> futureTask = new FutureTask<String>(new CallTest());

        new Thread(futureTask).start();

        try {

            String result = futureTask.get();

            System.out.println(result);

        } catch (InterruptedException e) {

            e.printStackTrace();

        } catch (ExecutionException e) {

            e.printStackTrace();

        }

    }

}

 使用继承方式的好处是方便传参,你可以在子类里面添加成员变量,通过set方法设置参数或者通过构造函数进行传递,而如果使用Runnable方式,则只能使用主线程里面被声明为final的变量。不好的地方是Java不支持多继承,如果继承了Thread类,那么子类不能再继承其他类,而Runable则没有这个限制。前两种方式都没办法拿到任务的返回结果,但是Callable方式可以

 

 

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
进程线程是操作系统中的两个基本概念,它们都是用来执行程序的执行单元,但在一些方面有着明显的区别。 1. 进程(Process): - 进程是程序在执行过程中的一个实例。 - 每个进程都有自己的独立内存空间,包括代码段、数据段和堆栈段。 - 进程之间相互独立,拥有各自的资源,通信需要通过进程间通信(IPC)机制。 - 进程拥有自己的进程控制块(PCB),用于描述进程的状态、资源和调度信息。 2. 线程(Thread): - 线程进程中的一个执行单元。 - 多个线程可以共享同一个进程的内存空间,包括代码段、数据段和堆栈段。 - 线程之间共享进程的资源,如打开的文件、信号处理等。 - 线程线程控制块(TCB)来描述,每个线程有自己的栈和寄存器上下文。 区别: 1. 资源占用:每个进程都有独立的内存空间和系统资源,而线程共享进程的资源。 2. 创建销毁开销:创建或销毁进程线程开销大,因为进程需要分配独立的内存空间和系统资源,而线程只需要创建线程控制块。 3. 切换开销:进程切换的开销较大,需要保存和恢复整个进程的上下文,而线程切换只需要保存和恢复线程的上下文。 4. 通信和同步:进程间通信需要使用进程间通信机制,如管道、消息队列等。线程间通信和同步相对容易,可以使用共享内存、信号量、互斥量等机制。 总结: 进程线程都是用于执行程序的执行单元,但进程是资源分配的基本单位,线程是CPU调度的基本单位。多线程比多进程更轻量级,线程之间的切换开销更小,但进程之间相互独立,安全性更高。在实际应用中,需要根据具体需求选择使用进程还是线程

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值