【周阳-JUC入门】【01】多线程复习、JUC引入


持续学习&持续更新中…

学习态度:守破离


JUC是什么

在这里插入图片描述

进程、线程是什么

  • 进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

  • 线程:通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。


  • 说人话:
  • 进程:后台运行的一个程序;进程与操作系统有关;比如:qq.exe、netmusic.exe、winword.exe
  • 线程:相当于轻量级的进程,依附在某个进程上,共享进程的系统内存资源;一般来讲,一个进程中会有多个线程在运行

  • 举例子:

  • 使用QQ,查看进程一定有一个QQ.exe的进程,我可以用qq和A文字聊天,和B视频聊天,给C传文件,给D发一段语言,QQ支持录入信息的搜索。

  • 大四的时候写论文,用word写论文,同时用QQ音乐放音乐,同时用QQ聊天,多个进程。

  • word如没有保存,停电关机,再通电后打开word可以恢复之前未保存的文档,word也会检查你的拼写,两个线程:容灾备份,语法检查

  • IDEA是一个进程,IDEA中有语法检查、编译class、保存文件等线程

并发、并行是什么

  • 并发:同一时刻多个线程在访问同一个资源,多个线程对一个点
  • 例子:小米9今天上午10点,限量抢购、春运抢票、电商秒杀…

  • 并行:多项工作一起执行,之后再汇总
  • 例子:泡方便面,电水壶烧水,一边撕调料倒入桶中;一边洗脚,一边看B站

线程状态

public enum State {
    /**
     * Thread state for a thread which has not yet started.
     */
    NEW, // 新建

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    RUNNABLE, // 可运行(就绪)

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED, // 阻塞,等待锁

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     */
    WAITING, // 等待,等待其它线程(不见不散,傻傻的等)

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING, // 等待,定时等待其它线程(过时不候,过了时间就撤了)

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    TERMINATED; // 执行完毕,已死亡
}

wait、sleep的区别

  • 功能都是让当前线程暂停,有什么区别?
  • wait放开手去睡,放开手里的锁
  • sleep握紧手去睡,醒了手里还有锁

卖票复习

/*
题目:3个售票员               售卖                     100张票

多线程编程的企业级套路+模板:
前提:在高内聚低耦合的情况下(高内聚:一个模块应尽量独立的干好自己该干的一件事;低耦合:模块与模块之间尽量减少依赖)
方法:   线程         操作(资源类对外暴露的接口)       资源类
 */

class Ticket { // 资源类
    private volatile int tickets = 100;
    public synchronized void saleTicket() { // 操作方法(资源类对外的接口)
        if (tickets > 0) {
            System.out.printf("%s卖了第 %d 张票,还剩 %d 张\n", Thread.currentThread().getName(), tickets--, tickets);
        }
    }
}

public class SaleTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket(); // 资源类(100张票)
        new Thread(new Runnable() { // 线程(售票员1)
            @Override
            public void run() {
                for (int i = 0; i < 40; i++) { // 操作(售卖)
                    ticket.saleTicket();
                }
            }
        }, "售票员1").start();
        new Thread(new Runnable() { // 线程(售票员2)
            @Override
            public void run() {
                for (int i = 0; i < 40; i++) { // 操作(售卖)
                    ticket.saleTicket();
                }
            }
        }, "售票员2").start();
        new Thread(new Runnable() { // 线程(售票员3)
            @Override
            public void run() {
                for (int i = 0; i < 40; i++) { // 操作(售卖)
                    ticket.saleTicket();
                }
            }
        }, "售票员3").start();
    }
}

引入Lambda、ReentrantLock:

/*
多线程套路:	线程		操作(资源类对外暴露的接口)		资源类
 */
class Ticket { // 资源类
    private Lock lock = new ReentrantLock(); // re:重复;entrant:进入;ReentrantLock:可重入锁
    private volatile int tickets = 100;

    public void saleTicket() { // 操作方法(资源类对外的接口)
        lock.lock();
        try {
            if (tickets > 0) {
                System.out.printf("%s卖了第 %d 张票,还剩 %d 张\n", Thread.currentThread().getName(), tickets--, tickets);
            }
        } finally {
            lock.unlock();
        }
    }
}

public class SaleTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> { for (int i = 0; i < 40; i++) ticket.saleTicket(); }, "售票员A").start();
        new Thread(() -> { for (int i = 0; i < 40; i++) ticket.saleTicket(); }, "售票员B").start();
        new Thread(() -> { for (int i = 0; i < 40; i++) ticket.saleTicket(); }, "售票员C").start();
    }
}

Lock、synchronized的区别

  1. 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
  2. synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
  3. synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
  4. 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
  5. synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
  6. Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

Lambda

  • Lambda 是一个匿名函数,我们可以把 Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

  • Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或剪头操作符。它将 Lambda 分为两个部分:左侧:指定了 Lambda 表达式需要的所有参数;右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能

/*
Lambda口诀:拷贝小括号    写死右箭头    落地大括号
 */

@FunctionalInterface
interface Foo {
    void fun();
    default void a() {
        System.out.println("a");
    }
    default void b() {
        System.out.println("b");
    }
    static void c() {
        System.out.println("c");
    }
    static void d() {
        System.out.println("d");
    }
}

//@FunctionalInterface
interface Consumer<V> {
    void fun(V v);
}

@FunctionalInterface
interface Producer<S, R> {
    R run(S s);
}

public class LambdaExpress {

    public static void main(String[] args) {
        Foo foo = () -> {
            System.out.println("hhhh");
        };
//        Foo foo = () -> System.out.println("aaaa");
//        foo.fun();
//        foo.a();
        foo = Foo::c;
        foo.a();
        Foo.c();

//        Consumer<String> consumer = v -> System.out.println(v + "----哈哈哈哈");
//        Consumer<String> consumer = System.out::println;
//        consumer.fun("okokok");

//        Producer<String,Integer> producer = (String str)->{return Integer.parseInt(str);};
//        Producer<String, Integer> producer = s -> Integer.parseInt(s) + 100;
//        System.out.println(producer.run("10") + 1);
    }

}

线程间通信

需要注意:防止虚假唤醒线程:if —> while

例1
/*
资源:一个变量:初始值为0
线程:
    A线程对该变量 +1
    B线程对该变量 -1
操作:两个线程交替执行10轮
 */

/*
    防止虚假唤醒
 */
class AirConditioner { // 资源类
    private int num; // 初始值为0

    public synchronized void increment() {
        while (num != 0) { // 判断
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 干活
        num++;
        System.out.println(Thread.currentThread().getName() + "\t" + num);
        notifyAll(); // 通知
    }

    public synchronized void decrement() {
        while (num == 0) { // 判断
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 干活
        num--;
        System.out.println(Thread.currentThread().getName() + "\t" + num);
        notifyAll(); // 通知
    }
}

/**
 * 口诀:
 * 1. 高内聚低耦合的情况下:线程操作资源类。(操作就是资源类对外暴露的接口)
 * 2. 线程间通信:判断、干活、通知
 */
public class Test01 {
    public static void main(String[] args) {
        final AirConditioner airConditioner = new AirConditioner();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                airConditioner.increment();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                airConditioner.decrement();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                airConditioner.increment();
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                airConditioner.decrement();
            }
        }, "D").start();
    }
}
/*
    防止虚假唤醒 + Lock + Condition
 */
class AirConditioner3 {
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    private int num;

    public void increment() {
        lock.lock();
        try {
            while (num != 0) {
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName() + "\t" + num);
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void decrement() {
        lock.lock();
        try {
            while (num != 1) {
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + "\t" + num);
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public class Test03 {
    public static void main(String[] args) {
        final AirConditioner3 airConditioner = new AirConditioner3();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                airConditioner.increment();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                airConditioner.decrement();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                airConditioner.increment();
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                airConditioner.decrement();
            }
        }, "D").start();
    }
}

例2
/*
多线程之间按照顺序调用:实现A ——> B ——> C
三个线程启动:要求如下:
AA打印5次,BB打印10次,CC打印15次

来10轮
 */

/*
线程操作资源类
判断、干活、通知
if—>while
标志位
 */

class ShareResource {
    // 标志位
    private volatile int sign = 1; // 1:A 2:B 3:C
    private final Lock lock = new ReentrantLock();
    private final Condition conditionA = lock.newCondition();
    private final Condition conditionB = lock.newCondition();
    private final Condition conditionC = lock.newCondition();

    public void print5() {
        lock.lock();
        try {
            while (sign != 1) { // 判断;需要防止虚假唤醒
                try {
                    conditionA.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 干活
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            // 通知
            sign = 2;
            conditionB.signal();
        } finally {
            lock.unlock();
        }
    }

    public void print10() {
        lock.lock();
        try {
            while (sign != 2) { // 判断;需要防止虚假唤醒
                try {
                    conditionB.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 干活
            for (int i = 1; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            // 通知
            sign = 3;
            conditionC.signal();
        } finally {
            lock.unlock();
        }
    }

    public void print15() {
        lock.lock();
        try {
            while (sign != 3) { // 判断;需要防止虚假唤醒
                try {
                    conditionC.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 干活
            for (int i = 1; i <= 15; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            // 通知
            sign = 1;
            conditionA.signal();
        } finally {
            lock.unlock();
        }
    }
}

public class ThreadOrderAccess {
    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareResource.print5();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareResource.print10();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareResource.print15();
            }
        }, "C").start();
    }
}

口诀总结

  • 多线程程序模板:线程、操作(资源类对外暴露的接口)、资源类
  • 线程间通信模板:判断、干活、通知
  • 线程间通信(交互:wait、notify)必须要防止线程的虚假唤醒:if —> while
  • 线程间通信时,要做到精确通知、顺序执行:Condition + 标志位(标志位可能要用volatile)

注意

  • Thread代表线程
  • Runnable只是一个接口而已

参考

周阳: JUC入门.


本文完,感谢您的关注支持!


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值