高频面试题
1.volatile
1.1定义:1.2 特性:
1.2.1 保证可见性:
JMM(java内存模型)代码验证: 1.2.2 不保证原子性:1.2.3 禁止指令重排: 1.3你在哪个地方见过 volatile ?(单例模式) 2.CAS你知道吗?
2.1. 什么是CAS?2.2 底层原理(自旋锁和 Unsafe类):2.3 总结:2.4 CAS 缺点2.5解决ABA问题(加版本号相当于乐观锁): 3.集合类不安全的问题
3.1ArrayList类3.2 HashSet不安全(同上边的 ArrayList ):3.3 HashMap 不安全: 4 Java里的锁!
4.1公平锁与非公平锁!4.2 可重入(递归)
4.21 synchronized4.22 ReentrantLock 4.3 自旋锁!4.4 读(共享)写(独占)锁 5.阻塞队列:
5.1 BlockingQueue概念:5.2 BlockingQueue优点:5.3BlockingQueue的实现类5.4BlockingQueue的核心方法
5.41 SychronousQueueDome实例: 6.生产者消费者 小总结:
6.1 传统模式:6.2 阻塞队列模式:6.3 synchronized 和lock的区别:6.4 Condition(可以精确唤醒) 7.创建多线程之callable接口:
7.1 其他创建方式:7.2 callable 和Runnable区别:7.3Callable的使用 8. 线程池:
8.1线程池的优点:8.2 使用:8.3 线程池的7打参数简介:8.4 自定义线程池:8.5合理配置线程数 9.死锁:
9.1什么是死锁9.2 代码:
1.volatile 1.1定义:volatile是java虚拟机提供的轻量级的同步机制
1.2 特性: 1.2.1 保证可见性: JMM(java内存模型) JMM本身是一种抽象的概念并不真实存在,它描述的是一组规则或者是规范,通过这组规范定义了程序中的各个变量的(包括实例字段、静态字段和构成数组对象的元素)访问方式。
JMM关于同步的规定:
线程解锁前,必须把共享变量的值刷新回主内存线程加锁前,必须读取主内存的最新值到自己的工作内存加锁解锁是同一把锁
三个线程同时去操作age这个属性,首先都会对age=25进行变量拷贝,在各自线程的工作内存进行操作,如果t1线程对age做了修改,而t2、t3并不知道age做了修改,为了让t2、t3与主内存的数据保持一致,这就有了volatile。
没用 volatile 之前:程序一直跑 ,没人通知 mydate 已经改变!
public class ThreadDemo001 {
public static void main(String[] args) {
MyData myData = new MyData();
//第一个线程
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"t come in");
//暂停线程3s
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3s钟以后将number改为60
myData.addTo60();
System.out.println(Thread.currentThread().getName()+"t update number value:"+myData.number);
},"AAA").start();
//第二个线程,main线程
while (myData.number==0){
}
System.out.println(Thread.currentThread().getName()+"t mission is over,main get number over: "+myData.number);
}
}
class MyData{
int number=0;
public void addTo60(){
this.number=60;
}
}
用 volatile 之后:程序自己结束!
public class ThreadDemo001 {
public static void main(String[] args) {
MyData myData = new MyData();
//第一个线程
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"t come in");
//暂停线程3s
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3s钟以后将number改为60
myData.addTo60();
System.out.println(Thread.currentThread().getName()+"t update number value:"+myData.number);
},"AAA").start();
//第二个线程,main线程
while (myData.number==0){
}
System.out.println(Thread.currentThread().getName()+"t mission is over,main get number over: "+myData.number);
}
}
class MyData{
volatile int number=0;
public void addTo60(){
this.number=60;
}
}
volatile 保证可见性!!!!
原子性:不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以加塞或者被分割,需要整体完整要么同时成功,要么同时失败。
不保证性:代码验证:
public class ThreadDemo001
{
public static void main(String[] args)
{
MyData myData = new MyData();
//新建20个线程
for (int i=1;i<=20;i++){
new Thread(()->{
//每个线程执行1000次
for (int j = 0; j < 1000; j++) {
myData.addPlusPlus();
}
},String.valueOf(i)).start();
}
//等待上面20个线程都执行完成之后,再用main线程取得最终结果 2:main+gc
while (Thread.activeCount()>2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"t final number"+myData.number);
}
}
class MyData {
volatile int number=0;
public void addTo60(){
this.number=60;
}
public void addPlusPlus(){
number++;
}
}
多次运行结果不一样 且小于 保证原子性的结果20000!
那么怎么解决不保证原子性呢?
加 synchronized 关键字!
加Atomic
看结果:
1.2.3 禁止指令重排:
计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排,一般分为以下3种:
单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致处理器在进行重排序时必须要考虑指令之间的数据依赖性多线程环境种线程交替执行,由于编译器优化重排的存在,多个线程中- 使用的变量能否保证一致性是无法确定的,结果无法预测。 1.3你在哪个地方见过 volatile ?(单例模式)
在单线程下的单例模式是没有问题的:
public class SingletonDome {
private static SingletonDome singletonDome =null;
private SingletonDome () {
System.out.println (Thread.currentThread ().getName ()+"我是构造器");
}
public static SingletonDome getInstance(){
if (singletonDome == null){
singletonDome = new SingletonDome ();
}
return singletonDome;
}
public static void main ( String[] args ) {
System.out.println (SingletonDome.getInstance ()==SingletonDome.getInstance ());
System.out.println (SingletonDome.getInstance ()==SingletonDome.getInstance ());
System.out.println (SingletonDome.getInstance ()==SingletonDome.getInstance ());
}
}
结果没有问题:只有一个对象被造出来了
但是在多线程下:这种单例模式就是不安全的!!
public static void main ( String[] args ) {
//单线程版
//
System.out.println ("***************************************************************");
//多线程版
for (int i = 0 ; i < 10 ; i++) {
new Thread (()->{
SingletonDome.getInstance ( );
},String.valueOf (i)).start ();
}
}
结果就出错了:(造出了多个对象 因为构造器被执行了多次)
当然可以加 sychronzied 关键字保证线程安全!当然也有别的方法(DCL:double check Lock 双重检索机制)!!!!
//DCL Double Check Lock 双重检锁机制
public static SingletonDome getInstance(){
if (singletonDome == null){ //第一次
synchronized (SingletonDome.class){
if (singletonDome == null){ //第二次
singletonDome = new SingletonDome ();
}
}
}
return singletonDome;
}
但是 DCL 不一定是安全的!! 会发生指令重排!加volatile后可以禁止指令重牌!
private static volatile SingletonDome singletonDome =null;2.CAS你知道吗? 2.1. 什么是CAS?
compare and swap比较并交换!
public class CASDome {
public static void main ( String[] args ) {
AtomicInteger atomicInteger = new AtomicInteger (5);
System.out.println (atomicInteger.compareAndSet (5, 2019) + "data:" + atomicInteger.get ());
System.out.println (atomicInteger.compareAndSet (5, 1024) + "data:" + atomicInteger.get ());
}
}
结果:
atomicInteger.getAndIncrement();
getAndIncrement()方法底层调的是 Unsafe类 的getAndAddInt(this, valueOffset, 1)方法
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
那么Unsafe 类又是什么?
upsafe是CAS的核心类,由于java方法无法直接访问底层系统,需要通过本地(Native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在与sum.misc包中,其内部方法操作可以像C的指针一样直接操作内存,所以说CAS依赖与Unsafe类。!注意Unsafe类中的所有方法都是native修饰的,也就是说,Unsafe类中的方法都直接调用操作系统底层资源执行相应的任务
CAS的全称为Compare-And-Swap,它是一条CPU并发原语。他的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,否则,不改,这个过程是原子的。
CAS并发原语体现在JAVA语言中就是sum.misc.Unsafe类中的各个方法。调用Unsafe类中的CAS方法,JVM会帮我们实现CAS汇编指令。这是一种完全依赖与硬件的功能,通过他实现了原子操作。再次强调,由于CAS是一种系统原语,原语是属于操作系统范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被打断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {//var1:this var2:valueOffset var4:1
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
//将var5与当前内存的值进行比较,相同更新var5+var4并且返回true,不同,继续取值并且比较,直到更新完成。
return var5;
}
2.4 CAS 缺点
循环时间长,开销很大。通过上面源码可知,getAndAddInt()方法执行时,有个do while。如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能给CPU带来很大的开销。只能保证一个共享变量的原子操作。通过源码可知,cas只对当前变量(this)起作用。
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
导致 ABA问题:(狸猫换太子)
ABA问题。即狸猫换太子,A->B->A。线程t1 、t2同时去修改某个值A,t1慢,t2快,t2将A->B->A,t1再去compareAndSwap时发现内存指V和预期值A相同,就会执行相关操作。尽管t1线程操作成功,但不代表这个过程是没有问题的。
2.5解决ABA问题(加版本号相当于乐观锁):
public class ResolveABADemo {
public static AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "t第一次版本号" + stamp);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "t第二次版本号" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "t第三次版本号" + atomicStampedReference.getStamp());
}, "t1").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "t第一次版本号" + stamp);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = atomicStampedReference.compareAndSet(100, 2021, stamp, stamp+ 1);
System.out.println(Thread.currentThread().getName() + "t修改成功否:" + b + "t第四次版本号" + atomicStampedReference.getStamp());
}, "t2").start();
}
}
结果:
3.集合类不安全的问题 3.1ArrayList类1.故障出现(并发修改异常)
java.util.ConcurrentModificationException
2.导致原因:
并发争抢导致(签到花名册)
3.如何解决:
3.1 new Vector ();3.2 Collections.synchronizedList (new ArrayList<> ());3.3 new CopyOnWriteArrayList<> () 写时复制
Listlist = new CopyOnWriteArrayList<> (); for (int i = 0 ; i < 30 ; i++) { new Thread (()->{ list.add (UUID.randomUUID ().toString ().substring (0,8)); System.out.println (Thread.currentThread ().getName ()+"t"+ list); },String.valueOf (i)).start (); }
- 原理(写时复制 ReentrantLock 和 Arrays.copyOf() ):
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
3.2 HashSet不安全(同上边的 ArrayList ):
3.3 HashMap 不安全:
与上两个不同的是 : 使用 ConcurrentHashMap()解决
4 Java里的锁! 4.1公平锁与非公平锁!公平锁:排队,先来后到。类似与排队打饭。
非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
Synchronized 和 ReentrantLock 默认是非公平的
可重入锁又名递归锁,指的是自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增 1,所以要等到锁的计数器下降为 0 时才能释放锁。
ReentrantLock/Synchronized就是典型的可重入锁。可重入锁最大的作用是避免死锁。
4.21 synchronizedclass Phone{
public synchronized void sendSms(){
System.out.println (Thread.currentThread ().getName ()+"t invoked sendSms");
sendEmile ();
}
public synchronized void sendEmile(){
System.out.println (Thread.currentThread ().getName ()+"t #####invoked sendEmile");
}
}
public class ReenterLockDome {
public static void main ( String[] args ) {
Phone phone = new Phone ();
new Thread (()->{
phone.sendSms ();
},"t1").start ();
new Thread (()->{
phone.sendSms ();
},"te2").start ();
}
}
结果:
结论:
4.22 ReentrantLockpackage com.dl.maishi.LockQ;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Phone implements Runnable{
public synchronized void sendSms(){
System.out.println (Thread.currentThread ().getName ()+"t invoked sendSms");
sendEmile ();
}
public synchronized void sendEmile(){
System.out.println (Thread.currentThread ().getName ()+"t #####invoked sendEmile");
}
@Override
public void run () {
Lock lock = new ReentrantLock ();
lock.lock ();
try{
get ();
}finally {
lock.unlock ();
}
}
private void get(){
System.out.println (Thread.currentThread ().getName ()+"t invoked get");
set ();
}
private void set(){
System.out.println (Thread.currentThread ().getName ()+"t @@@@invoked set");
}
}
public class ReenterLockDome {
public static void main ( String[] args ) {
Phone phone = new Phone ();
new Thread (()->{
phone.sendSms ();
},"t1").start ();
new Thread (()->{
phone.sendSms ();
},"te2").start ();
try {
Thread.sleep (3);
} catch (InterruptedException e) {
e.printStackTrace ();
}
System.out.println ("*************************************");
Thread t3 = new Thread (phone,"t3");
t3.start ();
Thread t4 = new Thread (phone,"t4");
t4.start ();
}
}
结果:
结论:ReenterLock也是可重入锁
4.3 自旋锁!自旋锁(spinlock)是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程的上下文切换,缺点是循环会消耗cpu。下面的源码是cas学习时的代码。
public final int getAndAddInt(Object var1, long var2, int var4) {//var1:this var2:valueOffset var4:1
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
//将var5与当前内存的值进行比较,相同更新var5+var4并且返回true,不同,继续取值并且比较,直到更新完成。
return var5;
}
代码验证:
package com.dl.maishi.LockQ;
import java.util.concurrent.atomic.AtomicReference;
public class SpinLockDemo {
//原子引用线程
AtomicReference atomicReference = new AtomicReference<> ();
public void mylock(){
Thread thread = Thread.currentThread ();
System.out.println (thread.getName ()+"t 进来了");
while (!atomicReference.compareAndSet (null,thread)){
}
}
public void myUnLock(){
Thread thread = Thread.currentThread ();
// System.out.println (thread.getName ()+"t 进来了");
atomicReference.compareAndSet (thread,null);//释放
System.out.println (thread.getName ()+"t invoked myUnLock()");
}
public static void main ( String[] args ) {
SpinLockDemo spinLockDemo = new SpinLockDemo ();
new Thread (()->{
spinLockDemo.mylock ();
//暂停一会
try {
Thread.sleep (5000);
} catch (InterruptedException e) {
e.printStackTrace ();
}
spinLockDemo.myUnLock ();
},"aa").start ();
try {
Thread.sleep (1000);
} catch (InterruptedException e) {
e.printStackTrace ();
}
new Thread (()->{
spinLockDemo.mylock ();
//暂停一会
try {
Thread.sleep (1);
} catch (InterruptedException e) {
e.printStackTrace ();
}
spinLockDemo.myUnLock ();
},"bb").start ();
}
}
结果:
独占:只能被一个线程所拥有
共享锁: 可以被多个线程所拥有
代码实例:
public class ReadAndWriteLockDome {
public static void main ( String[] args ) {
MyCache myCache = new MyCache();
for (int i=1;i<=5;i++){
int finalI = i;
new Thread(()->{
myCache.put(finalI+"", finalI+"");
},String.valueOf(i)).start();
}
for (int i=1;i<=5;i++){
int finalI = i;
new Thread(()->{
myCache.get(finalI+"");
},String.valueOf(i)).start();
}
}
}
class MyCache {
private volatile Map map=new HashMap<>();
private ReentrantReadWriteLock rwLook=new ReentrantReadWriteLock();
public void put(String key,Object value){
rwLook.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"t 正在写入"+key);
try {
TimeUnit.MICROSECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"t 写入完成");
}catch (Exception e){
e.printStackTrace();
}finally {
rwLook.writeLock().unlock();
}
}
public void get(String key){
rwLook.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"t 正在读取");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object result = map.get(key);
System.out.println(Thread.currentThread().getName()+"t 读取完成:"+result);
}catch (Exception e){
e.printStackTrace();
}finally {
rwLook.writeLock().unlock();
}
}
}
结果:
当阻塞队列是空时,从队列中获取元素的操作将会被阻塞,直到有线程往队列中添加元素当阻塞队列时满时,从队列中添加元素的操作将会被阻塞,直到有线程从队列中获取元素 5.2 BlockingQueue优点:
在多线程领域,所谓阻塞,就是在某些情况下会``挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒`。
有了BlockingQueue之后,我们就不需要关心什么时候阻塞线程(挂起),什么时候需要唤醒,因为这一切BlockingQueue都给你一手包办了。在concurrent包发布以前,在多线程的环境下,我们每个程序员都必须要自己去控制这些细节,尤其是还要兼顾效率和线程安全,而这样会给我们的程序带来不小的复杂度。
5.3BlockingQueue的实现类ArrayBlockingQueue:由数组结构组成的有界阻塞队列。linkedBlockingQueue:由链表结构组成的有界阻塞队列(但是默认值为- Integer.MAX_VALUE ,21亿)PriorityBlockingQueue:支持优先级排序的无界阻塞队列。DelayQueue:使用优先级队列实现的延迟无边界阻塞队列SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列,生产一个消费一个,你不消费我不生产linkedTransferQueue:由链表结构组成的无界阻塞队列linkedBlockingDeque:由链表结构组成的双向阻塞队列 5.4BlockingQueue的核心方法
方法类型 抛出异常 特殊值 阻塞 超时
插入 add(e) offer(e) put(e) offer(e,time,unit)
移除 remove() poll() take() poll(time,unit)
检查 element peek() 不可用 不可用
add()
java.lang.IllegalStateException: Queue full 队列满的异常
element() * 队首元素 a
remove() : java.util.NoSuchElementException
offer() 插入元素:false
peek () 队首元素:apoll 取出元素: 没有为 null
put(): 如果满 了 一直等待 直到能 插进去
take(): 如果没有 一直等待 直到有了
blockingQueue.offer (“a”, 3, TimeUnit.SECONDS) ; 等待3秒 还不行就false
blockingQueue.poll (3,TimeUnit.SECONDS) 等待3秒 拿不到就是null 5.41 SychronousQueueDome实例:
package com.dl.maishi.QueueQ;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
public class SychronousQueueDome {
public static void main ( String[] args ) {
BlockingQueue queue = new SynchronousQueue<> ();
new Thread (()->{
try {
System.out.println (Thread.currentThread ().getName ()+" put 1");
queue.put ("1");
System.out.println (Thread.currentThread ().getName ()+" put2");
queue.put ("2");
System.out.println (Thread.currentThread ().getName ()+" put3");
queue.put ("3");
} catch (InterruptedException e) {
e.printStackTrace ();
}
},"aaa").start ();
new Thread (()->{
try {
Thread.sleep (3000);
System.out.println (Thread.currentThread ().getName ()+" 取出来一个");
queue.take ();
Thread.sleep (3000);
System.out.println (Thread.currentThread ().getName ()+" 取出来一个");
queue.take ();
Thread.sleep (3000);
System.out.println (Thread.currentThread ().getName ()+" 取出来一个");
queue.take ();
} catch (InterruptedException e) {
e.printStackTrace ();
}
},"bbb").start ();
}
}
结果:
package com.dl.maishi.QueueQ;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ProdConsumer_TraditionDemo {
public static void main ( String[] args ) {
ShowData showData = new ShowData ();
new Thread(()->{
for (int i = 0; i <= 5; i++) {
showData.increase ();
}
},"AA").start();
new Thread(()->{
for (int i = 0; i <= 5; i++) {
showData.decrease ();
}
},"BB").start();
}
}
class ShowData{
private int number=0;
private Lock myLock = new ReentrantLock ();
final Condition condition = myLock.newCondition ();
public void increase(){
myLock.lock(); //枷锁
try {
//判断
while (number!=0){
condition.await();
}
//干活
number++;
System.out.println(Thread.currentThread().getName()+"t"+number);
//通知
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
myLock.unlock();
}
}
public void decrease(){
myLock.lock();
try {
while (number==0){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"t"+number);
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
myLock.unlock();
}
}
}
结果:
package com.dl.maishi.QueueQ;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class ProdConsumer_BlockingQueueDemo {
public static void main ( String[] args ) {
MySha mySha = new MySha (new ArrayBlockingQueue<> (10));
new Thread(()->{
System.out.println("生产者线程启动。。。。");
try {
mySha.product();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"prod").start();
new Thread(()->{
try {
System.out.println("消费者线程启动。。。。");
Thread.sleep (1);
mySha.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"consume").start();
//停止线程5s
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println();
System.out.println();
System.out.println();
System.out.println("5s时间到,大老板叫停,活动结束");
mySha.stop();
}
}
class MySha
{
private volatile boolean FLAG=true;
AtomicInteger atomicInteger= new AtomicInteger();
BlockingQueue blockingQueue=null;
public MySha(BlockingQueue blockingQueue) {
this.blockingQueue = blockingQueue;
}
public void product() throws InterruptedException {
String data = null ;
boolean result ;
while (FLAG){
data = atomicInteger.incrementAndGet ()+"";
result = blockingQueue.offer (data,2, TimeUnit.SECONDS);
if (result){
System.out.println (Thread.currentThread ().getName ()+"t 生产成功 +"+data);
}else {
System.out.println (Thread.currentThread ().getName ()+"t 生产失败 +"+data);
}
TimeUnit.SECONDS.sleep (1);
}
System.out.println (Thread.currentThread().getName()+"t 大老板叫停了,表示flag=false");
}
public void consume() throws InterruptedException {
String result=null;
while (FLAG){
result = blockingQueue.poll(2, TimeUnit.SECONDS);
if (result==null || result.equalsIgnoreCase("")){
FLAG=false;
System.out.println(Thread.currentThread().getName()+"t 超过2s没有取到蛋糕,消费结束");
System.out.println();
System.out.println();
return;
}
System.out.println(Thread.currentThread().getName()+"t 消费队列"+result+"成功");
}
}
public void stop(){
this.FLAG=false;
}
}
结果:
生产者线程启动。。。。 消费者线程启动。。。。 prod 生产成功 +1 consume 消费队列1成功 prod 生产成功 +2 consume 消费队列2成功 prod 生产成功 +3 consume 消费队列3成功 prod 生产成功 +4 consume 消费队列4成功 prod 生产成功 +5 consume 消费队列5成功 5s时间到,大老板叫停,活动结束 prod 大老板叫停了,表示flag=false consume 超过2s没有取到蛋糕,消费结束 Process finished with exit code 06.3 synchronized 和lock的区别:
synchronized 是jvm层面:
lock是api层面:JUC.locks.lock使用方法:
synchronized :不需要 手动释放锁,当代码执行完自动释放
lock : 需要手动加锁(lock.lock),手动释放(lock.unlock);等待是否可中断:
synchronized 不可中断,除非抛异常出去或者自己运行完出去
lock:可中断 设置超市方法:trylock(Long time,TimeUnit unit)加锁是否公平:
synchronized 非公平锁
ReentrantLock 默认非公平锁 但是可以设值 true 则为公平锁锁绑定多个条件Condition 值:
sychronized:没有;
ReentantLock :可以用条件之精确唤醒;
6.4 Condition(可以精确唤醒)
package com.dl.maishi.QueueQ;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionDome {
public static void main ( String[] args ) {
ShareData shareData = new ShareData ();
new Thread (()->{
shareData.print2 ();
},"a").start ();
new Thread (()->{
shareData.print3 ();
},"b").start ();
new Thread (()->{
shareData.print5 ();
},"c").start ();
}
}
class ShareData{
private Integer flag=1; //a:1 b:2, c:3
private Lock lock = new ReentrantLock ();
private Condition condition1 = lock.newCondition ();
private Condition condition2 = lock.newCondition ();
private Condition condition3 = lock.newCondition ();
public void print2(){
lock.lock ();
try {
//判断
while (flag!=1){
condition1.await ();
}
//干活
for (int i = 1 ; i <=2 ; i++) {
System.out.println (Thread.currentThread ().getName ()+"t "+i);
}
//通知
flag=2;
condition2.signalAll ();
}catch (InterruptedException e) {
e.printStackTrace ();
}finally {
lock.unlock ();
}
}
public void print3(){
lock.lock ();
try {
//判断
while (flag!=2){
condition2.await ();
}
//干活
for (int i = 1 ; i <=3 ; i++) {
System.out.println (Thread.currentThread ().getName ()+"t "+i);
}
//通知
flag=3;
condition3.signalAll ();
}catch (InterruptedException e) {
e.printStackTrace ();
}finally {
lock.unlock ();
}
}
public void print5(){
lock.lock ();
try {
//判断
while (flag!=3){
condition3.await ();
}
//干活
for (int i = 1 ; i <=5 ; i++) {
System.out.println (Thread.currentThread ().getName ()+"t "+i);
}
//通知
flag=1;
condition1.signalAll ();
}catch (InterruptedException e) {
e.printStackTrace ();
}finally {
lock.unlock ();
}
}
}
7.创建多线程之callable接口:
7.1 其他创建方式:
继承 extendsThread;实现Runnable接口;实现Callable接口;线程池; 7.2 callable 和Runnable区别:
public class CallableDemo implements Callable{ @Override public Integer call() throws Exception { return null; } } class RunnableDemo implements Runnable{ @Override public void run() { } }
Callable可以抛出异常Callable有返回值实现的方法不一样,run() call() 7.3Callable的使用
public class CallableDome {
public static void main ( String[] args ) {
FutureTask futureTask = new FutureTask (new MyThread1 ());
try {
Thread t1 = new Thread (futureTask,"aa");
t1.start ();
System.out.println (futureTask.get ());
} catch (InterruptedException e) {
e.printStackTrace ();
} catch (ExecutionException e) {
e.printStackTrace ();
}
}
}
class MyThread1 implements Callable{
@Override
public Integer call () throws Exception {
System.out.println ("我是call()方法");
return 1111;
}
}
结果:
我是call()方法 11118. 线程池: 8.1线程池的优点:
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出的数量排队等候,等其它线程执行完毕,再从队列中取出任务来执行。
!主要特点:线程复用:控制最大的并发数;管理线程。
public class ThreadPoolsDome {
public static void main ( String[] args ) {
//ExecutorService executorService = Executors.newSingleThreadExecutor (); //单一的窗口
// ExecutorService executorService = Executors.newCachedThreadPool (); //带缓存
ExecutorService executorService = Executors.newFixedThreadPool (5);
try {
for (int i = 1 ; i <=10 ; i++) {
executorService.execute (()->{
System.out.println (Thread.currentThread ().getName ()+"处理业务");
});
}
}catch (Exception e){
e.printStackTrace ();
}finally {
executorService.shutdown ();
}
}
}
结果:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
1.int corePoolSize 核心线程数(一直存在,除非设置了(allowCoreThreadTimeOut));线程池创建好以后就准备就绪的线程数量,就等待来接收异步任务去执行。好比与Thread thread=new Thread(); 而没有调用start()。
2.int maximumPoolSize 最大可执行线程数量,控制资源
3.long keepAliveTime 存活时间 如果当前正在运行的线程数量大于corePoolSize数量,当空闲时间达到keepAliveTime时,多余空闲线程会被销毁,只剩下corePoolSize
4.TimeUnit unit keepAliveTime时间单位
5.BlockingQueue workQueue 阻塞队列。被提交,但是尚未执行的任务。
6.ThreadFactory threadFactory 线程的创建工厂
7.RejectedExecutionHandler handler 如果队列满了,按照指定的拒绝策略,拒绝执行任务
案例:5个人到银行办理业务,银行有两个窗口,这里的5个人,是没有超过最大可执行线程数量的(5),所以说只会使用指定了的核心线程数(2),当我们把5个人改为10个人之后,就会直接使用最大可执行线程(5)的数量来执行,超过的(5)其中会有指定了的阻塞队列的数量(3),在阻塞区等候,剩下的2个会根据拒绝策略来执行相关的操作,我们这里使用的是超过的直接丢弃策略。
原理图:
主要流程:
1.在创建了线程之后,等待提交过来的任务请求。
2.当调用execute()方法添加一个任务请求时,线程池会做如下判断
2.1如果正在 运行的线程数量小于corePoolSize,马上创建线程执行这个任务。
2.2如果正在运行的线程数量大于或者等于corePoolSize,那么将这个任务放入队列。
2.3如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻来运行这个任务。
2.4如果队列满了且正在运行的线程数量大于或者等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
3.当一个线程完成任务时,它会从队列中取下一个任务来执行。
4.当一个线程无事可做超过指定的时间(keepAliveTime)时,线程会判断:
如果当前运行的线程数大于corePoolSize,那么这个线程就会被停掉。
如果线程池的所有任务完成后它最终会收缩到corePoolSize的大小
!注意 工作中 3个线程池一个不用 需要自己手写
8.4 自定义线程池:ExecutorService pool1 = new ThreadPoolExecutor (
2, //int corePoolSize,
5, //int maximumPoolSize,
1L, //long keepAliveTime,
TimeUnit.SECONDS, //时间单位
new linkedBlockingQueue<> (3), //等待队列
Executors.defaultThreadFactory (), //线程的创建工厂
//new ThreadPoolExecutor.AbortPolicy () //拒绝策略 AbortPolicy 抛异常
// new ThreadPoolExecutor.CallerRunsPolicy () //CallerRunsPolicy 丢给其他的线程
// new ThreadPoolExecutor.DiscardOldestPolicy () //丢弃多的线程
new ThreadPoolExecutor.DiscardPolicy () //丢弃多的线程
);
try {
for (int i = 1 ; i <=10 ; i++) {
pool1.execute (()->{
System.out.println (Thread.currentThread ().getName ()+"处理业务");
});
}
}catch (Exception e){
e.printStackTrace ();
}finally {
pool1.shutdown ();
}
8.5合理配置线程数
CPU密集型:任务配置尽可能少的线程数量,一般公式:CPU核数+1个线程的线程池。
IO密集型:由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数*2
9.死锁: 9.1什么是死锁 9.2 代码:
public class DeadLockDome {
public static void main ( String[] args ) {
String lockA="lockA";
String lockB="lockB";
new Thread(new Thread1 (lockA,lockB),"ThreadAAA").start();
new Thread(new Thread1 (lockB,lockA),"ThreadBBB").start();
}
}
class Thread1 implements Runnable{
private String A;
private String B;
public Thread1(String a,String b){
this.A = a;
this.B = b;
}
@Override
public void run () {
synchronized (A){
System.out.println (Thread.currentThread ().getName ()+"t 拿着锁"+A+"想要"+B);
try {
TimeUnit.SECONDS.sleep (1);
} catch (InterruptedException e) {
e.printStackTrace ();
}
synchronized (B){
System.out.println (Thread.currentThread ().getName ()+"t 拿着锁"+B+"想要"+A);
}
}
}
}
结果:



