学习笔记:juc并发编程总结

1. java线程的状态

有6种,new、runnable、blocked、waiting、time_waiting、terminated。同一个时刻一个线程只可能处在其中的一个状态。

java线程状态变迁可以参考《java并发编程的艺术》中的图或者查看源码Thread.State的注释。

可以通过jstack和线程id查看运行的java线程状态

2.Daemon线程

守护线程是一种支持型线程,主要用于程序中后台调度等工作。

java虚拟机中如果没有用户线程就会退出,此时所有的守护线程都会立即终止,所以守护线程中的finally块不一定会执行。

在线程没有运行之前可以通过setDaemon()方法把线程设置为守护线程。

3.线程的创建方式
  • 继承Thread类:Thread类实现了Runable接口
public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"run");
    }
}
class MyThreadTest{
    public static void main(String[] args) {
        Thread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        thread1.start();
        thread2.start();
    }
}
  • 实现Runnable接口
class MyThread2{
    public static void main(String[] args) {
        //新建线程是Thread类的匿名对象,通过构造器创建,这一个Thread构造器中的前一个参数是Runnable接口的匿名实现类对象,后一个参数是线程名不能省略。
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" run");
            }
        },"线程A").start();
        //lambda表达式
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+" run");
        },"线程B").start();
    }
}
  • 实现callable接口:
    FutureTaskRunable接口的实现类,同时也有属性依赖callable接口,可以用来方便的创建线程。
//线程的创建方式:实现callable接口
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //这里FutureTask构造器传入的是callable接口的匿名实现类对象
        FutureTask<Integer> futureTask = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName() + " come in callable");
            return 1024;
        });
        FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName() + " come in callable");
            return 200;
        });
        new Thread(futureTask,"线程A").start();
        new Thread(futureTask2,"线程B").start();

        while (!futureTask.isDone()){
            System.out.println("wait...");
        }
        System.out.println(futureTask.get());
        System.out.println(futureTask.get());
        System.out.println(futureTask2.get());
        System.out.println(Thread.currentThread().getName()+" come over");
    }
}
  • 线程池创建: execute()方法
4.synchronized关键字(实现线程同步)
  • synchronized锁的实现基础:java中的每一个对象都可以作为锁。
    当一个线程试图访问同步代码块,它必须先得到锁,退出或者抛出异常时必须释放锁。
public class SaleTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(new Runnable() {
            @Override
            public void run() {
               for (int i = 0 ; i < 30; i++) {
                   ticket.sale();
               }
            }
        },"线程1").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0 ; i < 30; i++) {
                    ticket.sale();
                }
            }
        },"线程2").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0 ; i < 30; i++) {
                    ticket.sale();
                }
            }
        },"线程3").start();
    }

}

class Ticket{
    private int number = 30;

    public synchronized void sale() {
        if (number > 0) {
            System.out.println(Thread.currentThread().getName()+"卖出了第"+number--+"票,剩下"+number+"张票");
        }
    }
  • synchronized(八锁问题):
    反映synchronized的特点:
    • 对于同步方法,锁就是当前对象。
    • 对于静态同步方法,锁是当前类的class对象
    • 对于同步代码块,锁是synchronized括号里配置的对象。
//1.标准情况  syn通用一把锁
//2.加入延时  syn 锁的对象是方法的调用者 ,两个方法用的是同一把锁,谁先拿到谁先执行
//3.增加普通方法
//4.两个对象
//5.增加两个静态同步方法  静态方法锁的是class模板
//6.两个对象两个静态方法
//7. 一个静态同步方法 一个普通同步方法
//8. 两个对象 一个静态同步方法 一个普通同步方法
public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone  phone1 = new Phone();

        new Thread(()->{
            phone.send();
        }).start();

        new Thread(()->{
            phone1.call();
        }).start();

        new Thread(()->{
            phone.sayHello();
        }).start();

    }
}

class Phone {
    public static synchronized void send() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public  synchronized void call() {
        System.out.println("打电话");
    }

    public void sayHello() {
        System.out.println("Hello");
    }
}
5.sleep()方法和wait()方法的区别

wait()定义在Object类中,使用在同步方法中,用于线程通信,线程调用wait方法,会释放掉锁。
sleep()定义在Thread类中,是个静态方法,用于暂停线程,线程调用sleep方法,不会释放锁
yield()定义在Thread类中,也是静态方法,用于暂停线程,让其他处于相同优先级的线程来竞争执行,始终处于runable状态
join()定义在Thread类中,是个同步方法,如果在A线程中调用了B.join()方法,A线程会取得同步方法的锁,如果B线程存活,A线程会调用wait()释放掉锁进入等待状态,直到方法完成B线程释放锁会自动唤醒A线程。这就完成了线程让步。

6.线程通信:
  • 虚假唤醒
    出现虚假唤醒问题的原因:wait()方法让线程进入waiting状态,被notify()方法唤醒后会继续执行,如果使用if仅判断一次,notifyAll()会让所有之前通过if的线程执行,哪怕现在检测条件已经不满足。所以为了避免出现,需要使用while来判断。
  • 等待/通知的经典范式:
    • 获取对象的锁
    • 判断检测,不满足就调用对象的wait()方法,被通知后仍要检查条件。
    • 条件满足则执行对应的逻辑。
//虚假唤醒问题:if / while
public class ThreadDemo1 {
    public static void main(String[] args) {
        Share share = new Share();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程1").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程2").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程3").start();

    }
}

class Share{
    private int number = 0;

    public synchronized void incr() throws InterruptedException {
        while (number != 0) {
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"执行incr,number: "+number);
        this.notifyAll();
    }

    public synchronized void decr() throws InterruptedException {
        while (number == 0) {
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"执行decr,number: "+number);
        this.notifyAll();
    }
}
7.lock
  • 实现多线程同步
public class LSaleTicket {
    public static void main(String[] args) {
        LTicket lTicket = new LTicket();
        //lambda表达式
        new Thread(()-> {
            for (int i = 0; i < 30; i++) {
                lTicket.sale();
            }
        },"线程1").start();
        new Thread(()-> {
            for (int i = 0; i < 30; i++) {
                lTicket.sale();
            }
        },"线程1").start();
        new Thread(()-> {
            for (int i = 0; i < 30; i++) {
                lTicket.sale();
            }
        },"线程1").start();
    }


}
class LTicket{
    private int number = 40;

    public void sale () {
        ReentrantLock lock = new ReentrantLock();
        lock.lock();
        try {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName()+"卖出了第"+number--+"票,剩下"+number+"张票");
            }
        } finally {
            lock.unlock();
        }
    }
}
  • lock实现线程通信:
public class ThreadDemo2 {
    public static void main(String[] args) {
        Share share = new Share();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                share.incr();
            }
        },"线程A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                share.decr();
            }
        },"线程B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                share.incr();
            }
        },"线程C").start();

    }
}
class Share{
    private int number = 0;
    private Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    public void incr() {
        lock.lock();
        try {
            while (number != 0) {
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+"执行incr,number: "+number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void decr() {
        lock.lock();
        try {
            while (number != 1) {
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"执行decr,number: "+number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
  • 实现线程间定制通信:通过改变标志位和等待/通知机制来实现
public class ThreadDemo3 {
    public static void main(String[] args) {

        ShareResource shareResource = new ShareResource();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                shareResource.print5(i);
            }
        },"线程A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareResource.print10(i);
            }
        }, "线程B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareResource.print15(i);
            }
        }, "线程C").start();

    }
}

class ShareResource{
    private  int flag = 1;
    
    private  Lock lock = new ReentrantLock();
    Condition conditionA = lock.newCondition();
    Condition conditionB = lock.newCondition();
    Condition conditionC = lock.newCondition();
    
    public void print5(long loop) {
        lock.lock();
        try{
            while (flag != 1) {
                conditionA.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName()+"打印"+i+"轮次"+loop);
            }
            //改变标志位
            flag = 2;
            //唤醒其它线程
            conditionB.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void print10(long loop) {
        lock.lock();
        try{
            while (flag != 2) {
                conditionB.await();
            }
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+"打印"+i+"轮次"+loop);
            }
            //改变标志位
            flag = 3;
            //唤醒其它线程
            conditionC.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print15(long loop)  {
        lock.lock();
        try{
            while (flag != 3) {
                conditionC.await();
            }
            for (int i = 0; i < 15; i++) {
                System.out.println(Thread.currentThread().getName()+"打印"+i+"轮次"+loop);
            }
            //改变标志位
            flag = 1;
            //唤醒其它线程
            conditionA.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
8、线程不安全的集合类
  • List
    ArrayList是线程不安全的,多个线程调用put()方法时,会抛出异常。
    常用的解决方法是写时复制机制,添加集合元素时,先复制一个副本,在副本上添加元素,再让变量指针指向副本。这个过程需要持有lock锁,完成后再释放锁。
//解决list不安全
//1.List<Integer> list = new Vector<>();
//2.List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());
//3.List<Integer> list = new CopyOnWriteArrayList<>();
public class ListTest {
    public static void main(String[] args) {
        List<Integer> list = new CopyOnWriteArrayList<>();

        for (int i = 0; i < 100; i++) {
            int temp = i;
            new Thread(()-> {
                list.add(temp);
                System.out.println(Thread.currentThread().getName()+list);
            }, String.valueOf(i)).start();
        }
    }
}
  • Set
//set不安全
//1.Set<Integer> set = Collections.synchronizedSet(new HashSet<>());
//2.Set<Integer> set = new CopyOnWriteArraySet<>();
public class SetTest {
    public static void main(String[] args) {
        Set<Integer> set = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 100; i++) {
            int temp = i;
            new Thread(()->{
                set.add(temp);
                System.out.println(Thread.currentThread().getName()+set);
            },String.valueOf(i)).start();
        }
    }
}
  • Map
//map不安全
//1.Map<Integer,String> map = Collections.synchronizedMap(new HashMap<>());
//2.Map<Integer,String> map = new ConcurrentHashMap<>();
public class MapTest {
    public static void main(String[] args) {
        Map<Integer,String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 100; i++) {
            int temp = i;
            new Thread(()->{
               map.put(temp,"hello");
                System.out.println(Thread.currentThread().getName()+map);
            },String.valueOf(i)).start();

        }
    }
}
9.死锁问题

出现原因

  • 竞争资源
  • 持有资源不释放
public class DeadLock {
    public static void main(String[] args) {
        Object a = new Object();
        Object b = new Object();
        new Thread(()->{
            synchronized (a) {
                System.out.println(Thread.currentThread().getName()+"持有锁a,想获取锁b");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b) {
                    System.out.println("获取到了锁b");
                }
            }
        },"线程A").start();
        new Thread(()->{
            synchronized (b) {
                System.out.println(Thread.currentThread().getName()+"持有锁b,想获取锁a");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (a) {
                    System.out.println("获取到了锁a");
                }
            }
        },"线程B").start();
    }
}
10. 并发工具类

简单的记录一下怎么使用。

  • CountDownLatch
    让一个线程或多个线程等待其它线程完成工作,如果其它线程没有完成工作,当前线程会一直处于阻塞状态,其它线程都完成工作后,才执行await()后面的程序。
public class CountDownLatch {
    public static void main(String[] args) throws InterruptedException {
        java.util.concurrent.CountDownLatch countDownLatch = new java.util.concurrent.CountDownLatch(6);
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 同学离开了!");
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }
        countDownLatch.await();
        System.out.println("班长锁门了!");
    }
}
  • 同步屏障CyclicBarrier
    每一个线程到达一个屏障时被阻塞,直到最后一个线程到达屏障,屏障才会打开,所有被屏障拦截的线程才会继续执行。每个线程调用await()方法时表明已经到达屏障,然后当前线程被阻塞。
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("集齐七龙珠召唤神龙!");
        });
        for (int i = 0; i < 7; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 收集到龙珠!");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}
  • 信号灯 SemaPhore
    控制同时访问特定资源的线程数量。
    release()会动态的增加许可数量,构造器参数只是说明了一个许可证的初始值。
public class SemaPhoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢到了车位!");
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                    System.out.println(Thread.currentThread().getName()+"离开了!");
                }
            },String.valueOf(i)).start();
        }
    }
}
11、读写锁
  • 读写锁的区别
    其他锁基本是排他锁,同一时刻只允许一个线程访问,读写锁维护类一对锁,一个读锁,一个写锁,在同一时刻可以允许多个读线程访问,在写线程访问时,所有的读线程和其它写线程被阻塞
  • 锁获取
    写锁获取:如果存在读锁,则不能获取,并且写锁是排他锁,只能由一个线程持有。如果允许读锁在已经获取的情况下获取写锁,那么持有读锁的线程时感觉不到写锁线程的操作的。
    读锁获取:只有在当前线程获取写锁或者没有任何一个线程获得写锁的情况下,才能获取读锁。
  • 锁降级
    过程:当前线程获得写锁,再获取读锁,最后释放写锁。
    为什么不支持锁升级?可能有多个线程获取了读锁,在获取写锁,其它持有读锁的线程感觉不到写锁线程的操作。
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        //写操作
        for (int i = 0; i < 5; i++) {
            final int num = i;
            new Thread(()->{
                myCache.put(num+"",num+"");
            },String.valueOf(i)).start();
        }
        //读操作
        for (int i = 0; i < 5; i++) {
            final int num = i;
            new Thread(()->{
                myCache.get(num+"");
            },String.valueOf(i)).start();
        }
    }
}

class MyCache{
    private volatile Map<Object,Object> map = new HashMap<>();

    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void put(Object k,Object v)  {
        //写锁
        readWriteLock.writeLock().lock();
        System.out.println(Thread.currentThread().getName()+" 正在写");
        try {
            TimeUnit.SECONDS.sleep(3);
            map.put(k,v);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            readWriteLock.writeLock().unlock();
        }
        System.out.println(Thread.currentThread().getName()+" 写完了!");
    }

    public Object get(Object k)  {
        //读锁
        readWriteLock.readLock().lock();
        System.out.println(Thread.currentThread().getName()+" 正在读");
        Object value = null;
        try {
            TimeUnit.SECONDS.sleep(3);
            value = map.get(k);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            readWriteLock.readLock().unlock();
        }
        System.out.println(Thread.currentThread().getName()+" 读完了!");
        return value;
    }
}
12.阻塞队列
  • 定义:支持两个阻塞操作的队列
    阻塞的插入方法:当队列满的的时候,队列会阻塞插入元素的线程,直到队列不满的时候
    阻塞的移除方法:在队列为空的时候,队列会阻塞获取元素的线程,等待队列变为非空。
  • 常见的阻塞队列:无界的阻塞队列默认的长度是Integer.MAX_VALUE
    ArrayBlockingQueue 有界阻塞队列; LinkedBlockingQueue 无界阻塞队列; DelayQueue 优先级队列实现的无界阻塞队列。
  • 4中处理方式
//阻塞队列
public class BlockQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<Integer>(3);

//        第一组方法
        System.out.println(blockingQueue.add(1));
        System.out.println(blockingQueue.add(2));
        System.out.println(blockingQueue.add(3));
        System.out.println(blockingQueue.add(4));

        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());

//        第二组方法
        System.out.println(blockingQueue.offer(1));
        System.out.println(blockingQueue.offer(2));
        System.out.println(blockingQueue.offer(3));
        System.out.println(blockingQueue.offer(4));

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());

//        第三组方法
       blockingQueue.put(1);
       blockingQueue.put(1);
       blockingQueue.put(1);
      blockingQueue.put(1);

        blockingQueue.take();
        blockingQueue.take();
        blockingQueue.take();
        blockingQueue.take();

        //第四组方法
        blockingQueue.offer(1,3, TimeUnit.SECONDS);
        blockingQueue.offer(1,3, TimeUnit.SECONDS);
        blockingQueue.offer(1,3, TimeUnit.SECONDS);
        blockingQueue.offer(1,4, TimeUnit.SECONDS);

        blockingQueue.poll(4,TimeUnit.SECONDS);
        blockingQueue.poll(4,TimeUnit.SECONDS);
        blockingQueue.poll(4,TimeUnit.SECONDS);
        blockingQueue.poll(2,TimeUnit.SECONDS);
        System.out.println(blockingQueue);

    }
}

13.线程池

有三种常见的线程池:FixedThreadPoolSingleThreadExecutorCachedThreadPool

  • 线程池的核心参数:
    corePoolSize:核心线程数
    maximumPoolSize:线程池最大数量
    runnableTaskQueue:任务阻塞队列
    keepAliveTime:线程活动保持时间
    TimeUnit: 线程活动保持时间的单位
    RejectedExcecutionHandler:饱和拒绝策略
  • 线程池工作流程
    执行execute(),判断核心线程池满没满(是否都在执行任务),没满就创建一个工作线程;满了就下一步判断阻塞队列满没满,没满就把任务存在队列里,满了就下一步判断线程池最大线程,没满就创建一个新的工作线程,满了就下一步执行拒绝策略。
    核心线程池->工作队列->线程池最大线程->拒绝策略
  • 自定义线程池:经常使用到
public class ThreadPollDemo2 {
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(3, 5, 2L, TimeUnit.MICROSECONDS,
                new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 10; i++) {
            threadPool.execute(()->{
                System.out.println(Thread.currentThread().getName()+"办理业务");
            });
        }
    }
}
posted @ 2021-09-25 15:09  夏天的风key  阅读(105)  评论(0编辑  收藏  举报