java多线程

java多线程
1.Java内存模型即JMM。
Java作为一个跨平台的语言,它的实现要面对不同的底层硬件系统,设计一个中间层模型来屏蔽底层的硬件差异,给上层的开发者一个一致的使用接口。Java内存模型就是这样一个中间层的模型,它为程序员屏蔽了底层的硬件实现细节,支持大部分的主流硬件平台。
Java Memory Model并不是真实存在的,他只是物理内存模型的一个映射。
原文:https://www.cnblogs.com/yanlong300/p/9009687.html
1.1JVM对Java内存模型的实现
JVM中内存分配的两个概念:

  1. stack(栈)
    特点: 存取速度快、对象生命周期确定、数据大小确定。
    存储数据:基本类型变量、对象引用(句柄)
    位置:缓存、寄存器、写缓冲区。
  2. heap(堆)
    特点: 存取速度慢、运行时动态分配大小、对象生命抽周期不确定、垃圾回收。
    存储数据:对象
    位置:主内存、缓存
    理论上说所有的stack和heap都存储在物理主内存中,但随着CPU运算其数据的副本可能被缓存或者寄存器持有,持有的数据遵从一致性协议.

jVM中运行的每个线程都拥有自己的线程栈,线程栈包含了当前线程执行的方法调用相关信息,我们也把它称作调用栈。随着代码的不断执行,调用栈会不断变化。
线程栈还包含了当前方法的所有本地变量信息。一个线程只能读取自己的线程栈,也就是说,线程中的本地变量对其它线程是不可见的。即使两个线程执行的是同一段代码,它们也会各自在自己的线程栈中创建本地变量,因此,每个线程中的本地变量都会有自己的版本。
所有原始类型(boolean,byte,short,char,int,long,float,double)的本地变量都直接保存在线程栈当中,对于它们的值各个线程之间都是独立的。对于原始类型的本地变量,一个线程可以传递一个副本给另一个线程,当它们之间是无法共享的。
堆区包含了Java应用创建的所有对象信息,不管对象是哪个线程创建的,其中的对象包括原始类型的封装类(如Byte、Integer、Long等等)。不管对象是属于一个成员变量还是方法中的本地变量,它都会被存储在堆区。
下图展示了调用栈和本地变量都存储在栈区,对象都存储在堆区:

一个本地变量如果是原始类型,那么它会被完全存储到栈区。
一个本地变量也有可能是一个对象的引用,这种情况下,这个本地引用会被存储到栈中,但是对象本身仍然存储在堆区。
对于一个对象的成员方法,这些方法中包含本地变量,仍需要存储在栈区,即使它们所属的对象在堆区。
对于一个对象的成员变量,不管它是原始类型还是包装类型,都会被存储到堆区。
Static类型的变量以及类本身相关信息都会随着类本身存储在堆区。
堆中的对象可以被多线程共享。如果一个线程获得一个对象的应用,它便可访问这个对象的成员变量。如果两个线程同时调用了同一个对象的同一个方法,那么这两个线程便可同时访问这个对象的成员变量,但是对于本地变量,每个线程都会拷贝一份到自己的线程栈中。
1.2硬件内存架构

在系统内存架构中并没有栈(stack)、堆(heap)这种概念,只有寄存器(register)、缓存(cache)、主内存(RAM、Main Memory)。理论上说所有的栈和堆都存储在主内存中,但随着CPU运算其数据的副本可能被缓存或者寄存器持有。持有的数据遵从CPU-Cache一致性协议。
1.3共享内存
JMM对共享内存的操作做出了如下两条规定:
• 线程对共享内存的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写。
• 不同线程无法直接访问其他线程工作内存中的变量,因此共享变量的值传递需要通过主内存完成。

假设线程A和线程B同事访问某个对象的成员变量x。当线程a需要操作变量a,时会将a副本复制到线程A的工作内存中。
但是线程a与线程b操作的是自己工作空间中的变量副本。 线程a中的副本和线程b中间的副本相符不可见。如果a线程率先完成了任务并写回主存。那么线程b的运算就是在使用后脏数据运算。如果b也写回主存那么线程a的任务就会丢失。

为了保证程序的准确性,我们就需要在并发时添加额外的同步操作。?
1.3java内存模型-内存间的八种同步操作
它们都是原子操作(除了对long和double类型的变量)

锁定(lock):作用于主内存中的变量,将他标记为一个线程独享变量。
通常意义上的上锁,就是一个线程正在使用时,其他线程必须等待该线程任务完成才能继续执行自己的任务。
解锁(unlock):作用于主内存中的变量,解除变量的锁定状态,被解除锁定状态的变量才能被其他线程锁定。
执行完成后解开锁。
read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
从主内存 读取到工作内存中。
load(载入):把read操作从主内存中得到的变量值放入工作内存的变量的副本中。
给工作内存中的副本赋值。
use(使用):把工作内存中的一个变量的值传给执行引擎,每当虚拟机遇到一个使用到变量的指令时都会使用该指令。
程序执行过程中读取该值时调用。
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
将运算完成后的新值赋回给工作内存中的变量,相当于修改工作内存中的变量。
store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
将该值从变量中取出,写入工作内存中。
write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。将工作内存中的值写回主内存。

1.4java工作内存与主内存的读取,写入
read write

1.JAVA与线程
1.1并发不一定要实现多线程,线程的引入,可以把一个进程的资源分配和执行调度分开,各个线程既可以共享进程资源(内存地址,文件I/O等),又可以独立调度.
java的线程是通过映射到系统的原生线程上来的
1.2java语言线程的生命周期
创建(new)、就绪(ready)、运行(running)、
阻塞(Blocked)、死亡(Dead)五种状态
线程状态:https://blog.csdn.net/LoveStudy_girl/article/details/52411473
1.3 JAVA中的多线程操作
等待、阻塞、让步, 挂起 睡眠
挂起:一般是主动的,由系统或程序发出,甚至于辅存中去。(不释放CPU,可能释放内存,放在外存)
阻塞:一般是被动的,在抢占资源中得不到资源,被动的挂起在内存,等待某种资源或信号量(即有了资源)将他唤醒。(释放CPU,不释放内存)
挂起进程在操作系统中可以定义为暂时被淘汰出内存的进程,机器的资源是有限的,在资源不足的情况下,操作系统对在内存中的程序进行合理的安排,其中有的进程被暂时调离出内存,当条件允许的时候,会被操作系统再次调回内存,重新进入等待被执行的状态即就绪态,系统在超过一定的时间没有任何动作
由于wait方法是在Object上的,而sleep方法是在Thread上,当调用Thread.sleep时,并不能改变对象的状态,因此也不会释放锁。
2.线程的实现:
2.1使用内核线程实现(一对一的线程模型)
缺点:
1.基于内核线程实现的,各种操作需要进行系统调用,代价高,需要在用户态内核态之间切换
2没个轻量级县城都需要有一个内核线程支持,要消耗一定的内核资源.(内核线程的栈空间),.因此一个系统支持轻量级线程数量有限.
2.2用户线程实现(一对多的线程模型)
没有内核线程支援
2.3用户线程+轻量级线程实现(多对多)
2.4线程的创建
一、继承Thread类创建线程类
二、通过Runnable接口创建线程类
三、通过Callable和Future创建线程
二、创建线程的三种方式的对比
采用实现Runnable、Callable接口的方式创见多线程时,优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
使用继承Thread类的方式创建多线程时优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。
3线程调度:
系统为线程分配处理器试用权的过程
3.1协同调度
线程自己控制
3.2抢占式调度
cpu控制线程
4synchronized线程安全

4.1object的wait让线程等待
4.2object的notify唤醒线程
4.juc线程安全
4.1volatile 轻量级的同步策略 可见性

可以理解为主存读写,效率会降低,但比synchronize高

4.2防止指令重排序
4.3与synchnoze相比较
1.volatile 不具备互斥性
2.不能保证变量的原子性 (CAS算法)

保证在主存中进行,但是可能在1修改的同时被线程2修改
4,4 CSA算法
cas算法是硬件对于并发操作共享数据的支持
CAS包含了三个操作: 内存值V 预估值A 更新值B
当且仅当V == A 时,才将B 赋值给V 否则不做操作

比较
替换

与synchronized 比较:
效率高,因为线程获得了内存,一旦cas执行不成功,将一直while循环等待成功
synchronized下,线程模型时1:1的内核线程实现,各种线程操作如创建.析构及同步,都需要进行系统调用.而系统调用的代价相对较高,需要在用户态与内核态间转化,例如 阻塞与唤醒,都需要操作系统帮忙,需要从用户态转换到核心态,因此耗费很多处理器时间.
4.5ConcurrentHashmap 采用”锁分段”机制

  1. HashMap在高并发的环境下,执行put操作会导致HashMap的Entry链表形成环形数据结构,从而导致Entry的next节点始终不为空,因此产生死循环获取Entry
  2. HashTable虽然是线程安全的,但是效率低下,当一个线程访问HashTable的同步方法时,其他线程如果也访问HashTable的同步方法,那么会进入阻塞或者轮训状态。
  3. 在jdk1.6中ConcurrentHashMap使用锁分段技术提高并发访问效率。首先将数据分成一段一段地存储,然后给每一段数据配一个锁,当一个线程占用锁访问其中一段数据时,其他段的数据也能被其他线程访问。然而在jdk1.8中的实现已经抛弃了Segment分段锁机制,利用CAS+Synchronized来保证并发更新的安全,底层依然采用数组+链表+红黑树的存储结构。
    jdk1.8分段锁消了,底层也成了CAS大行其道
    hashmap:多线程串行效率低

分段锁,独立的锁,当多线程并发操作访问数据可以并行,效率高
并且还有复合操作方法
4.6 CopyOnWriteArrayList 写入并复制
并发迭代多时效率高,添加操作多时效率低,每次添加会复制开销大
class HaThread implements Runnable{
private static CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();
static {
list.add(“11”);
list.add(“22”);
list.add(“33”);
}
@Override
public void run() {
Iterator it = list.iterator();
while (it.hasNext()) {
list.add(“aa”);
System.out.println(Thread.currentThread().getName()+"="+it.next());
}
}
}
4.7countDownLatch闭锁
在完成某些运算时,只有其他所有线程的运算结束时才运行
final CountDownLatch latch = new CountDownLatch(5);
CountDownRun run = new CountDownRun(latch);
latch.await(); //等待,知道latch=0 才执行下面语句
try{
} finally {
latch.countDown(); //latch 减操作
}
4.8ReentrantLock重入锁 lock的实现类
解决多线程安全方式:
1.同步代码块(隐式锁) 2.同步方法(隐式锁)
jdk1.5以后 3.同步锁(显示锁) lock() unlock()
5.生产者消费者案例(sync与lock)
生产者消费者机制有可能产生的问题: 虚假唤醒 数据错误
实战:添加创建(生产者) 销毁删除(消费者)
5.1synchronize
1.当给生产者加1秒延迟后,消费者要快于生产者,所以消费者先于生产者结束,生产者最后的一次状态为等待,此后再无消费者的notify唤醒它.

2.此时仅需将else去掉,但是在 多消费生产下 出现问题

原因: 虚假唤醒 (spurious wakeup)两消费线程同时阻塞在wait,一经打开,均执行—product故出现负值

在没有被通知、中断或超时的情况下,线程还可以唤醒一个所谓的虚假唤醒 (spurious wakeup)。虽然这种情况在实践中很少发生,但是应用程序必须通过以下方式防止其发生,即对应该导致该线程被提醒的条件进行测试,如果不满足该条件,则继续等待。换句话说,等待应总是发生在循环中,如下面的示例:
if-----while 可解决
while ()
obj.wait(timeout);
}

5.2 lock的等待 与 唤醒 condition
private Condition con = lock.newCondition();
con.await();
con.signalAll();

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值