您的当前位置:首页>新品 > 正文

JUC是什么?java线程基础知识

来源:CSDN 时间:2023-03-30 09:09:41

JUC是什么?

JUC,即java.util.concurrent包的缩写,是java原生的并发包和一些常用的工具类。

线程基础知识


(资料图片)

线程和进程进程:计算机中运行中的程序,如QQ.exe等。 线程:进程中执行的具体的任务,如打字、自动保存等。 一个进程可以包含多个线程,一个进程至少有一个线程。Java程序至少有两个线程:GC线程和Main线程。 并发和并行并发:多个线程操作同一个资源并且交替执行的过程。 并行:多个线程同时执行,只有在多核CPU下才能完成。 使用多线程或者并发编程的目的:提高效率,让CPU一直工作,达到最高的处理性能。线程的状态线程有6种状态,我们可以从源码中查看具体是哪6种状态。

public enum State {    // java能够创建线程吗? 不能!    // 新建    NEW,    // 运行    RUNNABLE,    // 阻塞    BLOCKED,    // 等待    WAITING,    // 延时等待    TIMED_WAITING,    // 终止!    TERMINATED;}

很显然,线程的六种状态分别是:新建(NEW)、运行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITTING)、延时等待(TMED_WAITTING)、终止(TERMINATED)。wait和sleep的区别

类不同 wait是属于Object类的方法,sleep是Thread类的方法。在JUC编程中,线程休眠的实现代码是:

TimeUnit.SECONDS.sleep(3)

是否会释放资源 sleep会一直持有锁,不会释放锁,wait则会释放锁。使用范围不同 wait和notify是一组,一般在线程通信的时候使用。sleep是单独的方法,在任何地方都可以使用。是否需要捕获异常 sleep需要捕获中断异常,wait不需要。

Lock锁

传统方式一般采用synchronized关键字来加锁,如以下代码:

package com.coding.demo01;// 传统的 Synchronized// Synchronized 方法 和 Synchronized 块/* * 我们的学习是基于企业级的开发进行的; * 1、架构:高内聚,低耦合 * 2、套路:线程操作资源类,资源类是单独的 */public class Demo01 {    public static void main(String[] args) throws InterruptedException {        // 1、新建资源类        Ticket ticket = new Ticket();        // 2、线程操纵资源类        new Thread(new Runnable() {            public void run() {                for (int i = 1; i <=40; i++) {                    ticket.saleTicket();                }            }        },"A").start();        new Thread(new Runnable() {            public void run() {                for (int i = 1; i <=40; i++) {                    ticket.saleTicket();                }            }        },"B").start();        new Thread(new Runnable() {            public void run() {                for (int i = 1; i <=40; class="" private="" int="" number="30;">close=>    public synchronized void saleTicket(){        if (number>0){            System.out.println(Thread.currentThread().getName() + "卖出第"+(number--)+"票,还剩:"+number);        }    }}

现在,我们也可以使用Lock来加锁。

Lock lock=new ReentrantLock()

ReentrantLock,即可重入锁(相当于回家的时候只要开了大门的锁,卧室,厕所不需要解锁就能进入),其默认是非公平锁(不公平,后面的线程可以插队)。如以下代码:

package com.coding.demo01;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/* * JUC之后的操作 * Lock锁 + lambda表达式! */public class Demo02 {    public static void main(String[] args) {        // 1、新建资源类        Ticket2 ticket = new Ticket2();        // 2、线程操作资源类 , 所有的函数式接口都可以用 lambda表达式简化!        // lambda表达式 (参数)->{具体的代码}        new Thread(()->{for (int i = 1; i <= 40="" new="" -="">{for (int i = 1; i <= 40="" new="" -="">{for (int i = 1; i <= 40="" class="" reentrantlock="" :="" private="" lock="new" int="" number="" public="" void="" try="" if="">0){                System.out.println(Thread.currentThread().getName() + "卖出第"+(number--)+"票,还剩:"+number);            }        } catch (Exception e) {            e.printStackTrace();        } finally {            lock.unlock(); // 解锁        }    }}

synchronized和Lock的区别1.synchronized是一个关键字,Lock是一个对象。 2.synchronized无法尝试获取锁,Lock可以尝试获取锁并判断。 3.synchronized会自动释放锁(a线程执行完毕,b如果出现异常也会释放锁),Lock锁必须手动进行释放,不释放就会变成死锁。 4.使用synchronized时,如果线程a获得锁并阻塞,线程b会一直进行等待,使用Lock则可以尝试获取锁,失败了之后就放弃。

5.synchronized一定是非公平的,但Lock锁可以是公平的,需要通过参数进行设置。 6.代码量特别大时,一般使用Lock实现精准控制,synchronized适合代码量较小的同步问题。

生产者消费者问题

线程和线程之间本来是不能通信的,但有时我们需要线程之间进行协调操作。 比如有两个线程:A、B ,还有一个值初始为0,实现两个线程交替执行,对该变量 + 1,-1;交替10次。 先来看使用synchronized实现线程之间通信的版本,代码如下:

package com.coding.demo01;// Synchronized 版/*目的: 有两个线程:A  B ,还有一个值初始为0,       实现两个线程交替执行,对该变量 + 1,-1;交替10次 */public class Demo03 {    public static void main(String[] args) {        Data data = new Data();        // +1        new Thread(()->{            for (int i = 1; i <=10 try="" catch="" interruptedexception="" -1="" new="" -="">{            for (int i = 1; i <=10 ; i++) {                try {                    data.decrement();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        },"B").start();    }}// 资源类// 线程之间的通信: 判断  执行  通知class Data{    private int number = 0;    // +1    public synchronized void increment() throws InterruptedException {        if (number!=0){ // 判断是否需要等待            this.wait();        }        number++; // 执行        System.out.println(Thread.currentThread().getName()+""+number);        // 通知        this.notifyAll(); //唤醒所有线程    }    // -1    public synchronized void decrement() throws InterruptedException {        if (number==0){ // 判断是否需要等待            this.wait();        }        number--; // 执行        System.out.println(Thread.currentThread().getName()+""+number);        // 通知        this.notifyAll(); //唤醒所有线程    }}

那么问题来了,这四条线程可以实现交替吗?答案是不能!因为会产生虚假唤醒问题,jdk文档中对该问题也有说明。

需要特别注意的if和while的区别,当两个线程同时执行if判断,if只会判断一次,而while会对每一个线程都进行判断。显然,上面的if应该改为while,代码如下:

package com.coding.demo01;// Synchronized 版/*目的: 有两个线程:A  B ,还有一个值初始为0,       实现两个线程交替执行,对该变量 + 1,-1;交替10次       传统的 wait 和 notify方法不能实现精准唤醒通知! */public class Demo03 {    public static void main(String[] args) {        Data data = new Data();        // +1        new Thread(()->{            for (int i = 1; i <=10 try="" catch="" interruptedexception="" new="" -="">{            for (int i = 1; i <=10 try="" catch="" interruptedexception="" -1="" new="" -="">{            for (int i = 1; i <=10 try="" catch="" interruptedexception="" new="" -="">{            for (int i = 1; i <=10 ; i++) {                try {                    data.decrement();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        },"D").start();    }}// 资源类// 线程之间的通信: 判断  执行  通知class Data{    private int number = 0;    // +1    public synchronized void increment() throws InterruptedException {        while (number!=0){ // 判断是否需要等待            this.wait();        }        number++; // 执行        System.out.println(Thread.currentThread().getName()+""+number);        // 通知        this.notifyAll(); //唤醒所有线程    }    // -1    public synchronized void decrement() throws InterruptedException {        while (number==0){ // 判断是否需要等待            this.wait();        }        number--; // 执行        System.out.println(Thread.currentThread().getName()+""+number);        // 通知        this.notifyAll(); //唤醒所有线程    }}

问题又来了,从测试的结果可以看出,传统的 wait 和 notify方法不能实现精准唤醒通知。 这时我们就需要考虑使用JUC来实现了,先来看看JUC中的一个重要的接口Condition的文档说明。

我们使用Lock锁和Condition来实现精准唤醒线程,代码如下:

package com.coding.demo01;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/*实现线程交替执行!主要的实现目标:精准的唤醒线程!    三个线程:A B C    三个方法:A p5  B p10   C p15 依次循环 */public class Demo04 {    public static void main(String[] args) {        Data2 data = new Data2();        new Thread(()->{            for (int i = 1; i <= try="" catch="" interruptedexception="" new="" -="">{            for (int i = 1; i <= try="" catch="" interruptedexception="" new="" -="">{            for (int i = 1; i <= 10; i++) {                try {                    data.print15();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        },"C").start();    }}// 资源类class Data2{    private int number = 1; // 1A 2B  3C    private Lock lock = new ReentrantLock();    // 实现精准访问    private Condition condition1 = lock.newCondition();    private Condition condition2 = lock.newCondition();    private Condition condition3 = lock.newCondition();    public void print5() throws InterruptedException {        lock.lock();        try {            // 判断            while (number!=1){                condition1.await();            }            // 执行            for (int i = 1; i <= 5; i++) {                System.out.println(Thread.currentThread().getName() + "" + i);            }            // 通知第二个线程干活!            number = 2;            condition2.signal(); // 唤醒        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            lock.unlock(); // 一定要解锁        }    }    public void print10() throws InterruptedException {        lock.lock();        try {            // 判断            while (number!=2){                condition2.await();            }            // 执行            for (int i = 1; i <= 10; i++) {                System.out.println(Thread.currentThread().getName() + "" + i);            }            // 通知3干活            number = 3;            condition3.signal();        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }    public void print15() throws InterruptedException {        lock.lock();        try {            // 判断            while (number!=3){                condition3.await();            }            // 执行            for (int i = 1; i <= 15; i++) {                System.out.println(Thread.currentThread().getName() + "" + i);            }            // 通知 1 干活            number = 1;            condition1.signal();        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }}

测试结果说明,使用Lock锁很容易就解决上述问题,由此我们可以得到一个结论:一个新技术的出现,一定是为了替换一些旧的技术的!

锁对象的判断方法

1.被synchronized修饰的方法,锁的对象是方法的调用者,当两个方法调用的对象是同一个时,先调用的先执行。 2.没有被synchronized修饰的方法,不是同步方法,不受锁的影响。 3.只要方法被static修饰,不管是否同时被synchronized修饰,锁的对象就是Class模板对象,这个对象是全局唯一的。 4.synchronized锁的是调用的对象,static锁的是这个类的Class模板,这是两个不同的锁。

不安全的集合类

只要在并发环境下,List、Map、Set这些类都是不安全的。 List不安全的代码示例:

package com.coding.unsafe;import java.util.*;import java.util.concurrent.CopyOnWriteArrayList;/** * 故障现象:ConcurrentModificationException 并发修改异常 * 导致原因:add方法没有锁! * 解决方案: * 1、List list = new Vector<>(); //jdk1.0 就存在的!效率低 * 2、List list = Collections.synchronizedList(new ArrayList<>()); * 3、List list = new CopyOnWriteArrayList<>(); *  * 什么是 CopyOnWrite; 写入是复制 (思想 COW) * 多个调用者同时要相同的资源;这个有一个指针的概念。 * 读写分离的思想: */public class UnSafeList {    public static void main(String[] args) {//        List list = Arrays.asList("a", "b", "c");//        list.forEach(System.out::println);//        List list = new ArrayList<>();        List list = new CopyOnWriteArrayList<>();        for (int i = 1; i <= new="" -="">{                list.add(UUID.randomUUID().toString().substring(0,3));                System.out.println(list);            },String.valueOf(i)).start();        }    }}

如上述代码所示,解决List不安全问题的方法有两种:

List list = Collections.synchronizedList(new ArrayList<>());List list = new CopyOnWriteArrayList<>();

CopyOnWrite(COW),写入是复制,多个调用者同时要相同的资源,这是一种读写分离的思想,其源码如下:

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

Set不安全的代码示例:

package com.coding.unsafe;import java.util.Collections;import java.util.HashSet;import java.util.Set;import java.util.UUID;import java.util.concurrent.CopyOnWriteArraySet;// ConcurrentModificationExceptionpublic class UnSafeSet {    public static void main(String[] args) {        // HashSet 底层是什么 就是 HashMap        // add,就是 HashMap 的 key;        Set set = new HashSet<>();//        Set set = Collections.synchronizedSet(new HashSet<>());//        Set set = new CopyOnWriteArraySet();        for (int i = 1; i <=30 new="" -="">{                set.add(UUID.randomUUID().toString().substring(0,3));                System.out.println(set);            },String.valueOf(i)).start();        }    }}

如上述代码所示,解决Set不安全问题的方法有两种:

Set set = Collections.synchronizedSet(new HashSet<>());Set set = new CopyOnWriteArraySet();

Map不安全的代码示例:

package com.coding.unsafe;import java.util.HashMap;import java.util.Map;import java.util.UUID;import java.util.concurrent.ConcurrentHashMap;//ConcurrentModificationExceptionpublic class UnsafeMap {    public static void main(String[] args) {        // new HashMap<>() 工作中是这样用的吗? 不是        // 加载因子0.75f;,容量 16; 这两个值工作中不一定这样用!        // 优化性能!        // HashMap 底层数据结构,链表 + 红黑树        // = = = = = = =//        Map map = new HashMap<>();        Map map = new ConcurrentHashMap<>();        // 人生如程序,不是选择就是循环,时常的自我总结十分重要!        for (int i = 1; i <=30 new="" -="">{                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,3));                System.out.println(map);            },String.valueOf(i)).start();        }    }}

解决Map不安全问题的方法是使用ConcurrentHashMap来替代HashMap:

Map map = new ConcurrentHashMap<>();

综上所述,要解决一般集合的线程不安全的问题,核心思路就是使用JUC并发包下面的并发安全的集合去替代这些不安全的集合。

标签:

最新新闻:

新闻放送
Top