> 文档中心 > JUC基础知识点(一)

JUC基础知识点(一)


1、JUC概述

1.1 什么是JUC

在Java中,线程部分是一个重点,本篇文章说的JUC是关于线程的。JUC就是java.util.concurrent工具包的简称。这是一个处理线程的工具包,JDK1.5开始出现的

1.2 线程和进程的概念

  1. 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。

  2. 线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

  3. 总结:

    1. 进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位

    2. 线程:系统分配处理器时间资源的基本单元,或者说进程内独立执行的一个单元执行流。线程——程序执行的最小单位

1.3 线程的状态

线程状态枚举类(Thread.State): NEW(新建)、RUNNABLE(准备就绪)、BLOCKED(阻塞)、WAITING(不见不散)、TIMED_WAITING(过时不候)、TERMINATED(终结)

wait和sleep的区别

  1. sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用

  2. sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized中)

  3. 它们都可以被interrupted方法中断

并发与并行

  1. 并发:多个线程在访问同一个资源(线程频繁切换),多个线程对一个点

    1. 例子:春运抢票、电商秒杀,一个医生给多个病人轮流看病

  2. 并行:多项工作一起执行(同时),之后再汇总

    1. 多个医生同时给病人看病

管程: Monitor监视器 就是我们所说的锁 是一种同步机制,保证同一个时间,只有一个线程访问被保护数据或者代码。JVM同步基于进入和退出,使用管程对象实现的。

用户线程和守护线程

  1. 用户线程:自定义线程 主线程结束了,用户线程还在运行,jvm存活

  2. 守护线程:比如垃圾回收 没有用户线程了,都是守护线程,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中的关键字,是一种同步锁。它修饰的对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象

  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法作用的对象是调用这个方法的对象

  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象

  4. 修改一个,其作用的范围是synchronized后面括号括起来的部分,作用主要的对象是这个类的所有对象

2.2 什么是Lock接口

Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的Condition对象

所有已知实现类:ReentrantLock、ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock

Lock与Synchronized区别:

  1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现

  2. synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock()时需要在finally块中释放锁

  3. Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断

  4. 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到

  5. Lock可以提高多个线程进行操作的效率

  6. Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;

  7. Lock则必须要用户去手动释放锁,如果没有手动释放锁,就有可能出现死锁现象

2.3 创建线程的多种方式

  1. 继承Thread类创建线程

  2. 实现Runnable接口创建线程

  3. 使用Callable和Future创建线程

  4. 使用线程池例如用Executor框架

多线程编程步骤:

  1. 第一步:创建资源类,在资源类创建属性和操作方法(例子:空调)

  2. 第二步:创建多个线程,调用资源类的操作方法

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();        }    }}