Java并发编程--基础(三)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/LuuvyJune/article/details/86507529

阅读《Java高并发编程详解》后的笔记。

线程间通信

同步阻塞和异步非阻塞

同步方式提交业务请求:

缺陷:

  • 客户端等待时间过长:提交Event时间+接受Event创建thread时长+业务处理时长+返回结果时长
  • 系统整体吞吐量不高
  • 一个线程处理一个Event,频繁创建开启与销毁,增加额外开销
  • 业务高峰时,大量业务处理线程阻塞导致频繁CPU上下文切换,降低系统性能。

异步方式提交请求:

客户端提交event后得到一个相应的工单并且立即返回,Event则会被放置在Event队列中。服务端有若干工作线程,不断从event队列中获取任务并且进行异步处理,最后将处理结果保存在一个结果集中,若是客户端想要活的处理结果,可根据工单号再次查询。

相比同步阻塞方式:

  • 客户端不用等到处理结果处理结束后才能返回,提高系统吞吐量和并发量
  • 服务端线程的线程数量在一定可控范围内不会导致太多CPU上下文切换带来的额外开销
  • 服务端线程可重复利用,减少不断创建线程带来的资源浪费

缺陷:客户端想要得到结果还需要再次调用接口方法进行查询(可利用异步回调接口的方式解决--待续)。

wait()与notify()

这两个方法都是Object方法,JDK中每一个类都有这两个方法。

wait方法:

  • wait(long timeout)导致当前线程进入阻塞状态,直到有其他线程调用notify/notifyAll才能将其唤醒,或者阻塞时间达到了timeout而自动唤醒。
  • wait方法必须在同步方法中使用。
  • 如果有线程执行了某个object的wait方法之后,它会放弃对该对象monitor的所有权并且进入与该对象关联的wait set中,其他线程有机会继续争抢该monitor的所有权。

notify方法:

  • 唤醒单个正在执行该对象wait方法的线程,若当前线程没有执行该对象的wait方法,则会被忽略。
  • 被唤醒的线程需要重新获取对该对象关联monitor的lock才能执行。

注意事项:

  • 每个对象的monitor都有一个与之关联的wait set。
  • 必须在同步方法中使用wait和notify方法,执行前提是必须持有同步方法的monitor的所有权。否则抛出IllegalMonitorSateException。
  • 用哪个对象的monitor同步,只能用哪个对象进行wait和notify。

在虚拟机的规范中存在一个wait set(线程休息室)的概念,具体数据结构没有明确定义。线程调用了某个对象的wait方法之后都会被划入到该对象monitor关联的wait set中,并释放monitor的所有权。

wait与sleep区别:

同:(1)都会使得线程进入Blocked

       (2)都是可中断方法,中断后都会收到异常。

异:(1)wait方法是Object的,sleep方式是Thread的。

       (2)wait方法必须在同步方法中执行,sleep不需要。

       (3)线程在同步方法中执行wait方法,会释放monitor的锁,sleep不会。

       (4)wait方法在没有指定时间时,被其他线程中断才可以退出阻塞,sleep方法短暂休眠之后自动退出阻塞。

生产者与消费者

notify每次只能唤醒由于调用了wait方法而阻塞的线程中一个线程,而notifyAll方法可以同时唤醒全部的阻塞线程,同样被唤醒的线程需要继续争抢monitor 的锁。

生产者和消费者是多线程间通信的最好的例子:

import java.util.LinkedList;

import static java.lang.Thread.currentThread;

public class EventQueue {

    private final int max;

    static class Event{}

    private final LinkedList<Event> eventQueue = new LinkedList<Event>();

    private final static int DEFAULT_MAX_EVENT = 10;

    public EventQueue(){

        this(DEFAULT_MAX_EVENT);
    }

    public EventQueue(int max){
        this.max = max;
    }

    public void offer(Event event){
        synchronized (eventQueue){
            while(eventQueue.size() > max){
                try {
                    System.out.println(currentThread().getName()+" the queue is full");
                    eventQueue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println(currentThread().getName()+" the new event  is submitted");
            eventQueue.addLast(event);
            eventQueue.notifyAll();
        }
    }

    public Event take(){
        synchronized (eventQueue){
            while(eventQueue.isEmpty()){
                try {
                    System.out.println(currentThread().getName()+" the queue is empty");
                    eventQueue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            Event event = eventQueue.removeFirst();
            this.eventQueue.notifyAll();
            System.out.println(currentThread().getName()+" the event"+event+"is handled");
            return event;
        }
    }
}

自定义显式锁BooleanLock

synchronized关键字提供了一种排他式的数据同步机制,某个线程在获取monitor lock的时候可能会被阻塞。这种阻塞有两个明显的缺陷:无法控制阻塞时长,阻塞不可中断。

自定义一个BooleanLock,具备synchronized关键字所有功能的同时又具备可中断和lock超时的功能:

import java.util.List;
import java.util.concurrent.TimeoutException;

/**
 * Lock接口
 */
public interface Lock {

    //永远阻塞,除非获取到了锁,可被中断
    void lock() throws  InterruptedException;

    //可被中断,增加超时
    void lock(long mills) throws InterruptedException, TimeoutException;

    //释放锁
    void unlock();

    //获取当前有哪些线程被阻塞
    List<Thread> getBlockedThreads();
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeoutException;

import static java.lang.System.currentTimeMillis;
import static java.lang.Thread.currentThread;

public class BooleanLock implements Lock {

    //当前拥有锁的线程
    private Thread currentThread;

    //false代表当前没有任何线程获得锁或者该锁已被释放,true代表currentThread获取了锁
    private boolean locked = false;

    //存储哪些线程在获取当前线程时进入阻塞状态
    private final List<Thread> blockedList = new ArrayList<Thread>();

    @Override
    public void lock() throws InterruptedException {

        synchronized (this){
            while(locked){
                //暂存当前线程
                final Thread tempThread = currentThread();
                try {
                    //如果当前锁已经被某个线程获得,则将该线程加入阻塞队列,并使当前线程wait释放对this monitor的所有权
                    if(!blockedList.contains(tempThread))
                        blockedList.add(tempThread);
                    this.wait();
                } catch (InterruptedException e) {
                    //如果当前线程在wait时被打断,从blockedList中将其移除,避免内存泄露
                    blockedList.remove(tempThread);
                    //继续抛出中断异常
                    throw e;
                }
            }

            //如果当前锁没有被其他线程获得,从阻塞队列中删除自己(没有进入阻塞队列删除不会有影响)
            blockedList.remove(currentThread());
            this.locked = true;
            //记录获取锁的线程
            this.currentThread = currentThread();
        }
    }

    @Override
    public void lock(long mills) throws InterruptedException, TimeoutException {

        synchronized (this){
            //mills如果不合法,调用lock(),抛出参数非法异常
            if(mills <= 0){
                this.lock();
            }else{
                long remainingMills = mills;
                long endMills = currentTimeMillis() + remainingMills;
                while(locked){
                    if(remainingMills <= 0)
                        //说明当前线程是被其他线程唤醒或者在指定的wait时间到了还没有获取锁,超时
                        throw new TimeoutException("can not get the lock during" + mills + "ms");
                    if(!blockedList.contains(currentThread()))
                        blockedList.add(currentThread());
                    this.wait(remainingMills);
                    //重新计算remainingMills时间
                    remainingMills = endMills - currentTimeMillis();
                }
                //获得锁,并从block列表中删除该线程,指定获得该锁的线程就是当前线程
                blockedList.remove(currentThread());
                this.locked = true;
                this.currentThread = currentThread();
            }
        }
    }

    @Override
    public void unlock() {
        synchronized (this){
            //判断当前线程是否是获得锁的那个线程,只有加了锁的线程才有资格解锁
            if(currentThread == currentThread()){
                this.locked = false;
                Optional.of(currentThread().getName() + " release the lock.").ifPresent(System.out::println);
                //通知wait set中的线程,可以再次尝试抢锁
                this.notifyAll();
            }
        }
    }

    @Override
    public List<Thread> getBlockedThreads() {
        return Collections.unmodifiableList(blockedList);
    }
}

  测试一:多个线程通过lock()争抢锁

import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

import static java.lang.Thread.currentThread;
import static java.util.concurrent.ThreadLocalRandom.current;

public class BooleanLockTest {

    //定义BooleanLock
    private final Lock lock = new BooleanLock();

    /**
     * try..finally 确保lock每次都能被正确释放
     */
    public void synMethod(){
        try {
            //加锁
            lock.lock();
            int randomInt = current().nextInt(10);
            System.out.println(currentThread() + "get the lock");
            TimeUnit.SECONDS.sleep(randomInt);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //释放锁
            lock.unlock();
        }
    }

    public  static void main(String args[]){
        BooleanLockTest blt = new BooleanLockTest();
        //定义一个线程并启动
        IntStream.range(0,10).mapToObj(i->new Thread(blt::synMethod)).forEach(Thread::start);
    }
}

  测试二:可中断被阻塞的线程

public class BooleanLockTest {

    //定义BooleanLock
    private final Lock lock = new BooleanLock();

    /**
     * try..finally 确保lock每次都能被正确释放
     */
    public void synMethod(){
        try {
            //加锁
            lock.lock();
            int randomInt = current().nextInt(10);
            System.out.println(currentThread() + "get the lock");
            TimeUnit.SECONDS.sleep(randomInt);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //释放锁
            lock.unlock();
        }
    }

    public  static void main(String args[]) throws InterruptedException{
        BooleanLockTest blt = new BooleanLockTest();
        new Thread(blt::synMethod,"T1").start();
        TimeUnit.MILLISECONDS.sleep(2);
        Thread t2 = new Thread(blt::synMethod,"T2");
        t2.start();
        TimeUnit.MILLISECONDS.sleep(10);
        t2.interrupt();
    }
} 

  测试三:阻塞的线程可超时

public class BooleanLockTest {

    //定义BooleanLock
    private final Lock lock = new BooleanLock();

    /**
     * try..finally 确保lock每次都能被正确释放
     */
    public void synMethod(){
        try {
            //加锁
            lock.lock();
            int randomInt = current().nextInt(10);
            System.out.println(currentThread() + "get the lock");
            TimeUnit.SECONDS.sleep(randomInt);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //释放锁
            lock.unlock();
        }
    }

    public void syncMethodTimeoutable(){
        try {
            lock.lock(1000);
            System.out.println(currentThread() + "get the lock");
            int randomInt = current().nextInt(10);
            TimeUnit.SECONDS.sleep(randomInt);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }


     public  static void main(String args[]) throws InterruptedException{
        BooleanLockTest blt = new BooleanLockTest();
        new Thread(blt::synMethod,"T1").start();
        TimeUnit.MILLISECONDS.sleep(2);
        Thread t2 = new Thread(blt::syncMethodTimeoutable,"T2");
        t2.start();
        TimeUnit.MILLISECONDS.sleep(10);
    }
}

猜你喜欢

转载自blog.csdn.net/LuuvyJune/article/details/86507529