Java多线程笔记[未更新完]

最近课上可摸鱼时间较多,所以并发开坑学习java

本篇学习自Java多线程编程实战指南编程

目前进展:刚开坑,处于理解概念阶段缓存


本篇学习自Java多线程编程实战指南安全

Q.进程和线程的区别服务器

进程Process是程序运行的实例,在Java的范畴中,运行一个Java程序的实质是启动一个JVM进程,也就是一个运行的Java程序就是一个Java虚拟机进程(Java Web服务器是一个进程同时运行多个Java Web应用)多线程

进程是程序向操做系统申请资源(内存空间、文件句柄等)的基本单位,而线程Thread是进程中可独立运行的最小单位,一个进程能够包含多个线程,同一个进程中的全部线程共享该进程的资源,并发

Q.extends Thread 和 Runnable的区别学习

1.面向对象来看,前者是继承实现,后者是组合实现,所以后者有更低的耦合程度优化

2.从对象共享来看,Runnable意味着多个线程可共享同一个Runnable实例this

一种具备迷惑性的例子,在例子中两种非空构造的Thread构造均可以有一致的hashCode的缘由是构造声明是Thread(Runnable),而Thread的JDK声明是public class Thread implements Runnable,后两种例子都是体现Runnable的特性而非区分二者

class XjbThreadImpl implements Runnable {
    
    public void run() {
        System.out.println("Implements: "+this.hashCode());
    }
}

class XjbThreadExt extends Thread {
    
    public void run() {
        System.out.println("Extends: "+this.hashCode());
    }
}
//
public class Main {
    
   public static void main(String[] args) {
        for(int i = 0; i < 2; i++) {
            new Thread(new XjbThreadImpl()).start();
            new Thread(new XjbThreadExt()).start();
        }
        // 使用时请分别注释上下两部分
        Runnable tmpImpl = new XjbThreadImpl();
        Thread impl0 = new Thread(tmpImpl);
        Thread impl1 = new Thread(tmpImpl);
        Thread tmpExt = new XjbThreadExt();
        Thread ext0 = new Thread(tmpExt);
        Thread ext1 = new Thread(tmpExt);
        impl0.start();
        impl1.start(); //hashCode一致
        ext0.start();
        ext1.start(); //hashCode一致
   }
}

3.从建立成原本看,建立线程实例比Runnable成本更高,JVM须要为它分配调用栈空间、内核线程等资源

Java的线程分为守护线程(Daemon)和用户线程,前者用于执行重要性不高的任务,后者反之,且只有当全部用户进程结束后JVM才会正常中止(System.exit调用除外)

Thread的方法

.join() 等待相应进程结束/ yield()当前线程主动放弃处理器的占用

Q.线程的生命周期状态

NEW 已建立而为启动的线程,线程只有一次机会处于该状态

RUNNABLE 包括两个自状态READYRUNNING,前者表示处于改状态的线程能够被Scheduler调度而处于RUNNING。后者表示正在运行(即run正在被处理器执行),可使用yield()返回到READY。(活跃子状态)

BLOCKED 线程发起阻塞式IO或申请锁(被其余线程占有)时处于该状态,该状态不占用处理器资源。当完成阻塞式IO操做或得到锁时从新回到RUNNALE

WAITING 线程执行特定方法后处于等待其余线程执行另外特定操做的状态。好比Object.wait(),Thread.join(),LockSupport.park(Object)能够得到该状态,返回RUNNABLE的方法有Object.notify()/notifyAll(),LockSupport.unpark(Object)

TIMED_WAITING有时间限制的等待状态,如Thread.sleep(long),Object.wait(long),超时自动返回RUNNABLE

TERMINATED 已经执行结束的进程处于该状态(只有一次),既Thread.run()调用结束、

Q.并发与并行的区别

并行Parallel多个线程在同一时刻处理任务

并发Concurrent单个线程交替的完成不一样任务

(书里说的仍是不太明确,下次查查别的资料)

竞态指计算的正确性依赖相对时间顺序或者线程的交错,容易产生脏读问题(读取到一个过世的数据、丢失更新)

竞态的模式:read-modify-write(好比a++)和check-then-act(好比对变量的if-else),注意这些都是对于共享变量而言的,局部变量(如形参)和synchronized不会形成竞态

PS.提一下a++的过程,1.read:将变量a的值从内存读到寄存器r中,2.将寄存器r的值+1,3.将寄存器r的内容写入变量a对应的内存空间

若是某线程在执行到第二步的时候变量a的值已经更新了,那它就是脏读(旧数据),接着到第三部覆盖到内存空间的操做就是丢失更新

解决竞态:上锁/局部变量 (Q.有没有更好的方法?

竞态不必定会形成错误结果,拿上面的模式来讲,若是是交错执行read(Thread-0)-read(Thread-1)或者check(Thread-0)-check(Thread-1),很显然结果依然正确

Synchronized使修饰的方法在任一时刻只能被一个线程执行

Q.什么是线程安全

若是一个类在单线程环境下额可以正常运行,而且在多线程环境下,使用方没必要为其作任何改变的状况下也能运行正常,那就称其线程安全

线程安全表如今原子性、可见性和有序性

原子性:说白了就是“不可分割”,细分为两层含义

1.对于涉及共享变量的操做,在操做之外的线程看来是不可分割的,那就是原子操做(尽管对于正在执行的线程来讲是分不少步骤,但在外界看来该操做要么还没有开始,要么已经结束)

2.访问同一组共享变量的非只读原子操做是不能被线程交错执行的

原子性只在多线程以及线程共享的变量如下讨论才有意义

实现原子性的两种Solutions:1.Lock软件实现锁 2.CAS硬件实现锁

Java语言规范中规定6种基础类型的变量和引用变量的写操做都是原子性的

(long/double除外,缘由是32位的JVM对64位的写操做可能会分解成低32和高32处理,仍是有冲突的可能,好消息是volatile下是确保原子性的)

PS.关于读写的原子性和volatile的关系

1.volatile只保证变量写操做的原子性,不保证两种竞态模式的原子性

2.Java针对任何变量的读操做均符合原子性

Q.你tm还记得哪8种基础类型吗

A.byte boolean short char int long float double

可见性:多线程环境下,一个线程对某个共享变量进行更新后,后续访问该变量的县城可能没法马上读取该更新的结果(可能永远读取不了),读取不了则是不可见

问题产生的可能:JIT优化对多线程形成不利(书里提了个循环不变式外提的优化,水平太菜不敢评论)、共享变量分配到寄存器(两个线程运行在不一样处理器上,一个处理器没法读取另外一个处理器的寄存器)等

保证可见性:volatile

volatile做用之一是提示JIT编译器被修饰的变量可能被多个线程共享,阻止不恰当的优化

另外一做用是读取volatile关键字修饰的变量会使相应的处理器执行刷新处理器缓存的动做(停留在写缓冲器是不可见的,要求被写入到高速缓存或主内存)

可见性只保证线程能读取到相对新值(更新后其余线程能获得更新后的值),不保证最新值(读取该变量的线程在读取使用时其它线程没法更新,该线程读取到的相对新值为最新值)

PS.单处理器也会存在可见性问题

虽然寄存器都在同一处理器中,但多线程是经过时间片 分配实现的,在上下文切换的过程当中,一个线程对寄存器变量的修改会被线程上下文保存,另外一线程没法得知该修改

有序性:(玄学

volatile的另外一做用是禁止重排序,保证有序性

PS.我用不大准确的词语概括一下

原子性:线程之间是否能任意穿插

可见性:线程执行中是否时刻得知更新

上下文切换:多个线程共享同一个处理器,不一样线程被选中开始或继续执行实现并发,切入切出过程当中须要保存和恢复相应进程的进度信息(执行到哪一条指令)称为上下文,通常包括通用寄存器和程序计数器的内容

在Java的角度来看,从RUNNABLE到非RUNNABLE状态的切换过程就是一个上下文切换(暂停or恢复)

自发性上下文切换可从线程的状态转移图(各类方法、锁、IO)得出

非自发性上下文切换由线程调度器引发,好比GC过程对JVM堆进行整理时要求中止全部应用线程

然而,上下文切换是须要开销的,所以并不是线程越多越厉害,计算效率可能更加惨淡

一些活性故障的例子:

死锁:不解释

锁死:拿到锁的线程跑路了,资源永远不释放

活锁:线程处于RUNNABLE但执行的任务没有进展(带薪摸鱼)

饥饿:线程没法得到所需资源而没法执行任务

非公平调度优势:吞吐率大、减小上下文切换次数(公平调度会带来更多的暂停和唤醒);缺点:申请资源的时间误差大、可能致使饥饿,通常首选非公平调度

锁机制的思路:将多个线程对共享数据的并发访问转换为串行访问

JVM对锁的划分有内部锁(Synchronized实现)和显式锁(实现Lock接口的ReentrantLock类实现)

锁的做用是保障线程安全,体如今上面提到的三个方面

1.经过互斥实现原子性,对外在线程来讲是不可分割的(由于没法得到锁)

2.锁的得到隐含刷新处理器缓存的动做,使得读线程在临界区前将写线程对共享变量的更新同步到该线程执行处理器的高速缓冲中(可见性+原子性保证了最新值)

3.写线程在临界区执行的操做在读线程所执行的临界区看起来是按源代码顺序执行的(原子性保证读线程没法区分具体顺序,还和可见性保证了最新值,所以感知上是按顺序执行的)

last update:19/03/26

相关文章
相关标签/搜索