JUC的世界II

勤奋能够弥补聪明的不足,但聪明无法弥补懒惰的缺陷。
你好,我是梦阳辰!
期待与你相遇!

概述

JUC就是java.util .concurrent工具包的简称,俗称java并发包。
这是一个处理线程的工具包,JDK 1.5开始出现的.

01.Java多线程复习

Interface Lock

Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition 。

锁是用于通过多个线程控制对共享资源的访问的工具。 通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。 但是,一些锁可能允许并发访问共享资源,如ReadWriteLock的读锁。

 Lock l = ...; 
 l.lock(); 
 try { 
 // access the resource protected by this lock
  } 
 finally {
  l.unlock();
 } 

Lock接口的实现类:Class ReentrantLock可重复锁

  class X {
private final ReentrantLock lock = new ReentrantLock();
// ... public void m() { 
lock.lock(); 
// block until condition holds 
try {
 // ... method body 
} finally {
    lock.unlock() 
}

Lock可以缩小锁的资源范围。

start()线程不会马上启动,只是进入了就绪状态。
等操作系统的调度。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Ticket {//资源类
    private int number = 300;
    private Lock lock= new ReentrantLock();
    public  void saleTicket(){
        lock.lock();
        try {
            if(number>0){
                System.out.println(Thread.currentThread().getName()+"卖出第:"+(number--)+"还剩下:"+number);
            }
        } finally {
            lock.unlock();
        }
    }
}

/*在高内聚低耦合的前提下,  线程  操作  资源类*/
public class SaleTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();


        new Thread(new Runnable (){
            @Override
            public void run() {
                for(int i=1;i<=300;i++){
                    ticket.saleTicket();
                }
            }
        }, "t1").start();

        new Thread(new Runnable (){
            @Override
            public void run() {
               for(int i=1;i<=300;i++){
                    ticket.saleTicket();
                }
            }
        }, "t2").start();

        new Thread(new Runnable (){
            @Override
            public void run() {
               for(int i=1;i<=300;i++){
                    ticket.saleTicket();
                }
            }
        }, "t3").start();


    }
}

线程的状态:
NEW(创建),RUNNABLE(就绪可运行),BLOCKED(阻塞),

WAITING(傻傻的等),TIMED_WAITING(过时不候)

TERMINATED(被终止,即执行完毕或线程死亡)

wait/sleep

功能都是当前线程暂停,有什么区别?

wait放开手去睡,放开手里的锁

sleep握紧手去睡,醒了手里还有锁

Lamda表达式:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Ticket {//资源类
    private int number = 300;
    private Lock lock= new ReentrantLock();
    public  void saleTicket(){
        lock.lock();
        try {
            if(number>0){
                System.out.println(Thread.currentThread().getName()+"卖出第:"+(number--)+"还剩下:"+number);
            }
        } finally {
            lock.unlock();
        }
    }
}

/*在高内聚低耦合的前提下,  线程  操作  资源类*/
public class SaleTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();


        new Thread(()->{
                for(int i=1;i<=300;i++){
                    ticket.saleTicket();
                }
        }, "t1").start();

        new Thread(()->{

            for(int i=1;i<=300;i++){
                ticket.saleTicket();
            }
        }, "t2").start();

        new Thread(()->{
            for(int i=1;i<=300;i++){
                ticket.saleTicket();
            }
        }, "t3").start();
        
    }

}

函数式接口,只有一个方法,java8之后接口可以有实现方法(default)。

02.生产者与消费者复习

判断/干活/通知

案例生产:商家生产一个蛋糕,用户消费一个。


class Cake{
    private int number =0;
    public synchronized void increment() throws InterruptedException {
        //判断
        if(number!=0){
            this.wait();
        }
        //否则生成蛋糕
        number++;
        System.out.println(Thread.currentThread().getName()+"\t"+number);
        //通知,唤醒可以准备做蛋糕了
        this.notifyAll();
    }
    public synchronized void decrement() throws InterruptedException {
        //判断
        if(number==0){
            this.wait();
        }
        //消费
        number--;
        System.out.println(Thread.currentThread().getName()+"\t"+number);
        //通知,可以准备消费
        this.notifyAll();
    }
}
public class Foods {
    public static void main(String[] args) {
        Cake cake = new Cake();

        new Thread(()->{
            for (int i=1;i<=10;i++){
                try {
                    cake.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"做蛋糕").start();
        new Thread(()->{
            for (int i=1;i<=10;i++){
                try {
                    cake.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"买蛋糕").start();
    }

}

如果是多个厨师和多个消费者。

多线程交互中,必须要防止多线程的虚假唤醒

判断的时候必须用while不能用if,否者会出错。


class Cake{
    private int number =0;
    public synchronized void increment() throws InterruptedException {
        //判断
        while (number!=0){
            this.wait();
        }
        //否则生成蛋糕
        number++;
        System.out.println(Thread.currentThread().getName()+"\t"+number);
        //通知,唤醒可以准备做蛋糕了
        this.notifyAll();
    }
    public synchronized void decrement() throws InterruptedException {
        //判断
        while (number==0){
            this.wait();
        }
        //消费
        number--;
        System.out.println(Thread.currentThread().getName()+"\t"+number);
        //通知,可以准备消费
        this.notifyAll();
    }
}
public class Foods {
    public static void main(String[] args) {
        Cake cake = new Cake();

        new Thread(()->{
            for (int i=1;i<=10;i++){
                try {
                    cake.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A1做蛋糕").start();

        new Thread(()->{
            for (int i=1;i<=10;i++){
                try {
                    cake.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B1买蛋糕").start();

        new Thread(()->{
            for (int i=1;i<=10;i++){
                try {
                    cake.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A2做蛋糕").start();



        new Thread(()->{
            for (int i=1;i<=10;i++){
                try {
                    cake.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B2买蛋糕").start();
    }

}

03.新版生产者与消费者写法(使用Lock,Condition)

public interface

ConditionCondition因素出Object监视器方法( wait , notify和notifyAll )成不同的对象,以得到具有多个等待集的每个对象,通过将它们与使用任意的组合的效果Lock个实现。 Lock替换synchronized方法和语句的使用, Condition取代了对象监视器方法的使用。

官方的写法非常官方:

class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock(); try {
       while (count == items.length)
         notFull.await();
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally { lock.unlock(); }
   }

   public Object take() throws InterruptedException {
     lock.lock(); try {
       while (count == 0)
         notEmpty.await();
       Object x = items[takeptr];
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally { lock.unlock(); }
   }
 } 


改进后:

package Part1;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Food{
    private int number = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();//生产者
    private Condition condition2 = lock.newCondition();//消费者
    public void increment() throws InterruptedException {
        lock.lock();
        try{
            while (number!=0){
                condition1.await();//生产者等待
            }
            number++;//干活
            System.out.println(Thread.currentThread().getName()+"生产\t"+number);
            condition2.signal();//唤醒消费者
        }finally {
            lock.unlock();//释放锁
        }
    }
    public void decrement() throws InterruptedException {
        lock.lock();
        try{
            while (number==0){
                condition2.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"消费\t"+number);
            condition1.signal();
        }finally {
            lock.unlock();
        }
    }
}

public class Produce_customer {
    public static void main(String[] args) {
        Food food = new Food();
        new Thread(()->{
            for(int i=1;i<=30;i++){
                try {
                    food.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t1").start();

        new Thread(()->{
            for(int i=1;i<=30;i++){
                try {
                    food.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t2").start();
    }
}


在这里插入图片描述

新版写法的优势

精准通知,精准唤醒。

案例:
多线程之间按顺序调用

线程实现A,B,C的执行顺序
A线程打印5次,B线程打印10次,C线程打印15次。

package Part1;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class First{
    private int state = 1;//线程标识位

    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    public void print(int num) {
        int temp = 1;
        if(num==5){
            temp = 1;
        }else if(num==10){
            temp = 2;
        }else{
            temp = 3;
        }
        lock.lock();
        //判断
        try {
            while(state!=temp) {
                if (num == 5) {
                    condition1.await();
                } else if (num == 10) {
                    condition2.await();
                } else {
                    condition3.await();
                }
            }
                //干活
                for(int i=0;i<num;i++){
                    System.out.println(Thread.currentThread().getName()+"工作!");
                }
                if(num==5){
                    state = 2;
                    condition2.signal();
                }else if(num==10){
                    state = 3;
                    condition3.signal();
                }else{
                    state = 1;
                    condition1.signal();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
    }
}

public class Multithreading {
    public static void main(String[] args) {
        First first = new First();


        new Thread(()->{
            for(int i=0;i<5;i++){
                first.print(5);
            }
        },"A").start();

        new Thread(()->{
            for(int i=0;i<5;i++){
                first.print(10);
            }
        },"B").start();

        new Thread(()->{
            for(int i=0;i<5;i++){
                first.print(15);
            }
        },"C").start();
    }
}

04.锁

synchronized锁定的是整个资源类,尽管它在方法上。

一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法

锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法

加个普通方法后发现和同步锁无关
换成两个对象后,不是同一把锁了,情况立刻变化。

都换成静态同步方法后,情况又变化
所有的非静态同步方法用的都是同一把锁—实例对象本身,
synchronized实现同步的基础: Java中的每一个对象都可以作为锁。

具体表现为以下3种形式
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的class对象。
对于同步方法块,锁是Synchonized括号里配置的对象。

当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。

也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以必须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。

所有的静态同步方法用的也是同一把锁----类对象本身,这两把锁(this/Class)是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。

但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!

05.list线程不安全处理

package Part2;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class NotSafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        for(int i=1;i<=3;i++){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

每次执行的结果都不一致。存在问题。

如果线程增多:直接报下面的异常
在这里插入图片描述
并发修改异常。
解决办法:

方法1:使用Vector集合,Vector是线程安全的,加了同步锁。
但是访问性能降低。

package Part2;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.Vector;

public class NotSafeList {
    public static void main(String[] args) {
        List<String> list = new Vector<>();

        for(int i=1;i<=30;i++){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

方法二:使用集合工具类
虽然相比于ventor而言,效率变高。数据一致性会降低。

package Part2;

import java.util.*;

public class NotSafeList {
    public static void main(String[] args) {
        List<String> list = Collections.synchronizedList(new ArrayList<>());

        for(int i=1;i<=30;i++){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

使用包:java.util.concurrent 的
方法三:Class CopyOnWriteArrayList<E>(重点)

package Part2;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

public class NotSafeList {
    public static void main(String[] args) {
        List<String> list =new CopyOnWriteArrayList<>();

        for(int i=1;i<=30;i++){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

读写分离:

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

复制一份用来写,原先那一份用来读取,写完后,最新版替换原先的版本。

写时复制

Copyonwrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容涨object[]添加),而是先将当前容mobject[]进icopy.复制出一个新的容器object[ ] newELements,然后新的容器object[ ] newELements里添加元素,添加完元素之后,
再将原容器的引用指向新的容器setArray(newELements);。这样做的好处是可以对Copyonwrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以Copyonwrite容器也是一种读写分离的思想,读和写不同的容器

06.set线程不安全处理

解决线程不安全
方法一:使用Collections集合类

package Part2;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

public class NotSafeSet {
    public static void main(String[] args) {
        Set<String> set = Collections.synchronizedSet(new HashSet<>());

        for(int i=1;i<=30;i++){
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

方法二:使用JUC中的类:
java.util.concurrent

Class CopyOnWriteArraySet<E>

实例:

package Part2;

import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;

public class NotSafeSet {
    public static void main(String[] args) {
        Set<String> set = new CopyOnWriteArraySet<>(new HashSet<>());

        for(int i=1;i<=30;i++){
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

HashSet底层是HashMap

但是Set是一个值,而HashMap是键值对。
HashSet底层调用的就是HashMap的put方法。

值存放在键(key)上,value是一个object的PRESENT常量。

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
private static final Object PRESENT = new Object();

HashMap的底层是:
Node的数组,链表和红黑树

HashMap() 
构造一个空的 HashMap ,默认初始容量(16)和默认负载系数(0.75HashMap(int initialCapacity, float loadFactor) 
构造一个空的 HashMap具有指定的初始容量和负载因子。 

实际开发中使用:

HashMap(int initialCapacity, float loadFactor) 
构造一个空的 HashMap具有指定的初始容量和负载因子

07.map线程不安全处理

解决不安全问题:
方法一:使用HashTable
方法二:使用Collections的Collections.synchronizedMap(new HashMap<>());

方法三:

java.util.concurrent 
Class ConcurrentHashMap<K,V>

ArrrayList扩容位原来的一半,HashMap的扩容为原来的一倍。

HashMap的底层原理

Don’t let dream just be your dream.

在这里插入图片描述
在这里插入图片描述

posted @ 2021-02-11 22:57  轻松玩编程  阅读(114)  评论(0编辑  收藏  举报