面试必备考点 Java 并发编程(2)

Lock 锁

Lock 是对 synchronized 的升级,是一个 JUC 接口,常用的实现类是 ReentrantLock(重入锁)。

synchronized 通过 JVM 实现锁机制,ReentrantLock 通过 JDK 实现锁机制。

重入锁指可以给同一个资源添加多把锁,解锁的方式与 synchronized,synchronized 线程执行完毕之后自动释放锁,ReentrantLock 需要手动释放锁。

Synchronized

package com.janeroad.demo;

import java.util.concurrent.TimeUnit;

public class Test {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(()->{

            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }

        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"B").start();
    }

}

class Ticket{
    private Integer saleNum = 0;
    private Integer lastNum = 30;

    public synchronized void sale(){
        if(lastNum > 0){
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            saleNum++;
            lastNum--;
            System.out.println(Thread.currentThread().getName()+"卖出了第"+saleNum+"张票,剩余"+lastNum+"张票");
        }
    }

}

Lock

package com.janeroad.demo2;

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

public class Test {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(()->{

            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }

        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"B").start();
    }

}

class Ticket{
    private Integer saleNum = 0;
    private Integer lastNum = 30;

    Lock lock = new ReentrantLock();

    public void sale(){
        //上锁
        lock.lock();
        if(lastNum > 0){
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            saleNum++;
            lastNum--;
            System.out.println(Thread.currentThread().getName()+"卖出了第"+saleNum+"张票,剩余"+lastNum+"张票");
        }
        //解锁
        lock.unlock();
    }

}

synchronized 和 Lock 的区别

1、synchronized 是关键字,Lock 是接口。

2、synchronized 通过 JVM 实现锁机制,Lock 通过 JDK 实现锁机制。

3、synchronized 自动上锁,自动解锁,Lock 手动上锁,手动解锁。

4、synchronized 无法判断是否可以获得锁,Lock 可以判断是否拿到了锁。

5、synchronized 拿不到锁就会一直等待,Lock 不一定会一直等下去。

6、synchronizde 是非公平锁,Lock 可以设置是否为公平锁。

公平锁:排队,当锁没有被占用时,当前线程需要判断队列中是否有其他等待线程。

非公平锁:插队,当锁没有被占用时,当前线程可以直接占用,不需要判断队列中是否有其他等待线程。

ReentrantLock 除了可重入之外,还有一个可中断的特点:允许在某个线程等待时,主动去中断线程,不需要获取锁,但是会抛出异常。

package com.janeroad.demo3;

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

public class Test {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        lock.lock();
        Thread thread = new Thread(()->{
            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"interrupted");
        });
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}

image.png

ReentrantLock 还具备限时性的特点,可以判断某个线程在一定时间内能否获取到锁,tryLock 返回一个 boolean 的值,true 表示可以拿到锁,false 表示拿不到锁。

package com.janeroad.demo4;

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

public class Test {
    public static void main(String[] args) {
        TimeLock timeLock = new TimeLock();
        new Thread(()->{
            timeLock.getLock();
        },"A").start();

        new Thread(()->{
            timeLock.getLock();
        },"B").start();
    }
}

class TimeLock{
    private ReentrantLock lock = new ReentrantLock();

    public void getLock(){
        try {
            //3秒之内能否获取到锁
            if(lock.tryLock(3, TimeUnit.SECONDS)){
                System.out.println(Thread.currentThread().getName()+"拿到了锁");
                TimeUnit.SECONDS.sleep(5);
            }else{
                System.out.println(Thread.currentThread().getName()+"没有拿到锁");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //判断当前线程是否持有锁
            if(lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
    }
}

死锁

package com.janeroad.demo5;

import java.util.concurrent.TimeUnit;

public class Test {



    public static void main(String[] args) {
        DeadLock deadLock1 = new DeadLock(1);
        DeadLock deadLock2 = new DeadLock(2);

        new Thread(()->{
            deadLock1.lock();
        },"张三").start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            deadLock2.lock();
        },"李四").start();
    }

}

class Data{

}

class DeadLock{
    private int num;
    private static Data data1 = new Data();
    private static Data data2 = new Data();

    public DeadLock(int num) {
        this.num = num;
    }

    public void lock(){
        if(num == 1){
            System.out.println(Thread.currentThread().getName()+"获取到data1,等待获取data2");
            synchronized (data1){
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (data2){
                    System.out.println(Thread.currentThread().getName()+"用餐完毕");
                }
            }
        }

        if(num == 2){
            System.out.println(Thread.currentThread().getName()+"获取到data2,等待获取data1");
            synchronized (data2){
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (data1){
                    System.out.println(Thread.currentThread().getName()+"用餐完毕");
                }
            }
        }
    }
}

生产者消费者模式

在一个生产环境中,生产者和消费者在同一时间段内共享同一块缓冲区,生产者负责向缓冲区中添加数据,消费者负责从缓冲区中取出数据,生产者消费者模式本质是为了实现线程通信。

package com.janeroad.demo6;

import java.util.concurrent.TimeUnit;

public class ProCon {

    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                data.increment();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                data.decrement();
            }
        },"B").start();
    }
}

class Data{
    private Integer num = 0;

    /**
     * 加一
     */
    public synchronized void increment(){
        while (num!=0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num++;
        this.notify();
        System.out.println(Thread.currentThread().getName()+"--->"+num);
    }

    /**
     * 减一
     */
    public synchronized void decrement(){
        //当num==0,不要再减
        while(num == 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num--;
        this.notify();
        System.out.println(Thread.currentThread().getName()+"--->"+num);
    }

}

判断的时候必须使用 while 循环,而不能使用 if,因为使用 if 会存在线程虚假唤醒的问题,虚假唤醒是指 wait 会在除了 notify 以外的情况被唤醒,而此时是不应该被唤醒的,使用 while 可以多次检测,避免虚假唤醒的问题。

Lock

package com.janeroad.demo7;

import java.lang.management.LockInfo;
import java.time.LocalDateTime;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                data.increment();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                data.decrement();
            }
        },"B").start();
    }
}

class Data{
    private Integer num = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    /**
     * 加一
     */
    public void increment(){
        try {
            lock.lock();
            while (num!=0){
                condition.await();
            }
            num++;
            condition.signal();
            System.out.println(Thread.currentThread().getName()+"--->"+num);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    /**
     * 减一
     */
    public void decrement(){

        try {
            lock.lock();
            while(num == 0){
                condition.await();
            }
            num--;
            condition.signal();
            System.out.println(Thread.currentThread().getName()+"--->"+num);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }


    }
}

Condition 接口也提供了类似 Object 的监视方法,与 Lock 配合可以实现等待/通知模式,Condition 对象依赖于 Lock 对象的。

ConcurrentModificationException 异常

并发修改异常

package com.janeroad.demo8;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("a");
                System.out.println(list);
            }).start();
        }
    }
}

ArrayList 线程不安全

如何解决?

1、将 ArrayList 替换成线程安全的 Vector。

ArrayList add 非线程安全的

image.png

Vector add 线程安全

image.png
2、Collections.synchronizedList

package com.janeroad.demo8;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        List<String> list = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("a");
                System.out.println(list);
            }).start();
        }
    }
}

3、JUC CopyOnWriteArrayList

CopyOnWrite 写时复制

public class Test {
    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("a");
                System.out.println(list);
            }).start();
        }
    }
}

image.png

image.png

package com.janeroad.demo8;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {

        /**
         * List
         */
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("a");
                System.out.println(list);
            }).start();
        }

        /**
         * Set
         */
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                set.add(String.valueOf(temp));
                System.out.println(set);
            }).start();
        }

        /**
         * Map
         */
        Map<String,String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 10; i++) {
            try {
                TimeUnit.MILLISECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(UUID.randomUUID().toString().substring(0,3),UUID.randomUUID().toString().substring(0,3));
            System.out.println(map);
        }

    }
}

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×