JUC(一)

1、概念

1.1 什么是JUC

JUC是java.util.concurrent工具包,在java5时出现,是操作线程的工具包

1.2 线程与进程

1.2.1 区分

进程:指系统中正在运行的应用程序,进程就是运行了的程序,进程是资源分配的最小单位
线程:系统分配处理器时间资源的基本单元,线程是程序执行的最小单元

1.2.2 wait/sleep

  1. sleep是Thread类的静态方法,wait是Object的方法(任何实例对象都可以调用)
  2. sleep不会释放锁,他也不需要占用锁。wait会释放锁(调用wait前提是当前线程站有锁,即代码在synchronized中)
  3. 他们都会被interrupted打断

1.2.3 并发与并行

串行:多个任务一个一个执行
并行:多个任务同时执行
并发:同一时刻多个线程访问同一个资源(抢票)

1.2.4 管程

Monitor 监视器 java中也叫锁
是一种同步机制,保证同一时间只有一个线程在操作资源
jvm中的同步基于进入和退出,是使用管程对象实现的

2、Lock接口

2.1 Synchronized

  1. 修饰代码块
  2. 修饰方法

2.2 创建线程的方式

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口
  4. 使用线程池

2.3 Lock使用

    private ReentrantLock lock = new ReentrantLock();
    ......
        lock.lock();
        try {
			......
        } finally {
            lock.unlock();
        }
	......

2.4 synchronized与Lock的区别

  1. Lock是一个接口,synchronized是java中的关键字
  2. synchronized在发生异常时,会自动释放线程占有的锁,所以不会导致死锁的出现;Lock在出现异常时如果没有去使用unlock()释放,会造成死锁现象,所以需要在finally块中释放锁。
  3. 通过Lock可以知道有没有成功的获取到锁,synchronized不行
  4. 在性能上说当资源的竞争不激烈时,两者性能差不多,而当竞争资源非常激烈时(同时存在大量线程竞争),此时Lock性能要优于synchronized

3、线程间的通信

class ANum{
    private int num = 0;

    public synchronized void add() {
        //判断
        if (num !=0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //do something
        num++;
        System.out.println(Thread.currentThread().getName() + "== value::" + num);
        this.notify();
    }
    public synchronized void reduce() {
        if(num != 1) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "== value::" + num);
        //通知其他
        this.notify();
    }

}

public class ThreadCommuniaction {

    public static void main(String[] args) {

        ANum aNum = new ANum();
        new Thread(() ->{
            for (int i = 0; i < 10; i++){
                aNum.add();
            }
        },"AA").start();

        new Thread(() ->{
            for (int i = 0; i < 10; i++){
                aNum.reduce();
            }
        },"BB").start();
    }
}

再增加两个线程,作加和减操作,可能会出现类似的错误

AA== value::1
BB== value::0
AA== value::1
BB== value::0
AA== value::1
DD== value::0
BB== value::-1
DD== value::-2

wait的虚假唤醒:

线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:

  synchronized (obj) {
         while (<condition does not hold>)
             obj.wait(timeout);
         ... // Perform action appropriate to condition
     } 
class Share{
    private int num = 0;
    ReentrantLock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    public void add() {
        //判断
        lock.lock();
        try {
            if (num !=0){
                condition.await();
            }
            //do something
            num++;
            System.out.println(Thread.currentThread().getName() + "== value::" + num);

            //通知其他线程
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
    public void reduce() {
        lock.lock();
        try {
            if (num !=1){
                condition.await();
            }
            //do something
            num--;
            System.out.println(Thread.currentThread().getName() + "== value::" + num);
            //通知其他线程
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
public class ThreadCommuniaction {
    public static void main(String[] args) {
        ANum aNum = new ANum();
        Share share = new Share();
        new Thread(() ->{
            for (int i = 0; i < 10; i++){
                share.add();
            }
        },"AA").start();

        new Thread(() ->{
            for (int i = 0; i < 10; i++){
                share.reduce();
            }
        },"BB").start();
    }
}

3.1 线程间的定制通信

三个线程A、B、C ,让线程A、线程B、线程C按照顺序输出,多次循环。

class ShareResource {

    //标志位
    private Integer flag = 1;

    private ReentrantLock lock = new ReentrantLock();

    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    public void print5(int loop) {
        lock.lock();
        try {
            while (flag != 1) {
                c1.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println("===="+i+"===="+Thread.currentThread().getName()+" loop:"+loop);
            }

            //调整标志位
            flag = 2;
            //唤醒线程
            c2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print10(int loop) {
        lock.lock();
        try {
            while (flag != 2) {
                c2.await();
            }
            for (int i = 0; i < 10; i++) {
                System.out.println("####"+i+"####"+Thread.currentThread().getName()+" loop:"+loop);
            }
            flag = 3;
            c3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print15(int loop) {
        lock.lock();
        try {
            while (flag != 3) {
                c3.await();
            }
            for (int i = 0; i < 15; i++) {

                System.out.println("****"+i+"****"+Thread.currentThread().getName()+" loop:"+loop);

            }
            flag = 1;
            c1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public class CustomizationThread {

    public static void main(String[] args) {

        ShareResource resource = new ShareResource();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                resource.print5(i);
            }
        },"A").start();

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

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                resource.print15(i);
            }
        },"C").start();
    }
}

4、集合的线程安全

4.1 ArrayList

问题,ArrayList的add方法并不是线程安全的,多线程操作时可能会出现错误

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

        for (int i = 0; i < 30; i++) {
            new Thread(() ->{
                list.add(UUID.randomUUID().toString());
                //在输出的时候,可能会有线程修改list,报ConcurrentModificationException错误
                System.out.println(list);
            }).start();
        }
    }

4.1.1 Vector

java.util.Vector 是线程安全的因为它的方法上修饰了synchronized

    public synchronized void addElement(E obj) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }

4.1.2 Collections

使用Collections得到一个线程安全的list,内部也是使用synchronized修饰

//获得一个线程安全的list
List<String> list = Collections.synchronizedList(new ArrayList<>());

4.1.3 CopyOnWriteArrayList

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

//CopyOnWriteArrayList内部add()方法

private transient volatile Object[] array;

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	//将上面定义的array赋值给elements
            Object[] elements = getArray();
            int len = elements.length;
            //将elements中的内容复制到新的数组 这个数组长度比elements长度加一
            //这样线程的写入操作和其他线程的读取操作的对象是不同的,不会报出异常
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            //将newElements赋值给定义的array
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
    

4.2 HashMap与HashSet

4.2.1 CopyOnWriteArraySet

HashSet在多线程操作时也会出现ConcurrentModificationException错误

在CopyOnWriteArraySet中持有着CopyOnWriteArrayList实例,CopyOnWriteArraySet的操作是调用CopyOnWriteArrayList的方法。

private final CopyOnWriteArrayList<E> al;

4.2.2 ConcurrentHashMap

其实HashSet的底层就是使用HashMap保存数据的,所以HashMap也不是线程安全的

使用ConcurrentHashMap来替代HashMap在线程不安全的情景的使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值