1.什么是JUC?
JUC是java的并发工具包,java.util.concurrent;
在这个包里面增加了并发编程需要的工具类;
这个包里面包含几个小的,已标准化的可拓展框架;
并且提供了一些功能适用的类,没有这些类,一些功能实现起来冗长乏味.
2.进程和线程?
进程:进程是一个具有一定独立功能的程序关于数据集合的一次运行活动。他是操作系统动态执行的基本单元,在传统操作系统中,进程既是基本的分配单元,也是基本的执行单元;
线程:通常一个进程中包含多个线程;当然一个进程中至少有一个线程,不然没有存在的意义。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单元,而把线程作为独立运行和调度的基本单位,由于他比进程小,基本上不拥有系统资源,故对他的调度所付出的开销就会小得多,能够高效的提高系统多个程序间并发执行的程度…
3.并行和并发?
并行:多个任务一起执行,然后汇总 比如电商的商品详情的价格和销售属性和销售属性值(1+n)同时执行是可以的.
并发:多个任务对同一个点,比如春运抢票,集合的数据添加操作…会导致数据并发修改异常…
4.wait和sleep区别?
①wait是Object类的方法;sleep是Thread类里面的方法;
②wait是让线程处于等待状态,但是会释放锁;sleep是让线程处于睡眠状态,但是不会释放锁,睡眠时间一过,就会重新从当前位置醒来,继续执行逻辑代码…
5.创建线程有几种方式?
①继承Thread类,然后new xxxThread.start();
②实现Runnable接口,实现接口,以为着新增一个类(其实内部是一个匿名内部类),
class MyRunnable implements Runnable//新建类实现runnable接口
new Thread(new MyRunnable(), name).start // 使用Rannable实现类创建进程,name是线程名
//---------------
new Thread(new Runnable() {
@Override
public void run() {
// 调用资源方法,完成业务逻辑
}
}, "your thread name").start();
//lambda简化
new Thread(() -> {
}, "your thread name").start();
ps:@FunctionalInterface函数式接口标识
③实现Callable接口
④线程池
6.synchronized的8锁问题?
同步方法块(锁的是括号里面的对象),普通同步方法(锁的是当前实例对象),静态同步方法(锁的是当前类对象)
一个实例对象的非静态同步方法获得锁之后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁之后才能获得锁,说白了,就是一个实例对象里面的方法,都会被上锁;只有一个释放了,另外一个才能继续上锁;但是不同实例对象的非静态同步方法因为用的是不同对象的锁,所以无需等待其他实例对象的非静态同步方法释放锁,就可以获取自己的锁…
所有的静态同步方法用的是同一把锁—类对象本身.不管是不是同一个实例对象,只要是一个类的对象,一旦一个静态同步释放锁之后,其他的对象静态同步方法,都必须等该方法释放锁以后,方可获得锁…
静态同步方法和非静态同步方法之间没有静态条件…
7.Lock锁?*
ReentrantLLock可重入锁(递归锁),ReentrantReadWriteLock.readLock(),ReentrantReadWriteLock.writeLock();
class Ticket{
private Integer number = 20;
private ReentrantLock lock = new ReentrantLock();
public void sale(){
lock.lock();
//递归出口
if (number <= 0) {
System.out.println("票已售罄!");
lock.unlock();
return;
}
try {
Thread.sleep(200);
number--;
System.out.println(Thread.currentThread().getName() + "买票成功,当前剩余:" + number);
// 调用check方法测试锁的可重入性
this.check();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 为了测试可重入锁,添加检查余票方法
*/
public void check(){
lock.lock();
System.out.println("检查余票。。。。");
lock.unlock();
}
}
公平锁:
谁等锁的时间最长,谁先获得锁…
private ReentrantLock lock = new ReentrantLock(true);
//结果如下
AAA买票成功,当前剩余:19
检查余票。。。。
BBB买票成功,当前剩余:18
检查余票。。。。
CCC买票成功,当前剩余:17
检查余票。。。。
AAA买票成功,当前剩余:16
检查余票。。。。
BBB买票成功,当前剩余:15
检查余票。。。。
CCC买票成功,当前剩余:14
。。。。。。
限时等待
tryLock里面有一个时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true表示获取锁成功,false表示获取锁失败,我们将这种方法用来解决死锁问题…
new ReentrantLock().tryLock(1,TimeUnit.SECONDS);
8.ReentrantLock和synchronized区别?
①synchronized是独占锁,加锁和解锁过程自动进行,易于操作,但是不够灵活;reentrantLock也是独占锁,加锁和解锁需要手动进行,不易操作,但是非常灵活…
②synchronized可重入,因为加锁和解锁自动进行,不必担心是否释放锁;ReentrantLock可重入,但是加锁和解锁需要手动进行,并且次数一致,否则其他线程无法获得锁…
③synchronized不可以响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以响应中断…
ReentrantLockReadWriteLock读写锁
在并发场景下,我们几乎会高频率的使用独占式锁,通常使用同步方法块,比如synchronized,还有可重入锁ReentrantLock,但是同一时刻只有一个线程可以获得锁.但是实际场景下,我们是读多写少,依然使用独占锁,读的性能会大幅度下降…针对这种情况,java提供了另外一个实现lock接口的ReentrantReadWriteLock(读写锁).读写锁允许同一时刻被读个读线程访问,但是在写线程访问时,所有的读线程和其他写线程都会被阻塞…
特点:读读共享
读写/写写互斥
案例:读写锁
锁降级
线程间通信?
模板:判断,干活,通知
例子:加一减一操作,交替打印101010…
两个线程,利用object的wait和notifyAll方法,判断使用的是if,这个打印没有问题,只有两个线程,但是四个线程开启,就会出现问题了,首先是线程是从wait位置醒来继续执行的,然后if判断,没有递归性,导致,出现2,3,4…
中断和虚假唤醒是可能产生的,所以要使用loop循环,if只会判断一次,while是唤醒就要拉回来再次判断一次…
线程间通信
new ReentrantLock()===lock
lock.newCondition() == conidtion
condition.await()===等待
condition.singalAll()====唤醒其他线程
定制化调用通信
class ShareDataTwo {
private Integer flag = 1; // 线程标识位,通过它区分线程切换
private final Lock lock = new ReentrantLock();
private final Condition condition1 = lock.newCondition();
private final Condition condition2 = lock.newCondition();
private final Condition condition3 = lock.newCondition();
public void print5() {
lock.lock();
try {
while (flag != 1) {
condition1.await();
}
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + (i + 1));
}
flag = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10() {
lock.lock();
try {
while (flag != 2) {
condition2.await();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + (i + 1));
}
flag = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15() {
lock.lock();
try {
while (flag != 3) {
condition3.await();
}
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + (i + 1));
}
flag = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
/**
* 多线程之间按顺序调用,实现A->B->C
* 三个线程启动,要求如下:
* AA打印5次,BB打印10次,CC打印15次
* 接着
* AA打印5次,BB打印10次,CC打印15次
* ......来10轮
*/
public class ThreadOrderAccess {
public static void main(String[] args) {
ShareDataTwo sdt = new ShareDataTwo();
new Thread(()->{
for (int i = 0; i < 10; i++) {
sdt.print5();
}
}, "AAA").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
sdt.print10();
}
}, "BBB").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
sdt.print15();
}
}, "CCC").start();
}
}
并发容器类
Vector,Synchronized缺点
vector:内存消耗比较大,适合一次增量比较大的情况
synchronizedList:迭代器涉及的代码没有加上线程同步代码
CopyOnWrite容器
CopyOnWrite容器**(简称COW容器)即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
①copyOnWriteArrayList :本质数组
②copyOnwriteArraySet
CopyOnWrite并发容器用于读多写少的并发场景**。比如:白名单,黑名单。假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中,黑名单一定周期才会更新一次。
缺点:①内存占用问题:复制了一份数据,原来的数据也会占用一份内存
②数据一致性问题:可以保证最终一致性,但是不可以实时一致
拓展类比: Set和Map
ConcurrentHashSet,ConcurrentHashMap
JUC强大的辅助类
①CountDownLatch 倒计数器
new CountDownLatch()=====countDownLatch
countDownLatch.countDown()====减一
countDownLatch.await()======等待
面试题:join和countDownLatch区别?
①join是Object里面的方法,该线程是会一直被阻塞到线程运行完毕.但是countDownLatch则是使用计数器允许子线程运行完毕或者运行中进行递减操作,也就是它可以在子线程运行任何时候遇到await方法返回而不一定必须等到线程结束;另外使用线程池来管理线程的时候一般都是直接添加Runnable到线程池这个时候没有办法调用线程的join方法了.countDownLatch比join更灵活
②CycleBarrier 循环栅栏
集合打boss,设置的线程数,必须到达,才可以最终实现里面的runnable匿名内部类里面的方法
循环栅栏和倒计数器区别?
倒计数器只能用一次,但是循环栅栏可以用多次,还有一个reset方法,再次进行另外一组线程方法,重发利用;countDownLatch允许一个或多个线程等待一组事件的发生,而cyclicBarrier用于等待其他线程到达栅栏(第一个参数)
③Semaphore 信号量
可以用来控制同时访问的线程个数,非常适合需求量大,但是资源又很紧张的情况.比如给定一个资源数目有限的资源池,假设资源数目是N,每一个线程获取衣蛾资源,当资源分配完毕,后来线程阻塞等待,直到前面资源释放才能继续执行
public Semaphore(int permits) // 构造方法,permits指资源数目(信号量)
public void acquire() throws InterruptedException // 占用资源,当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),要么一直等下去,直到有线程释放信号量,或超时。
public void release() // (释放)实际上会将信号量的值加1,然后唤醒等待的线程。
目的:多个共享资源的互斥使用
2.并发线程数的控制,保护一个关键部分不要一次输入超过N个线程
Callable接口?
**面试题:**callable接口和runnable接口区别?
相同点:都是接口,都可以编写多线程程序,都采用Thread.start()启动线程
不同点:
1.具体方法不同:一个是run,一个是call
2.Runnable没有返回值;Callable可以返回执行结果,是一个泛型
3.Callable接口的call()可以允许抛异常;run()的异常只能内部捕获消化,不能继续往上抛
4.它提供了检查计算是否完成的方法,以等待计算的完成,并且检索计算的结果…
面试题:获得多线程的方法几种?
(1)继承thread类(2)runnable接口
如果只回答这两个你连被问到juc的机会都没有
正确答案如下:
传统的是继承thread类和实现runnable接口
java5以后又有实现callable接口和java的线程池获得
阻塞队列
线程池七大参数
线程池工作原理
并发底层原理