1、JUC概述
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位。
线程:系统分配处理器时间资源的基本单位,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。
线程状态:NEW(新建)、RUNNABLE(准备就绪)、BLOCK(阻塞)、WAITING(不见不散)、TIMED_WAITING(过时不候)、TERMINATED(终结)
wait和sleep的区别
- (1)sleep是Thread静态方法,wait是Object的方法,任何对象实例都能调用。
- (2)sleep不会释放锁,他也不需要占用锁。wait会释放锁,但调用它的而前提是当前线程占有锁(即代码要在synchronized中)。
- (3)他们都可以被interrupted方法打断。
并发和并行
串行是一次只能取得一个任务,并执行这个任务。
并行:多项工作一起执行,最后再汇总。
并发:同一时刻多个线程在访问同一个资源,多个线程对一个点。
管程:在操作系统中叫监视器,在java中叫锁。是一种同步机制,保证同一时间,只有一个线程访问被保护的数据或者代码。 在JVM同步基于进入和退出,使用管程对象实现的。
用户线程:自定义线程,new Thread()。主线程结束了,用户线程还在运行,jvm存活。
守护线程:后台特殊的线程,比如垃圾回收。没有用户线程了,都是守护线程,jvm结束。
2、Lock接口
Synachronized
可重入锁(ReentrantLock):来一个等待,上一个执行完再进行下一个。
lock和synachronized区别?
- 1、Lock是一个接口,而synachronized是Java中的关键字,synchronized是内置的语言实现;
- 2、synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unlock()去释放锁,则很可能造成死锁现象,因此使用lock时需要在finally块中释放锁;
- 3、Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
- 4、通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
- 5、Lock可以提高多个线程进行读操作的效率。
从性能上来说,如果竞争资源不是很激烈,性能差不多,如果竞争激烈,lock性能更好。
3、线程间通信
wait()
notify()
notiyAll()
package com.atguigu.sync;
class Share {
private int number =0;
public synchronized void incre() throws InterruptedException{
while(number!=0){
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"::"+number);
this.notifyAll();
}
public synchronized void decr() throws InterruptedException {
while(number!=1){
this.wait();
}
number--;
this.notifyAll();
System.out.println(Thread.currentThread().getName()+"::"+number);
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.incre();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"aa").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"bb").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.incre();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"cc").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"dd").start();
}
}
虚假唤醒问题
判断条件用if的话,wait在哪里等在哪里唤醒,直接往下执行,出现虚假唤醒问题。所以判断条件用while判断。(安检的例子,如果下了飞机再上飞机没有进行第二次安检,就会出现虚假唤醒的问题)。
4、线程间定制化通信
启动三个线程,按照如下要求:
AA打印5次,BB打印10次,CC打印15次。
每个线程按照顺序执行。
package com.atguigu.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareResource{
//定义标志位 1AA 2BB 3CC
private int flag = 1;
private Lock lock = new ReentrantLock();
//创建三个condition
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print5(int loop) throws Exception{
//上锁
lock.lock();
try {
//判断
while(flag != 1){
c1.await();
}
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName()+"::"+i+":轮数:"+loop);
}
flag =2;
c2.signal();
}finally {
lock.unlock();
}
}
public void print10(int loop) throws Exception{
lock.lock();
try {
while (flag!=2){
c2.await();
}
for (int i =1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+"::"+i+"轮数:"+loop);
}
flag=3;
c3.signal();
}finally {
lock.unlock();
}
}
public void print15(int loop) throws Exception{
lock.lock();
try {
while (flag!=3){
c3.await();
}
for (int i =1;i<=15;i++){
System.out.println(Thread.currentThread().getName()+"::"+i+"轮数:"+loop);
}
flag=1;
c1.signal();
}finally {
lock.unlock();
}
}
}
public class ThreadDemo3 {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
shareResource.print5(i);
} catch (Exception e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
shareResource.print10(i);
} catch (Exception e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
shareResource.print15(i);
} catch (Exception e) {
e.printStackTrace();
}
}
},"CC").start();
}
}
Condition 将 Object 监视器方法(wait 、notify 和 notifyAll )分解为不同的对象,通过将它们与任意 Lock 实现的使用相结合,从而产生每个对象具有多个等待集的效果。其中 Lock 替换了 synchronized 方法和语句的使用,Condition 替换了对象监视器方法的使用。
条件(也称为 condition queues 或 condition variables )为一个线程提供了一种暂停执行(“等待”)的方法,直到另一个线程通知某个状态条件现在可能为真。因为对这种共享状态信息的访问发生在不同的线程中,它必须受到保护,所以某种形式的锁与条件相关联。等待条件提供的关键属性是它 atomically 释放关联的锁并挂起当前线程,就像 Object.wait 一样。
Condition 实例本质上绑定到锁。要获取特定 Lock 实例的 Condition 实例,请使用其 newCondition() 方法。
例如,假设我们有一个支持 put 和 take 方法的有界缓冲区。如果在空缓冲区上尝试take,则线程将阻塞直到有项可用;如果尝试对满缓冲区执行 put,则线程将阻塞直到有空间可用。我们希望在单独的等待集中等待 put 个线程和 take 个线程,以便我们可以使用优化,在缓冲区中的项目或空间可用时一次只通知一个线程。这可以使用两个 Condition 实例来实现。
5、集合的线程安全
ArrayList是线程不安全的。
解决方案一:
Vector(jdk1.0)方法使用了synchronized
Vector<String> list = new Vector<>();
解决方案二:
Collections(不常用)
List<String> list = Collections.synchronizedList(new ArrayList<>());
解决方案三:
CopyOnWriteArrayList(JUC的解决方案,常用)
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
原理:写时复制技术。读是并发读。写的时候复制和之前集合一样的,写入新内容,写完后与之前的合并,读取新的集合。兼顾了并发读,也照顾到了独立写操作。
HashSet是线程不安全的。HashSet的底层实现原理是HashMap,放的值就是HashMap的键,是不可重复的,无序的。
解决方案:CopyOnWriteArraySet
Set<String> set = new CopyOnWriteArraySet<>();
HashMap的线程不安全问题
解决方案:
Map<String,String> map = new ConcurrentHashMap<>();
6、多线程锁
锁的八种情况
synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体如下:
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的Class对象。
- 对于同步方法快,锁是synchronized括号里配置的对象。
synchronized锁的是对象。所以同一个对象,会先执行第一个加synchronized方法,在执行第二个加synchronized方法。
如果一个synchronized方法,一个普通方法,使用同一个对象,synchronized方法停留4s,则先执行普通方法,在执行synchronized的方法。
两个对象,两个synchronized方法,就不是同一把锁。
静态方法 static,锁的就不是this,而是锁的当前类Class。不管一个对象还是两个对象,都会等待第一个static synchronized方法。
一个静态synchronized方法,一个普通静态方法,不是一把锁,一个是Class,一个是this,当前对象。
公平锁和非公平锁
非公平锁 new ReentrantLock()或者new ReentrantLock(false); 可能造成一个线程把所有活都干了,其他线程出现被饿死的情况。好处是执行效率高。
公平锁 new ReentrantLock(true); 阳光普照,效率相对低。
可重入锁
synchronized(隐式,上锁解锁隐式进行)和Lock(显示)都是可重入锁。
递归锁
进入了第一道大门,里面的锁都能进入。
package com.atguigu.sync;
public class SynLockDemo {
public static void main(String[] args) {
Object o = new Object();
new Thread(() -> {
synchronized (o){
System.out.println(Thread.currentThread().getName()+"外层");
synchronized (o){
System.out.println(Thread.currentThread().getName()+"中层");
synchronized (o){
System.out.println(Thread.currentThread().getName()+"内层");
}
}
}
},"AA").start();
}
}
死锁:两个或者两个以上的进程在执行过程中,因为争夺资源造成相互等待的线程,如果没有外力干涉,他们无法再执行下去。
死锁代码
package com.atguigu.lock;
public class DeadLock {
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized (a){
System.out.println(Thread.currentThread().getName()+" 持有锁a,尝试获取锁b");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b){
System.out.println(Thread.currentThread().getName()+"获取锁b");
}
}
},"A").start();
new Thread(()->{
synchronized (b){
System.out.println(Thread.currentThread().getName()+" 持有锁b,尝试获取锁a");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a){
System.out.println(Thread.currentThread().getName()+"获取锁a");
}
}
},"B").start();
}
}
查看是否发生死锁
- jps -l 查看目前的进程
- jstack 进程号 堆栈跟踪工具
7、Callable接口
创建线程的多种方式
第一种:继承Thread类(当线程终止,无法使线程返回结果)
package atguigu.java;
//1.创建一个继承于Thread类的子类
class MyThread extends Thread {
//2.重写Thread类的run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3.创建Thread类的子类的对象
MyThread t1 = new MyThread();
//4.通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
t1.start();
/*问题一:我们不能通过直接调用run()的方式启动线程,
这种方式只是简单调用方法,并未新开线程*/
//t1.run();
/*问题二:再启动一个线程,遍历100以内的偶数。
不可以还让已经start()的线程去执行。会报IllegalThreadStateException*/
//t1.start();
//重新创建一个线程的对象
MyThread t2 = new MyThread();
t2.start();
//如下操作仍然是在main线程中执行的。
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i + "***********main()************");
}
}
}
}
第二种:实现Runnable接口(当线程终止,无法使线程返回结果)
package atguigu.java;
//1.创建一个实现了Runnable接口的类
class MThread implements Runnable {
//2.实现类去实现Runnable中的抽象方法:run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
//3.创建实现类的对象
MThread mThread = new MThread();
//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mThread);
t1.setName("线程1");
//5.通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run()
t1.start();
//再启动一个线程,遍历100以内的偶数
Thread t2 = new Thread(mThread);
t2.setName("线程2");
t2.start();
}
}
第三种:Callable接口
Runnable和Callable接口的区别:
- 是否有返回值
- 是否会抛出异常,Callable无法计算结果会抛出异常。
- 实现方法名称不同,一个run方法,一个call方法
第四种:线程池的方式
JUC强大的辅助类
减少计数 CountDownLatch
// 6个同学都走了,班长锁门
for (int i = 1; i <= 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");
},String.valueOf(i)).start();
}
System.out.println(Thread.currentThread().getName()+" 班长锁门走人了");
//出现一个问题,main方法执行的快,一些还没走,班长就锁门了
使用CountDownLatch类
//定义初始值
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");
//减1操作
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//计数器不为0等待,为0继续执行阻塞的线程
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+" 班长锁门走人了");
循环栅栏CyclicBarrier
栅栏类似于闭锁,它能阻塞一组线程直到某个事件的发生。栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。
CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。
package com.atguigu.juc;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
private static final int NUMBER = 7;
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () -> {
System.out.println("******集齐七颗龙珠就可以召唤神龙");
});
for (int i = 1; i <= 7; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + " 集了龙珠");
try {
//等待
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
信号灯 Semaphore
//6辆汽车,3个停车位
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+" 获取到了停车位");
TimeUnit.SECONDS.sleep(new Random().nextInt(4));
System.out.println(Thread.currentThread().getName()+" ------离开了停车位");
} catch (Exception e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
读写锁
悲观锁 (只能一个人操作)
乐观锁 (通过版本号进行控制)
表锁 (不会发生死锁)
行锁(会发生死锁)
读锁(共享锁)会发生死锁,线程1读的时候会改,线程2读的时候也会改,线程1等线程2读完再改,线程2等线程1读完再改,发生死锁。
写锁(独占锁)会发生死锁,线程1写操作要操作第2条记录,要等线程2释放才能操作,线程2写操作要操作第1条记录,要等线程1释放才能操作。
读写锁
package com.atguigu.readwrite;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class MyCache{
private ReadWriteLock rwlock = new ReentrantReadWriteLock();
public volatile Map<String,Object> map =new HashMap<>();
public void put(String key, Object o){
try {
rwlock.writeLock().lock();
System.out.println(Thread.currentThread().getName()+" 正在写操作"+key);
TimeUnit.MILLISECONDS.sleep(300);
map.put(key, o);
System.out.println(Thread.currentThread().getName()+ "完成了写操作");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
rwlock.writeLock().unlock();
}
}
public Object get(String key){
Object o = null;
try {
rwlock.readLock().lock();
System.out.println(Thread.currentThread().getName()+" 正在读操作"+key);
o = map.get(key);
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(Thread.currentThread().getName()+" 完成了读操作");
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
rwlock.readLock().unlock();
}
return o;
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 1; i <= 6 ; i++) {
final int num = i;
new Thread(()->{
myCache.put(num+"",num);
},String.valueOf(i)).start();
}
for (int i = 1; i <=6 ; i++) {
final int m = i;
new Thread(()->{
Object o = myCache.get(m+"");
},String.valueOf(i)).start();
}
}
}
读写锁:一个资源,可以被多个读的线程进行访问,可以被一个写的线程进行访问,但是不能同时存在读和写的线程,读写互斥,读读共享。
第一 无锁:多线程抢夺资源,乱
第二 添加锁:使用synchronized和ReentrantLock 都是独占的,每次只能来一个操作。
第三 读写锁:ReentrantReadWriteLock 读读可以共享,提升性能,同时多人进行读操作。缺点:(1)造成锁饥饿,一直读。没有写操作。(2)读时候,不能写,只有读完成之后,才可以写,写操作可以读。
锁降级:将写入锁降级为读锁 :获取写锁-》获取读锁-》释放写锁-》释放读锁。在写操作的过程也能进行读操作。!!!但是读锁不能升级为写锁。不能先读再写。
public static void main(String[] args) {
ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.WriteLock writeLock = rwlock.writeLock();
ReentrantReadWriteLock.ReadLock readLock = rwlock.readLock();
writeLock.lock();
System.out.println("----写操作");
readLock.lock();
System.out.println("!!!读操作");
writeLock.unlock();
readLock.unlock();
}
BlockingQueue阻塞队列
阻塞队列分类
ArrayBlockingQueue:基于数组的阻塞队列实现,维护了一个定长的数组,以便换缓存队列种的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
LinkedBlockingDueue:由链表结构组成的有界的阻塞队列。
阻塞队列核心方法
ThreadPool线程池
线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建和销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
线程池底层的七个参数:ThreadPoolExecutor
int corePoolSize 常驻线程数量(核心)
int maxmumPollSize 最大线程数量
long keepAliveTime(值) TimeUnit unit(单位) 线程存活时间
BlockingQueue workQueue, 阻塞队列
ThreadFactory threadFactory,线程工厂,用于创建线程的
RejectedExecutionHandler handler 拒绝策略。
线程池底层的工作原理
AbortPolicy(默认):直接抛出RejectedExcutionException异常阻止系统正常运行
CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
DiacardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常。如果允许任务丢失,这是一种很好的策略。
自定义线程池
package com.atguigu.pool;
import java.util.concurrent.*;
public class ThreadPoolDemo2 {
public static void main(String[] args) {
ExecutorService threadPool1 = new ThreadPoolExecutor(
3,
10,
3L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try {
for (int i = 1; i <=10 ; i++) {
threadPool1.execute(()->{
System.out.println(Thread.currentThread().getName()+"办理业务");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool1.shutdown();
}
}
}