1 普通线程和 守护线程的区别。java
守护线程会跟随主线程的结束而结束,普通线程不会。web
2 线程的 stop 和 interrupted 的区别。 stop 会中止线程,可是不会释放锁之类的资源? interrupt 会让线程抛出异常。数据库
测试:stop 和 interrupt 关于锁释放的问题。缓存
3 关于线程 和 Thread 的 关系。 其实 实现线程的 方法只有一种,就是 new Thread 的对象 。 Thread 里面 的run 方法 默认执行方式会去 执行 Runnable target 的 run方法。 这也就是 咱们说的 实现Runnaable 方法。当时这时候其实五门也建立了一个Thread 对象。安全
Lambda 表达式也是被翻译了一个 Runnabel 的 子类对象。服务器
通常认为的实现线程的方法多线程
1 继承Thread 类并发
2 实现Runnable 接口框架
3 匿名内部类直接new一个 Thread 或者 直接new 一个Runnable。tcp
4 使用Runnable 带返回值的封装, FutureTask 和 Callable
5使用定时 使用 Timer 和 TimerTask
6 线程池
7 lambda 表达式
@Override public void run() { if (target != null) { target.run(); } }
4 若是一个线程参数里面传入了 Runnable ,而后又重写了 run 方法。这时候生效的是重写的run方法,由于 子类已经重写了 run方法,父类的 run方法不会别执行(多态)。
5 FutureTask 封装了 Runnable 和 Future 。让线程的执行能够拿到 一个返回值,其实它是 吧 Callable 的返回值 放在 一个成员上面,而后经过get 接口去拿,而且这个get 接口会等待 run 线程结束。
run 方法 执行的 Callable 的 call 方法,而后把 call 的返回值放在 成员变量上面,而后等待 Future 的 get 方法来取 这个返回值,而且这个 get 方法会等待线程执行完毕取到返回值。
6 Timer 和 t.schedule(task, time); 能够实现延时任务。 而且它会另外启动一个线程。
7 单利的写法
饿汉式:(没有线程安全问题)
/** * 饿汉式单利写法(线程安全) * @author ZHANGYUKUN * */ public class Single { private static Single single = new Single(); private Single (){} public static Single getInstance(){ return single; } }
懒汉式:(有线程安全问题)
/** * 懒汉式单利写法(有线程安全问题) * @author ZHANGYUKUN * */ public class Single2 { private static Single2 single; private Single2 (){} public static Single2 getInstance(){ if( single == null ){ single = new Single2(); } return single; } }
懒汉式,双if 写法(基本不会出线程安全,可是会出指令重排序)
/** * 懒汉式单利双id写法(基本无线程安全问题,可是有指令重排序问题) * @author ZHANGYUKUN * */ public class Single3 { private static Single3 single; private Single3 (){} public static Single3 getInstance(){ if( single == null ){ synchronized (Single3.class) { if( single == null ){ single = new Single3();//这里的 new 和 赋值 可能被重排序 } } } return single; } }
解释:new 一个对象,而后赋值分红3 部。
1 分配内存空间
2 初始这个空间
3 吧这个对象引用指向这个空间
指令重排序若是 让3 ,2 颠倒,那么这时候 这个 空间还没初始化,可是已经不为null了。另外一个线程 获取单利对象就会 获取到这个获取到这个没有初始化的对象( 在 3 执行了,2 还没执行的 这一小段时间 会出这个问题 )。
懒汉式最终版:
/** * 懒汉式单利双id写法(无线程安全问题) * @author ZHANGYUKUN * */ public class Single4 { private static volatile Single4 single; private Single4 (){} public static Single4 getInstance(){ if( single == null ){ synchronized (Single4.class) { if( single == null ){ single = new Single4();//volatile 修饰 禁止重排序 } } } return single; } }
8 自旋 是消耗资源的 wait 是不消耗资源的。
9 重入锁 。在同一线程中,容许屡次进入这个锁,不须要再次获抢锁,而只是在计数器上+1。
synchronized 是重入锁。
synchronized 是在 对象头里面写了一个线程id。标志这个锁被这个线程拿到,若是 一个线程调用了这个线程的两个方法 a,和 b ,他们 都须要获取这个对象改的锁,而且 a 里面调用了 b,由于是重入锁 因此不会 死锁。
10 synchronized 在等待锁的过程当中会自旋。
11 Thread.activeCount() 能够看到还有几个线程活着。
12 volatile 两个做用
1 禁止指令重排序
2 多线程 可见性
多线程环境中,一个变量被修改是发生在cpu 缓存中的。通常状况下这个修改操做不必定会里面刷新到,计算机内存中( 对象的堆内存 )。
若是是valatile 修饰的变量的改动会马上 更新到内存中,而且,会是别的cpu 缓存中的 这个变量的 值失效。从而保证多线程可见性。
volatile 的缺点:
1 禁用重排序,会下降效率
2 volatitle 会使 cpu 缓存失效,下降性能。
13 synchornized 级能保证多线程可见性,有能保证 原子性,由于它是同步的。
volatile 只能保证保证 可见性,不能保证 原子性,若是是 非原子的操做并不能保证线程安全。
可是 synchornized 效率比 volatile 低。
14 java java.util.concurrent.atomic 包 下面有支持原子操做的辅助类。
15 lock 和 synchronize 的 比较
lock 须要显示的声明和释放,比较麻烦。可是 比synchronize 灵活,而且能实现公平锁性。
synchronize 使用简单。
lock 的灵活体如今
1 非阻塞的获取锁
2 能中断获取锁
3 给获取锁设定最大等待时间
16 ReentrantReadWriteLock 锁的降级, 在写锁还没释放的时候,就获取读锁,这样能够保证,写的的那份数据后读到的就是就是最新的,而且不阻塞别的读线程,会被被别人修改。
锁的升级,和 锁的降级正好相反,ReentrantReadWriteLock 不支持。它是为了保证 我读到的是最新的,而且用这个读到的数据直接作修改。不会出现这个 读锁释放,到写锁获取这个 时间差 数据被修改。
备注: 读锁 是共享的,写锁的排他的,正常来讲 ,加了 写锁,读锁就加不上,可是 锁升级和降级是对 当前线程 读写锁 重入的一种特殊处理。
17 公平锁
获取锁的过程不是经过抢锁,而是经过排队的锁叫作公平锁。
18 读锁和写锁的目的
读锁:禁止别的线程写数据,可是别的线程能够读数据。这样的目的是我 读到的数据在锁的周期内是最新的,而且不会被改变(不让写)。
写锁: 禁止一切锁, 在写锁的范围内,数据必定是最新的,不会被别人改变(不让写),而且别人不会读到,修改数据时中间状态的数据(不让读)。
19 synchronize 在1.6 之前是个重量级锁
1.6 之后变得轻量了,
20 锁的 关系
偏向锁:在执行过程当中,假如该锁没有被其余线程所获取,没有其余线程来竞争该锁,那么持有偏向锁的线程将永远不须要进行同步操做也就是说:在此线程以后的执行过程当中,
若是再次进入或者退出同一段同步块代码,并再也不须要去进行加锁或者解锁操做
重入锁:若是当前线程已经获取了这个锁,那么再次获取这个锁的时候,只是数量加+。并不会由于再去抢锁。
轻量级锁: 通常指的自旋锁,和 自适应自旋锁 ,在 偏向锁,没有偏向本身的时候,偏向锁 膨胀成为 轻量级锁。自旋锁通常须要指定自旋次数,自适应自旋锁会自动选择自旋次数,刚才回去过锁的,线程自旋可能多一些,获取锁几率 小的线程可能自旋次数少一些,自旋锁消耗CPU资源。
重量级锁:轻量级锁膨胀以后,就升级为重量级锁了。重量级锁是依赖对象内部的monitor锁来实现的,而monitor又依赖操做系统的MutexLock(互斥锁)来实现的,因此重量级锁也被成为互斥锁.重量级锁 阻塞的时候是 wait ,不消耗 cpu 资源,可是阻塞或者唤醒一个线程时,都须要操做系统来帮忙,这就须要从用户态转换到内核态,而转换状态是须要消耗不少时间的,有可能比用户执行代码的时间还要长。
公平锁和非公平锁: 获取锁的过程是按照顺序来的就是公平锁,抢 锁的就是非公平锁。
21 synchronized 是 重量级锁吗?
不是,synchronized 活根据 锁的 场景自动切换 锁的类型, 同线程铜锁方法切换偏向锁-->偏向锁被别人抢了变成自旋轻量级---> 自旋10次,自动升级成重量级锁。
22 线程安全的方法
1 synchronized
2 volatile
3 Aotmic 包下面的类
4 Lock 接口的 的实现类。
23 TimeUnit.SECONDS.sleep(1); 能够是 让当前线程睡一会 。
public void sleep(long timeout) throws InterruptedException { if (timeout > 0) { long ms = toMillis(timeout); int ns = excessNanos(timeout, ms); Thread.sleep(ms, ns); } }
24 线程等锁阻塞(block),和 等待(wait) 有什么区别?
等锁 block 是被动的等待,在么抢到锁的状况下,被动的wait,不会一直 消耗cpu 资源。而且会在 下一次锁释放的时候自动的去抢锁。
wait 是主动的 等待,必需要拿到监视器 才能 主动的 wait。wait 会主动释放 锁,wait 也是不会一直消耗cpu 资源的。 wait 的线程不会由于别的线程释放锁,而被唤醒。 须要主动被被 notify 才能 被唤醒。 wait 状态被唤醒之后,若是抢到锁进入 运行状态,若是抢不到 进入 block 状态。
25 线程状态 图
26 生产者消费者问题中 ,synchronized 的 方法让生产者消费者是用的同一把锁,也就是说生产的同时 无法消费,消费的同时无法生产? 怎么解决?
27 ReentrantLock 的 的 wait 和 notify 都是经过 Condition 来实现的, condition 至关于一个组,在这个组上await 的线程被加入这个组,下次叫醒的时候 ,也就叫醒的这个组。
ReentrantLock lock = new ReentrantLock(); lock.lock(); Condition condition = lock.newCondition(); condition.await(); condition.signal(); condition.signalAll(); lock.unlock();
28 终结 java的 容器
1 有哪些支持并发
2 有哪些 List
3 有哪些map
4 有有哪些set
5 有哪些Hash
6 有哪些tree
29 总结 树 的 类型
1 二叉树
2 红黑树
3 b-tree
4 b+tree
30 线程 join 是,让当前线程wait ,直到 join的 线程执行完成。
线程执行完成会叫醒全部监事这个线程对象的线程。
主线程 join 一个 子 线程 --> 主线程 获取了 子线程对象的监视器 -->主线线程 wait --》 子线程执行完毕--->子线程触发 叫醒全部在在这个join线程对象上 wait 的线程。
31 ThreadLocal 线程局部变量。
ThreadLocal 不真实的存储数据,在向 ThreadLocal 里面放东西的时候,它会获取 当前线程的 一个 成员 变量 map,而后 用 ThreadLocal 做为这个 map 的 key ,用你 想存到这个 ThreadLocal 的 值做为 值。 这样你 不一样的线程 范围这个 ThreadLocal 对应的数据的时候,其实读的是本身 map成员变量里面 的值。
可是 ThreadLocalMap 并非 map 的子类,可是同时 map 相似的结构。
32 线程通讯类 CountDownLatch( 数量降低锁 ) ,指定一个数量,这个数量 每次减一,当这个数量变成0 的时候,await 的线程 被唤醒。
只要当 await 的 线程阻塞,没countdown 一次, state -1 ,直到 state 为0 ,await 的线程被唤醒 。
33 线程通讯类 CyclicBarrier (循环屏障) ,全部线程走到 屏障点,都会阻塞,直到左后一个线程走到屏障点,而后所有前程一块儿呗唤醒,一块儿向前走。
34 线程通讯类 Semaphore(信号量)Semaphore 的做用在与限流 , 指定一个 容许 的令牌数,而后 代码 执行的时候 获取一个令牌才能执行,执行完了释放令牌,保证同一时间 只有 令牌数量个线程在执行。超出的线程被拒绝杂外面
35 线程通讯类 Exchanger (交换者), 两个线程执行,其中一个线程执行到指定位置 阻塞,而后等待对方也执行到 某个位置 。而后交换数据之后,继续向下执行。
36 forkjoin 框架,相似于 MapReduce ,先分开计算,而后汇众 一个结果, 目的是充分的里用 多核 cpu 资源。
37 关于支持线程安全的容器。
常见的 有Vector, Hashtable,当是 Vector ,Hashtable 处理线安全的方式是 同步方法。 效率并不高。
除了 Vector 意外 还有 Collections.sync开头的方法,能够 传入一个容器,而后 返回 一个 线程 安全的 代理类。 可是这些容器 读写 都是 同步的,读的时候不能写,写的时候不能读。在大量的读的状况下,可能效率比较低。
上面这些 线程安全的容器,都是经过 同步 来处理 线程安全的的,上面你的 Vector, Hashtable 还有 Collections.sync____ 方法的代理容器都叫作 同步容器。 同步容器都 几乎都出现 在 jdk 1.2
38 和同步容器对应 的 并发容器。 几乎都出如今 jdk 1.5
ConcurrentHashMap 1经过控制锁的粒度,之前是 锁 整个容器,如今把一个容器分红多分 ,每次只锁定一小段容器
ConcurrentLinkedQueue 使用的乐观锁,经过cas 修改数据,若是数据被人修改了就从作。没有被修改就万事大吉。 因此 去size的时候是去算的,而不是存起来的一个 数字。
CopyOnWriteArrayList ,CopyOnWriteArraySet 这类容器是在写的时候复制 一个新的副本,写完之后在吧 内部容器指向新的 副本。 由于是复制,读的那个数字 老是不加锁(相似 innodb 的非锁定读 ),因此读的效率很是高。 写的时候 效率比较低,适合 写少读多。
39 关闭 并发的时候为何 加锁问题?
1 读写并发 一个线程在写数据,加了一个,可是这时候 size 还没增长,另外一个读线程在读数据, 读到元素有 11 个 ,可是 size 是10 ,明显数据不一致了。相似数据库的脏读(读到别人没作完,或者说没提交的数据 )。 因此 读写要 互斥。
2 写写 并发。 若是没有锁 ,原来容器是 10 个,如今两个线程都在加 数据,变成12 个, 可是 他们会作的事情是吧 11 设置为 长度, 而且 他们写入的位置多是同样的 ,都是11 这个位置。这样有一个就被 冲掉了(有点像数据库的丢失更新 )。 因此写写 要互斥。
3 读读 ,都是读 ,不会出 并发问题。
40 web 服务器,是经过 tcp 协议实现的。 只是 在堆请求和响应的格式有要求。用 SocketServer 能够实现一个简单的 web 服务器。
41 jdk 8 出的 LongAdder 和DoubleAdder 的 原理,相对于 AtomicLong 来讲, 好比一个 值是10 ,之前在这个 10 上面 作并发操做,如今把10 分红几份,好比 5,3,2,0,0 多线程环境下竞争 的就不是 一把锁,而是多吧锁。这样 几个值加起来就是 最终的的值。
上面的 5 份 叫作 cell , 总值叫作base。
42 java 8 的 StampedLock ,对 ReentranReadWriterLock 的加强。
StampedLock 的 高效的 缘由, ReentranReadWriterLock 读写是互斥的,StampedLock 能够读写并行。从而提升效率,原理在读的时候,若是被别的线程修改了数据,那么在读一次,取到最新的值。
StampedLock 和之前 ReentranReadWriterLock 相似 的 悲观的锁,读写互斥,也有 乐观锁的支持,读写,并行。
43 java 伪共享
伪共享的非标准定义为:缓存系统中是以缓存行(cache line)为单位存储的,当多线程修改互相独立的变量时,若是这些变量共享同一个缓存行,就会无心中影响彼此的性能,这就是伪共享。