(十一)java线程通讯、JUC显示锁、生产和消费

1. 线程通讯

在Java中,线程通信是指多个线程之间通过特定的机制来实现信息的传递和协调工作的过程。

常见的线程通信机制有以下几种:

  • 共享变量:多个线程共享同一个变量,通过对该变量的读写操作来进行通信。比如使用volatile关键字、synchronized关键字或Lock接口等来保证对共享变量的可见性和原子性操作。
  • 等待/通知机制:通过调用对象的wait()方法使线程进入等待状态,同时释放锁,当满足某个条件时,通过调用对象的notify()或notifyAll()方法来唤醒等待的线程。这种机制通常结合synchronized关键字使用。
  • 信号量(Semaphore):可以控制同时访问某资源的线程数,通过acquire()方法获取许可证,release()方法释放许可证,限制并发线程数。
  • 栅栏(CyclicBarrier)和倒计数器(CountDownLatch):在多个线程需要等待彼此完成后再继续执行时使用。栅栏允许一组线程相互等待达到某个状态,然后同时继续执行;倒计数器允许一个或多个线程等待其他线程完成某个任务后再继续执行。
  • 阻塞队列(BlockingQueue):用于在多个线程之间进行元素的传递。一个线程可以将元素放入队列中,而另一个线程可以从队列中获取元素,如果队列为空或已满,则线程可能会被阻塞等待。
  • 线程通信是实现多个线程之间协同工作和数据共享的重要手段,可以确保线程之间的同步和有序执行,有效避免竞态条件和线程安全问题。

2. 等待唤醒机制

在这里插入图片描述
synchronized(内置锁)

  • EntryList:双向队列:等待锁的多列
  • Owner:正在执行的线程
  • WaitSet:双向队列
    • locko.wait:wait方法一定在synchronize代码块中,把当前线程放到waitset中
    • locko.notify:把waitset队列中的一个随机线程放到entrylist中
    • locko.notifyAll:把waitset队列中的所有线程都放到entrylist中

所有对象都会关联一个c++结构,这个结构ObjectMonier 如下:

Monitor 结构体
ObjectMonitor::ObjectMonitor() {
    _header = NULL;
    _count = 0;
    _waiters = 0,
    
线程的重入次数
    _recursions = 0;
    _object = NULL;
    
标识拥有该 monitor 的线程
    _owner = NULL;
    
等待线程组成的双向循环链表
    _WaitSet = NULL;
    _WaitSetLock = 0 ;
    _Responsible = NULL ;
    _succ = NULL ;
    
多线程竞争锁进入时的单向链表
    cxq = NULL ;
    FreeNext = NULL ;
    
_owner 从该双向循环链表中唤醒线程节点
    _EntryList = NULL ;
    _SpinFreq = 0 ;
    _SpinClock = 0 ;
    OwnerIsThread = 0 ;
}

3. 生产者消费者

  • 包子铺线程负责生产包子
  • 消费者消费包子
  • 包子对象
    请添加图片描述
探究同步通讯 wait 和 notify
public class Test1 {
    public static void main(String[] args) {
        Bun bun = new Bun();
        Customer customer = new Customer(bun);
        BunHouse bunHouse = new BunHouse(bun);
        Thread thread1 = new Thread(customer);
        Thread thread2 = new Thread(bunHouse);
        thread1.start();
        thread2.start();
    }
}

public class Bun {//包子
    private String skin;
    private String filling;
    private boolean flag = false;
    public String getSkin() {
        return skin;
    }
    public void setSkin(String skin) {
        this.skin = skin;
    }
    public String getFilling() {
        return filling;
    }
    public void setFilling(String filling) {
        this.filling = filling;
    }
    public boolean isFlag() {
        return flag;
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    @Override
    public String toString() {
        return "Bun{" +
                "skin='" + skin + '\'' +
                ", filling='" + filling + '\'' +
                ", flag=" + flag +
                '}';
    }
}

public class BunHouse implements Runnable {包子铺
    private Bun bun;
    public BunHouse() {
    }
    public BunHouse(Bun bun) {
        this.bun = bun;
    }
    @Override
    public void run() {
        int count = 0;
        while (true) {
            synchronized (bun) {
                if (bun.isFlag() == true) {
                    try {
                        bun.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                if (count % 2 == 0) {
                    bun.setSkin("米皮");
                    bun.setFilling("韭菜鸡蛋");
                } else {
                    bun.setSkin("面皮");
                    bun.setFilling("猪肉大葱");
                }
                System.out.println("包子已经制作完毕," + bun);
                count++;
                bun.setFlag(true);
                bun.notify();
            }
        }
    }
}

public class Customer implements Runnable {顾客
    private Bun bun;
    public Customer() {
    }
    public Customer(Bun bun) {
        this.bun = bun;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (bun) {
                if (bun.isFlag() == false) {
                    try {
                        bun.wait();Customer抢到CPU,并进行if()语句,进入等待即(waitset区域)
                        此时只有相同的锁的线程才能继续,即BunHouse类。
                        BunHouse制作包子后,执行bun.notify()唤醒Customer对象并与Customer一同抢CPU的执行权
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                System.out.println("客人正在吃包子" + bun);
                bun.setFlag(false);
                bun.notify();
            }
        }
    }
}

注意wait()notigy()方法只能在synchronized锁下使用

4. JUC显示锁

  • 内置锁:synchronized
  • 显示锁:JUC锁

JDK5版本引入了java.util.concurrent并发包。简称JUC包,JUC出自并发大师Doug Lea之手,Doug Lea对Java并发性能的提升做出了巨大贡献。
JUC显示锁因为是存粹Java语言实现,避免了进程在内核态和用户态之间来回切换所导致的性能低的问题。

4.1 Lock接口

主要抽象方法如下

方法描述
void lock()抢锁。 成功则向下运行,失败则阻塞抢锁线程
void lockInterruptibly() throws InterruptedException可中断抢锁,当前线程在抢锁的过程中可以响应中断信号
boolean tryLock()尝试抢锁, 线程为非阻塞模式,在调用 tryLock 方法后立即返回。抢锁成功返回 true, 抢锁失败返回 false
boolean tryLock(long time, TimeUnit unit)throws InterruptedException限时抢锁,到达超时时间返回 false。并且此限时抢锁方法也可以响应中断信号
void unlock();释放锁
Condition newCondition();获取与显式锁绑定的 Condition 对象,用于“等待-通知”方式的线程间通信

从 Lock 提供的接口方法可以看出, 显式锁至少比 Java 内置锁多了以下优势:

  1. 可中断获取锁
    使用 synchronized 关键字获取锁的时候,如果线程没有获取到被阻塞,阻塞期间该线程是不响应中断信号(interrupt)的;而使用 Lock.lockInterruptibly( )方法获取锁时,如果线程被中断,线程将抛出中断异常。
  2. 可非阻塞获取锁
    使用 synchronized 关键字获取锁时,如果没有成功获取,线程只有被阻塞;而使用Lock.tryLock( )方法获取锁时,如果没有获取成功,线程也不会被阻塞,而是直接返回 false。
  3. 可限时抢锁
    使用 Lock.tryLock(long time, TimeUnit unit)方法, 显式锁可以设置限定抢占锁的超时时间。而在使用 synchronized 关键字获取锁时,如果不能抢到锁,线程只能无限制阻塞。

5. ReentrantLock(可重入锁)

ReentrantLock锁的使用
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockTest implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;
    @Override
    public void run() {
        lock.lock();//同一个线程可以多次获取同一个锁
        lock.lock();
        try {
            for (int j = 0; j < 100000000; j++) {
                i++;
//                System.out.println(Thread.currentThread().getName()+"\t" + i);
            }
        } finally {
            lock.unlock();//几次获取就需要几次释放
            lock.unlock();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ReentrantLockTest r1 = new ReentrantLockTest();
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r1);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("真实值" + i);
    }
}

6. (ReentrantReadWriteLock)读写锁

读写锁的内部包含了两把锁:一把是读锁,是一种共享锁;一把是写锁,是一种独占锁。读写锁适用于读多写少的并发情况。

  • 读、读共享(共享:两个线程可以同时进行)
  • 读、写互斥(互斥:两个线程必须串行进行)
  • 写、写互斥
探究读写锁
代码较难,建议多次观看
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {
    static CountDownLatch countDownLatch = new CountDownLatch(20);
    private static Lock lock = new ReentrantLock();
    private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private static Lock readLock = readWriteLock.readLock();
    private static Lock writeLock = readWriteLock.writeLock();
    private int value;
    public int handleRead(Lock lock) throws InterruptedException {
        lock.lock();
        try {
            Thread.sleep(1000);
            System.out.println("read success");
            countDownLatch.countDown();
            return value;
        } finally {
            lock.unlock();
        }
    }
    public void handleWrite(Lock lock,int index) throws InterruptedException {
        lock.lock();
        try {
            Thread.sleep(1000);
            value = index;
            System.out.println("write success");
            countDownLatch.countDown();
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        final ReadWriteLockDemo demo = new ReadWriteLockDemo();
        Runnable readRunnable = new Runnable() {
            @Override
            public void run() {
                try {
                    demo.handleRead(readLock);
//                    demo.handleRead(lock);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Runnable writeRunnable = new Runnable() {
            @Override
            public void run() {
                try {
                    demo.handleWrite(writeLock,new Random().nextInt());
//                    demo.handleWrite(lock,new Random().nextInt());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0; i < 2; i++) {
            new Thread(writeRunnable).start();
        }
        long t1 = System.currentTimeMillis();
        for (int i = 0; i < 18; i++) {
            new Thread(readRunnable).start();
        }
        countDownLatch.await();
        long t2 = System.currentTimeMillis();
        System.out.println(t2 - t1);
    }
}
运行结果
read success
read success
read success
read success
read success
read success
read success
read success
read success
read success
read success
read success
read success
read success
read success
read success
read success
read success
write success
write success
3033

7. Semaphore

Semaphore是一个许可管理器,可以用来控制在同一时刻访问共享资源的线程数量,Semaphore维护了一组虚拟许可,其数量可以通过构造函数的参数指定,线程在访问共享资源前,必须使用Semaphore的acquire方法获取许可,如果许可数量为0,该线程则一直阻塞,线程访问完成资源后,必须使用Semaphore的release方法去释放许可。

  • Semaphore(permits):构造一个 Semaphore 实例,初始化其管理的许可数量为 permits 参数值。
  • acquire(1):尝试获取 1 个许可。而当前线程被中断,则会抛出 InterruptedException 异常并终止阻塞
  • release(1):释放 1 个可用的许可

假设有 10 个人在银行办理业务,只有 2 个工作窗口,使用 Semaphore 模拟银行排队,代码如下:

共享锁Semaphore
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
public class SemaphoreTest {
    public static void main(String[] args) {
        final CountDownLatch countDownLatch = new CountDownLatch(10);
        final Semaphore semaphore = new Semaphore(2);//给定两个许可,当许可为0时,阻塞。
        AtomicInteger index = new AtomicInteger(0);//原子加一过程
        //创建Runnable可执行实例
        Runnable r = new Runnable() {
            @Override
            public void run() {
                try {
                    //抢占一个许可
                    semaphore.acquire(1);//一次获取一个许可,一共能有两个线程同时运行
                    //模拟业务操作,处理排队业务
                    SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");//时间戳,后面要学
                    System.out.println(sdf.format(new Date()) + ", 受理处理中...,服务号: " + index.incrementAndGet());
                    Thread.sleep(1000);
                    semaphore.release(1);//释放一个许可,建议获取多少许可,释放多少许可。
                } catch (Exception e) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            }
        };
        //创建10个线程
        Thread[] tArray = new Thread[10];
        for (int i = 0; i < 10; i++) {
            tArray[i] = new Thread(r, "线程" + i);
        }
        for (int i = 0; i < 10; i++) {
            tArray[i].start();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值