JAVA多线程——并发

本文相关的好文推荐:


线程状态图

图来自Java线程的6种状态及切换(透彻讲解)

初始、就绪、运行、等待、阻塞(synchronized)、终止。

Thread.sleep(millis)一定时间后自动恢复执行,不释放对象锁
Object.wait()直接释放锁,一直等待,知道被notify()/notifyAll()唤醒。
Thread1.join()表示运行的线程阻塞自己,等待Thread1执行完,再执行下面语句。
yield()让出cpu,不释放对象锁,进入就绪态,随时可能再次运行。

只有运行态和就绪态之间涉及到CPU资源的切换。(即yield())

Thread开启的3种方式
Thread、Runnable、Callable

  • Thread
    最简单的一种,直接继承Thread,重写run方法。主类中new ThreadName().start();则进去就绪态。
  • Runnable

    • 实现Runnable接口,重写run。主类中new RunnableName().start();
    • 实现Runnable接口,重写run。主类中new Thread(new Runnable()).start();
    • Thread类还有其他重载方法,如传入ThreadGoup,Runnable初始执行接口等等,以上两个最常用。
    Thread类也是实现了Runnable接口,实际上都是调用的Runnable接口的run()方法。
  • Callable
    实现Callable接口,重写call()方法。创建FutureTask对象new FutureTask<>(CallableName);再new Thread(FutureTak).start();即可。FutureTask间接实现了Runnable和Future,所以实际调用的还是Thread(Runnable)构造方法。

    Callable接口只有一个方法V call();
    Future接口包含cancel、isCancelled、isDone、get方法,用户获取返回值。(Futuretask对Future接口中的方法进行了重写)

Executor管理线程————线程池

  • newFixedThreadPool固定大小的线程池
  • newCachedThreadPool可扩展的
  • newScheduledThreadPool可调度的,运行一次,重复运行等
  • newSingleThreadPool单线程池
  • newWorkStealingPool窃取别的线程池的线程

使用:

ExecutorService executor = Executors.newFixedThreadPool(3);
Future future = excutor.submit(Callable);//加入线程池
future.get();//获取返回值

好文推荐:自定义线程池


后台线程deamon
定义:程序运行时在后台提供通用服务的线程,这种线程并不属于不可或缺的部分。后台线程运行的前提是必须有至少一个非后台线程运行。

Thread.setDeamon(ture);必须在start()前设置。

java.util.concurrent包

  • CountDownLatch
    同步多个任务,可以设置一个初值,任何在这个对象上调用wait()都将被阻塞,知道值变成0,每个任务执行完调用countDown都使值减1。

    初始值不能重置,只能一直减下去。CyclicBarrier可以重置。
  • CyclicBarrier
    用于并发任务等待(类似join),如:idm多线程下载文件,最后进行文件整理。可以重置初始值。
  • DelayedQueue
    用于延时队列(延时取出元素),队头的延迟最长。
  • PriorityBlockingQueue
    优先级阻塞队列
  • SemaPhore
    信号量,允许n个任务同时访问。

安全失败的原理
就是CopyOnWriteArrayList方法。
图片.png
java.util.concurrent包下面的所有的类都是安全失败的
快速失败的异常:ConcurrentModificationException
Copy On Write(写时拷贝)更准确地来说是一种思想。在文件没有被修改前,两个线程同时指向的同一文件;当发生修改时,就会立即产生一个副本,而另一个线程的文件保持不变。

Redis的两种持久化策略RDB、AOF都有使用Copy On Write这种思想。
相关文章推荐:
写时复制,写时拷贝,写时分裂,Copy on write
Copy On Write机制了解一下

乐观锁: version、CAS(compareAndSwap)(Atomic包、ReentrankLock就是具体实现)
悲观锁: synchroinized


java.nio.*
基于缓冲的流。
主要组成:Channel,Buffer,Selector
TIM图片20200712200351.jpg
clear,flip,compact区别:

ByteBuffer中有pos、lim、cap来记录缓冲使用情况。
  • clear: position=0,limit=capacity,【清空缓冲】
  • flip: limit=postion;position=0;【读指针移动缓冲头】
  • compact: 未读完的数据,压缩到缓冲头。【压缩未读完数据】

整体流程:获取通道,分配缓冲,clear,read一次,{flip,处理读到的数据,compact,read一次}

大括号{}中表示循环执行。
读的模板代码如下,基本大同小异。
void fileChannel(){
        try {
            FileInputStream fis = new FileInputStream("content.txt");
            FileChannel channel = fis.getChannel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);//分配缓冲
            //position置为0,limit置为capacity
            byteBuffer.clear();     
            int read = channel.read(byteBuffer);
            System.out.println(byteBuffer);
            while (read!=-1){
            //limit置成position最后的位值,再指针position调成0
            //从头读。(position、capacity、limit)
                byteBuffer.flip();          
                while(byteBuffer.hasRemaining()){
                    System.out.print((char)byteBuffer.get());
                }
                //把未读完的数据压缩至0-position,
                //下一次从position到limit读
                byteBuffer.compact();       
                read=channel.read(byteBuffer);
            }
            channel.close();
            fis.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
fileChannel还有tryLock(),lock()和release()方法。tryLock为非阻塞式,得不到锁就立马返回

IO多路复用——selector【NIO的重点】
基于非阻塞模式,一个选择器管理多个Channel。
阻塞模式BIO,在accept()这里会阻塞,知道收到客户端的请求,看看代码:

void BIOServerSocket(){
        java.net.ServerSocket serverSocket =null;
        InputStream is=null;
        try {
            serverSocket=new java.net.ServerSocket(8888);
            byte[] revBuf= new byte[1024];
            while (true){
                Socket socket = serverSocket.accept();//阻塞
                SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress();
                System.out.println("连接的IP为:"+remoteSocketAddress);
                is = socket.getInputStream();
                while(is.read(revBuf)!=-1){
                    System.out.print(new String(revBuf));
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

NIO的代码:

void selector(){
        Selector selector= null;
        ServerSocketChannel ssc =null;
        try{
            selector = Selector.open();
            ssc = ServerSocketChannel.open();
            ssc.bind(new InetSocketAddress("127.0.0.1",8888));
            ssc.configureBlocking(false);
            ssc.register(selector,SelectionKey.OP_ACCEPT);      //channel注册到selector中
            while(true){
                Iterator iterator = selector.selectedKeys().iterator();
                while(iterator.hasNext()){
                    SelectionKey key = iterator.next();         //channel通信类型 connect/accept/read/write
                    iterator.remove();
                    if(key.isAcceptable())handleAccept(key);
                    if (key.isReadable())handleRead(key);
                    if (key.isWritable()&&key.isValid())handleWrite(key);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

整体流程:

  • open一个selector
  • open一个severSocketChannel,
  • bind ip,
  • configureBlocking(false) 通道设置成非阻塞
  • ssr.register(selector,SelectionKey)注册到selector中
  • 后面就是不断获取遍历器,根据key进行相应处理。
SeletionKey主要是存放着channel、seletor,取消,可读/可写,添加附属信息,获取信息等操作;以及四个操作关键字常量 读、写、连接、收

自己能力和精力有限,推荐再阅读完下面NIO及IO多路复用相关优秀博客:
NIO的三大组件
深入浅出NIO之Selector实现原理
IO多路复用select、poll、epoll
同步、异步、阻塞、非阻塞

你可能感兴趣的:(java,nio,线程池)