线程,多线程,Java内存区域

共享内存及问题

共享内存

每个线程表示一条单独的执行流,有自己的程序计数器,有自己的栈,但线程之间可以共享内存,它们可以访问和操作相同的对象。
不同执行流可以访问和操作相同的变量,
不同执行流可以执行相同的程序代码。

当多条执行流执行相同的程序代码时,每条执行流都有单独的栈,方法中的参数和局部变量都有自己的一份。

当多条执行流可以操作相同的变量时,可能会出现一些意料之外的结果:

竞态条件

所谓竞态条件(race condition)是指,当多个线程访问和操作同一个对象时,最终执行结果与执行时序有关,可能正确也可能不正确。

解决方法:

使用synchronized关键字
使用显式锁同步
使用原子变量

内存可见性

多个线程可以共享访问和操作相同的变量,但一个线程对一个共享变量的修改,另一个线程不一定马上就能看到,甚至永远也看不到,这可能有悖直觉。
在计算机系统中,除了内存,数据还会被缓存在CPU的寄存器以及各级缓存中,当访问一个变量时,可能直接从寄存器或CPU缓存中获取,而不一定到内存中去取,当修改一个变量时,也可能是先写到缓存中,而稍后才会同步更新到内存中。
在单线程的程序中,这一般不是个问题,但在多线程的程序中,尤其是在有多CPU的情况下,这就是个严重的问题。一个线程对内存的修改,另一个线程看不到,一是修改没有及时同步到内存,二是另一个线程根本就没从内存读。

线程的优点及成本

优点
1.充分利用多CPU的计算能力,单线程只能利用一个CPU,使用多线程可以利用多CPU的计算能力。
2.硬件资源,CPU和硬盘、网络是可以同时工作的,一个线程在等待网络IO的同时,另一个线程完全可以利
用CPU,对于多个独立的网络请求,完全可以使用多个线程同时请求。
3.在用户界面(GUI)应用程序中,保持程序的响应性,界面和后台任务通常是不同的线程,否则,如果所有
事情都是一个线程来执行,当执行一个很慢的任务时,整个界面将停止响应,也无法取消该任务。
4.简化建模及IO处理,比如,在服务器应用程序中,对每个用户请求使用一个单独的线程进行处理,相比使
用一个线程,处理来自各种用户的各种请求,以及各种网络和文件IO事件,建模和编写程序要容易的多。
成本

1.创建线程需要消耗操作系统的资源,操作系统会为每个线程创建必要的数据结构、栈、程序计数器等,创建也需要一定的时间。

2.线程调度和切换也是有成本的,当有可运行线程的时候,操作系统会忙于调度,为一个线程分配一段时间,执行完后,再让另一个线程执行,
一个线程被切换出去后,操作系统需要保存它的当前上下文状态到内存,上下文状态包括当前CPU寄存器的值、程序计数器的值等,
而一个线程被切换回来后,操作系统需要恢复它原来的上下文状态,整个过程被称为上下文切换,这个切换不仅耗时,而且使CPU中的很多缓存失效,是有成本的。

当然,这些成本是相对而言的,如果线程中实际执行的事情比较多,这些成本是可以接受的。

另外,如果执行的任务都是CPU密集型的,即主要消耗的都是CPU,那创建超过CPU数量的线程就是没有必要的,并不会加快程序的执行。

线程池

为什么用线程池

1.单线程速度慢,影响处理效率
2.创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,资源消耗大、影响处理效率

线程池

int corePoolSize 核心线程数
int maximumPoolSize 线程总数最大值 线程总数 = 核心线程数 + 非核心线程数
BlockingQueue workQueue 该线程池中的任务队列,接收任务
execute():通过这个方法可以向线程池提交一个任务,交由线程池去执行。

原理:

线程池状态:volatile int runState表示当前线程池的状态,当创建线程池后,初始时,线程池处于RUNNING状态;
当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

当新任务在方法 execute(java.lang.Runnable) 中提交时,如果:
线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务;
线程数量达到了corePoolSize,则将任务移入队列(workQueue)等待;
队列已满,新建线程(非核心线程)执行任务;
队列已满,总线程数又达到了maximumPoolSize,就会抛出异常。

任务缓存队列,即workQueue,它用来存放等待执行的任务。
workQueue的类型为BlockingQueue,通常可以取下面三种类型:
1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

Java虚拟机,运行时数据区域RuntimeDataArea:

它主要分为五个部分

1、Heap(堆)(线程共享):一个Java虚拟实例中只存在一个堆空间,是Java垃圾收集器管理的主要区域;

2、MethodArea(方法区域)(线程共享):被加载的类信息、常量、静态变量存储在方法区中,方法区也可以被垃圾收集;
所有类可能变成没有被引用(unreferenced)的状态。当类变成这种状态时,就可能被垃圾收集掉。没有加载的类包括两种状态:1.真正的没有加载,2.“unreferenced”的状态;

3、JavaStack(java的栈)(线程私有):虚拟机只会直接对Javastack执行两种操作:以帧为单位的压栈或出栈,当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。

4、ProgramCounter(程序计数器)(线程私有):每一个线程都有它自己的PC寄存器,该线程启动时创建的。PC寄存器的内容总是指向下一条将被执行指令的地址;

5、Nativemethodstack(本地方法栈)(线程私有):保存native方法进入区域的地址。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值