重温JUC(一)

148 阅读2分钟

线程和进程的基本概念

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天,点击查看活动详情

什么是JUC

在java中,线程部分是一个重点,本片说的JUC也是关于现成的,juc就是java.util.concurrent工具包的简称,这是个处理线程的工具包。

什么是进程,线程

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

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

简单理解,一个360软件就是一个进程,你在里面做的很多操作就是一个个线程。

wait/sleep的区别

  • sleep是Thread的静态方法,wait是Object方法,任何实例对象都可以调用
  • sleep不会释放锁,他也不需要占用锁,wait会是释放锁,但调用前提是当前线程占有锁
  • 他们都可以被interrupt方法中断

并发和并行

并发: 同一时刻多个线程访问同一个资源。

例如:抢票,秒杀

并行: 多项工作一起执行,之后汇总

例如:泡泡面的时候,烧水,撕调料

管程

管程是一种同步机制,保证了同一时间内,只有一个线程访问被保护数据或者代码

在操作系统中叫Monitor监视器,在java中叫锁

说白了,加锁和解锁本质就是持有管程对象和非持有管程对象

用户线程和守护线程

用户线程:自定义的线程,比如自己new的线程

守护线程:比如垃圾回收,它运行在后台

用户线程和守护线程特点:

  • 主线程结束,用户线程如果还在运行,那么jvm依然存活
  • 如果没有用户线程,都是守护线程,那么jvm结束

我们来写代码测试一下

image-20221026130608556

image-20221026130644914

我们可以看到,主线程虽然结束了,但是用户线程还是在运行,并且用户线程的isDaemon方法是false说明这是个用户线程。

接下来,我们把用户线程变成守护线程试试

image-20221026130813891

image-20221026130820748

程序立刻就结束了

Lock接口

复习Sychronized

我们通过一个案例去进行复习吧

public class SaleTicket {
    public static void main(String[] args) {
​
        for (int i = 0; i < 13; i++) {
            new Thread(new sale(),"asd").start();
        }
​
    }
}
​
class sale implements Runnable{
​
    public static Shop shop = new Shop();
​
    @Override
    public void run() {
        shop.saleTicket();
    }
}
​
class Shop{
​
    public static int ticket = 10;
​
    public Shop() {
    }
​
    public static int getTicket() {
        return ticket;
    }
​
    public synchronized  void saleTicket() {
        if(ticket<=0){
            System.out.println("票卖完了");
            return;
        }
        ticket = ticket -1;
        System.out.println("票的数量是" + ticket);
    }
}

image-20221026132629412

总体分为两个步骤:

  • 创建资源类,创建属性和操作方法
  • 创建多线程调用资源类的方法

注意:我们这里通过sychronized关键字锁住了操作资源的方法,让线程安全

但是我们都知道sychronizied关键字是一种自动加锁解锁的逻辑。

下面我们来复习一下Lock接口

Lock锁实现提供了比sychronzed的同步方法和语句更广泛的锁操作。他们允许更灵活的结构,可能具有非常不同的属性。Lock比sychronized更多的功能

Lock和Sychronized的区别

  • Lock不是java语言内置的,Sychronized则是java语言的关键字,是内置特性。Lock是一个类,通过这个类可以实现同步访问
  • Lock必须要用户去手动释放锁,如果没有手动释放,就有可能出现死锁

下面进行实战

我们new了一个ReentrantLock,可重入锁来进行资源的加锁和手动释放锁

package org.example.lock;
​
import java.util.concurrent.locks.ReentrantLock;
​
class LTicket{
    public static int num = 30;
​
    private final ReentrantLock lock = new ReentrantLock();
​
    public void sell(){
​
        lock.lock();
        try{
            if(num>0){
                System.out.println(Thread.currentThread().getName() +"卖到"+ num--);
            }
        }finally {
            lock.unlock();
        }
​
​
    }
​
}
​
public class LSaleTicket {
    public static void main(String[] args) {
        LTicket lTicket = new LTicket();
        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                lTicket.sell();
            }
        },"AA").start();
        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                lTicket.sell();
            }
        },"BB").start();
        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                lTicket.sell();
            }
        },"CC").start();
    }
}

image-20221026134330663

这个时候有同学会好奇了,BB去哪了?

我们在执行完这个方法以后

image-20221026134745047

线程就立刻被创建了吗?

其实不是的!我们来看看start方法的源码

image-20221026134829859

它先调用了start0方法

image-20221026134848346

start0上被加了native关键字!

image-20221026134935513

这个方法是其它语言去写的!

其实它是否被创建由操作系统决定!所以B就会慢一些创建,自然抢不到资源

线程间的通信

Sychronized实现

案例:如果一个类里的一个数值为0,那么一个线程就让他变成1,如果这个数值是1,那就让线程把它变成0。

思路:让一个线程负责变1,一个线程负责变0.如果第一个线程发现是1,则休眠。等待第二个线程变0后将它唤醒

class Share{
    private int number = 0;
​
    public synchronized void incr() 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--;
        System.out.println(Thread.currentThread().getName() + "::" + number);
        this.notifyAll();
    }
}
​
public class TreadDemo1 {
    public static void main(String[] args) {
        Share share = new Share();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
​
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"AA").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"BB").start();
    }
}

image-20221026145736899

注意,我们这里用while做判断是为了防止虚假唤醒问题。

因为如果线程一多,由于线程在哪里等待就在哪里会被唤醒。所以如果用if的话,它在等待中被唤醒了,不一定是值为0或者1,所以需要它在循环中一直等才可

可重入锁实现

class Share2{
    private int number = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
​
    public void incr() throws InterruptedException {
        lock.lock();
        try {
            while (number!=0){
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "::" +number);
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
​
    public void decr() throws InterruptedException {
        lock.lock();
        try {
            while (number!=1){
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "::" +number);
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
}
public class ThreadDemo2 {
    public static void main(String[] args) {
        Share2 share2 = new Share2();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share2.incr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"AA").start();
​
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share2.incr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"BB").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share2.decr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"CC").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share2.decr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"DD").start();
    }
}

image-20221026152205449