JUC基础知识点(一)
1、JUC概述
1.1 什么是JUC
在Java中,线程部分是一个重点,本篇文章说的JUC是关于线程的。JUC就是java.util.concurrent
工具包的简称。这是一个处理线程的工具包,JDK1.5开始出现的
1.2 线程和进程的概念
-
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。
-
线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
-
总结:
-
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位
-
线程:系统分配处理器时间资源的基本单元,或者说进程内独立执行的一个单元执行流。线程——程序执行的最小单位
-
1.3 线程的状态
线程状态枚举类(Thread.State): NEW(新建)、RUNNABLE(准备就绪)、BLOCKED(阻塞)、WAITING(不见不散)、TIMED_WAITING(过时不候)、TERMINATED(终结)
wait和sleep的区别
-
sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用
-
sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized中)
-
它们都可以被interrupted方法中断
并发与并行
-
并发:多个线程在访问同一个资源(线程频繁切换),多个线程对一个点
-
例子:春运抢票、电商秒杀,一个医生给多个病人轮流看病
-
-
并行:多项工作一起执行(同时),之后再汇总
-
多个医生同时给病人看病
-
管程: Monitor监视器 就是我们所说的锁 是一种同步机制,保证同一个时间,只有一个线程访问被保护数据或者代码。JVM同步基于进入和退出,使用管程对象实现的。
用户线程和守护线程
-
用户线程:自定义线程 主线程结束了,用户线程还在运行,jvm存活
-
守护线程:比如垃圾回收 没有用户线程了,都是守护线程,jvm结束
public class Main { public static void main(String[] args){ Thread aa = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "::" + Thread.currentThread().isDaemon()); while(true){ } }, "aa"); //设置守护线程(注意要在start()方法之前设置) aa.setDaemon(true); aa.start(); System.out.println(Thread.currentThread().getName() + "over"); }}//第一次输出结果 main over aa::false 表示是用户线程//设置守护线程之后 main over
2、Lock接口
2.1 复习Synchronized
synchronized 是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
-
修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象
-
修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法作用的对象是调用这个方法的对象
-
修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象
-
修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主要的对象是这个类的所有对象
2.2 什么是Lock接口
Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的Condition对象
所有已知实现类:ReentrantLock、ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock
Lock与Synchronized区别:
-
Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现
-
synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock()时需要在finally块中释放锁
-
Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断
-
通过Lock可以知道有没有成功获取锁,而synchronized却无法办到
-
Lock可以提高多个线程进行读操作的效率
-
Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;
-
Lock则必须要用户去手动释放锁,如果没有手动释放锁,就有可能出现死锁现象
2.3 创建线程的多种方式
-
继承Thread类创建线程
-
实现Runnable接口创建线程
-
使用Callable和Future创建线程
-
使用线程池例如用Executor框架
多线程编程步骤:
-
第一步:创建资源类,在资源类创建属性和操作方法(例子:空调)
-
第二步:创建多个线程,调用资源类的操作方法
2.4 使用Lock实现卖票例子
//第一步 创建资源类,定义属性和操作方法class Ticket{ //票数 private int number = 30; //操作方法:卖票 public synchronized void sale(){ //判断:是否有票 if(number > 0){ System.out.println(Thread.currentThread().getName()+":卖出:" +(number--)+"剩下:"+number); } }}public class SaleTicket { //第二步 创建多个线程,调用资源类的操作方法 public static void main(String[] args){ //创建Ticket对象 Ticket ticket = new Ticket(); //创建三个线程 new Thread(new Runnable(){ @Override public void run(){ //调用卖票方法 for(int i = 0; i < 40; i++){ ticket.sale(); } } },"AA").start(); new Thread(new Runnable(){ @Override public void run(){ //调用卖票方法 for(int i = 0; i < 40; i++){ ticket.sale(); } } },"BB").start(); new Thread(new Runnable(){ @Override public void run(){ //调用卖票方法 for(int i = 0; i 0){ System.out.println(Thread.currentThread().getName()+":卖出:" +(number--)+"剩余:"+number); } }finally{ //解锁 lock.unlock(); } }}public class LSaleTicket(){ //第二步 创建多个线程,调用资源类的操作方法 //创建三个线程 public static void main(String[] args){ LTicket ticket = new LTicket(); new Thread(() -> { for(int i = 0; i { for(int i = 0; i { for(int i = 0; i < 40; i++){ ticket.sale(); } }, "CC").start(); }}
3、线程间通信
//线程间通信 —— synchronized实现//第一步 创建资源类,定义属性和操作方法class Share{ //初始值 private int number = 0; //+1的方法 public synchronized void incr() throws InterruptedException{ //第二步 判断 干活 通知 while(number != 0){//判断num是否是0,如果不是,等待 this.wait();//在哪里睡,就会在哪里醒 //前面判断如果用if就会有虚假唤醒问题 } //如果number值是0,就+1操作 number++; System.out.println(Thread.currentThread().getName()+"::"+number); //通知其他线程 this.notifyAll(); } //-1的方法 public synchronized void decr(){ while(number != 1){//判断 this.wait(); } number--;//干活 System.out.println(Thread.currentThread().getName()+"::"+number); //通知其他线程 this.notifyAll(); }}//第三步 创建多个线程,调用资源类的操作方法public class ThreadDemo1{ public static void main(String[] args){ Share share = new Share(); //创建线程 new Thread(() -> { for(int i = 1; i { for(int i = 1; i { for(int i = 1; i { for(int i = 1; i { for(int i = 1; i { for(int i = 1; i { for(int i = 1; i { for(int i = 1; i < 10; i++){ try{ share.decr(); }catch(InterruptedException e){ e.printStackTrace(); } } }, "DD").start(); } }
4、线程间定制化通信
//线程间定制化通信 —— 通过标志位//第一步 创建资源类class ShareResource{ //定义标志位 private int flag = 1;//1 AA 2 BB 3 CC //创建Lock锁 private lock lock = new ReentrantLock(); //创建三个condition private Condition c1 = lock.newCondition(); private Condition c2 = lock.newCondition(); private Condition c3 = lock.newCondition(); //打印5次,参数第几轮 public void print5(int loop){ //上锁 lock.lock(); try{ //判断 while(flag != 1){ //等待 c1.await(); } for(int i = 1; i <= 5; i++){//干活 System.out.println(Thread.currentThread().getName()+"::"+" : 轮数 :"+loop) } flag = 2;//修改标志位2 c2.signal();//通知 }finally{ //释放锁 lock.unlock(); } } //打印10次,参数第几轮 public void print15(int loop){ //上锁 lock.lock(); try{ //判断 while(flag != 2){ //等待 c2.await(); } for(int i = 1; i <= 5; i++){//干活 System.out.println(Thread.currentThread().getName()+"::"+" : 轮数 :"+loop) } flag = 3;//修改标志位2 c2.signal();//通知 }finally{ //释放锁 lock.unlock(); } } //打印15次,参数第几轮 public void print15(int loop){ //上锁 lock.lock(); try{ //判断 while(flag != 3){ //等待 c3.await(); } for(int i = 1; i { for(int i = 1; i { for(int i = 1; i { for(int i = 1; i <= 10; i++){ try{ shareRource.print15(i); } catch (InterruptedException e){ e.printStackTrace(); } } },"CC").start();}
5、集合的线程安全
5.1 集合线程不安全演示
//list集合线程不安全public class ThreadDemo4{ public static void main(String[] args){ //创建ArrayList集合 List list = new ArrayList(); for(int i = 1; i { //向集合添加内容 list.add(UUID.randomUUID().toString().substring(0, 8)); //从集合中获取内容 System.out.print(list); }, String.valueOf(i)).start(); } }}
解决方案-Vector
将List list = new ArrayList();
改成
List list = new Vector();即可 (很古老的方案——不常用)
解决方案-Collections
List list = Collections.synchronizedList(new ArrayList());
解决方案-CopyOnWriteArrayList ——常用
List list = new CopyOnWriteArrayList();
写时复制技术,读的时候支持并发读,写是独立写,写之前先复制内容,在新的地址写入新的内容之后合并,后面再读取新的内容 —— 例如签到
首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器
5.2 HashSet线程不安全
public class ThreadDemo4{ public static void main(String[] args){ //创建HashSet集合 //Set set = new HashSet(); //解决方案 Set set = new CopyOnWriteArraySet(); for(int i = 1; i { //向集合添加内容 set.add(UUID.randomUUID().toString().substring(0, 8)); //从集合中获取内容 System.out.print(set); }, String.valueOf(i)).start(); } }}
5.3 HashMap线程不安全
public class ThreadDemo4{ public static void main(String[] args){ //Map map = new HashMap(); Map map = new ConcurrentHashMap(); for(int i = 1; i { //向集合添加内容 map.put(key,UUID.randomUUID().toString().substring(0, 8)); //从集合中获取内容 System.out.print(map); }, String.valueOf(i)).start(); } }}