1.sleep()和wait()的区别?

sleep和wait的功能类似,都是让线程暂停执行任务。

  • sleep是Thread类提供的方法,wait是Object类提供的方法
  • sleep是直接作用于线程对象本身,wait作用于线程正在访问的资源。

调用A对象的wait:让当前正在访问的A对象的线程休眠,同时它拥有一个前提,当前线程必须拥有A对象,所以wait方法只能在同步方法或同步代码块中调用,否则会抛出异常。

  • wait会释放锁,sleep不会释放锁。
package com;

import java.util.concurrent.TimeUnit;

public class Test3 {
    public static void main(String[] args) {
        A a = new A();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                a.test(i);
            }
        }).start();
    }
}

class A{
    public synchronized void test(int i){
        if(i == 5){
            //wait没有时间,会一直休眠下去
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i + "-----------------");
    }
}

i == 5时,调用A的wait方法,会让正在访问A的线程休眠,并且永久不会解除阻塞。

2.如何让线程解除阻塞?

  • 指定wait的时间,wait(long millis)之后会自动解决阻塞,类似sleep

wait->Object,A继承了Object,A中自带wait方法。

sleep->Thread

class A{
    public synchronized void test(int i){
        if(i == 5){
            //wait没有时间,会一直休眠下去
            try {
                this.wait(5000);//指定时间后,阻塞5秒继续输出(this也可以去掉)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i + "-----------------");
    }
}
  • 调用notify方法唤醒线程
package com;

import java.util.concurrent.TimeUnit;

public class Test3 {
    public static void main(String[] args) {
        A a = new A();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                a.test(i);
            }
        }).start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(7);   //输出到5时,等待两秒,再继续输出
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            a.test1();
        }).start();
    }
}

class A{
    public synchronized void test(int i){
        if(i == 5){
            //wait没有时间,会一直休眠下去
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            TimeUnit.SECONDS.sleep(1); //表示1秒输出一个值
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i + "-----------------");
    }
    public synchronized void test1(){   //必须使用synchronized修饰
        this.notify();
    }
}

wait和notify方法必须放到同步方法或者同步代码中,否则会抛出异常。

3.synchronized锁定的是谁?

  • 如果synchronized修饰非静态方法,则锁定的是方法的调用者
import java.util.concurrent.TimeUnit;

public class SyDemo {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
           data.func1();
       },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        new Thread(()->{
            data.func2();
        },"B").start();
    }
}
class Data{
    public synchronized void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1=======");
    }
    public synchronized  void func2(){
        System.out.println("2++++");
    }
}

  • 如果synchronized修饰的是静态方法,则锁定的是类,无论有多少个对象,都会同步。

4.ConcurrentModificationException并发修改异常

并发修改:多个线程对同一个对象进行修改。

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

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

        }
    }
}

由于同时读,同时写,产生了异常。

ArrayList是线程不安全的集合,当多个线程同时操作集合时,会出现数据不准确的情况。

5.ConcurrentModificationException如何解决?

  • 将ArrayList替换成线程安全的集合Vector

Vector中add方法是synchronized修饰

public synchronized boolean add(E e) {
        modCount++;
        add(e, elementData, elementCount);
        return true;
    }

ArrayList中的add方法

  public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }
  • 使用Collections.synchronizedList

import java.util.*;

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

        }
    }
}
  • JUC工具类CopyOnWriteArrayList

java.util.concurrent  JDK的一个包,存放的都是关于并发的工具类。

写时复制。

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

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

        }
    }
}

copyOnWrite写时复制,当我们往一个容器中添加元素的时候,不直接往当前容器中添加,而是将当前容器复制,向新容器中添加元素,添加完成之后,再将原容器的引用指向新的容器。

public boolean add(E e) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            setArray(es);
            return true;
        }
    }

可以对CopyOnWrite容器进行并发的读,因为当前容器不会添加任何元素,添加元素都是针对复制出来的新集合进行操作,所以CopyOnWrite容器也是一种读写分明的思想,读和写操作的是不同容器。

6.JUC常见并发编程工具包

  • CountDownLatch

减法计数器,JUC的工具类,可以用来倒计时,当两个线程同时执行时,如果我们要确保一个线程优先执行,设置一个计数器,当计数器清零的时候,再执行另一个线程。

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest {
    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(100);//因为需要循环100次
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println("+++++++++++Thread");
                countDownLatch.countDown();
            }
        }).start();
        //判断是否清零
        try {
            countDownLatch.await();//注意:这里是await,不是wait,如果使用wait,会报异常,且不会执行main------
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 100; i++) {
            System.out.println("main-----------");
        }
    }
}
  • CyclicBarrier

加法计数器

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierTest {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(10,()->{
            System.out.println("放行");
        });
            for (int i = 0; i < 30; i++) {
              final int temp = i;
              new Thread(()->{
                  System.out.println("-->" + temp);
                  try {
                      cyclicBarrier.await();
                  } catch (InterruptedException e) {
                     e.printStackTrace();
                  } catch (BrokenBarrierException e) {
                      e.printStackTrace();
                  }
              }).start();
            }
    }
}
  • Semaphore

计数信号量,实际开发中主要用来完成限流操作,即限制可以访问某些资源的线程数量。

  • 初始化
  • 获得许可
  • 释放
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreTest {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(5);
        for (int i = 0; i < 15; i++) {
            new Thread(()->{
            try {
                semaphore.acquire();//获取许可
                System.out.println(Thread.currentThread().getName() + "进店购买");
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName() + "出店");
            } catch (InterruptedException e) {
               e.printStackTrace();
            }finally {
                semaphore.release();//释放
            }
        },String.valueOf(i)).start();
        }
    }
}
  • 读写锁

接口ReadWriteLock,实现类ReentrantReadWriteLock,可以多线程同时读,但是同一时间只能有一个线程进行写入操作。

读写锁功能也是为了实现线程同步,只不过粒度更细,可以分别给读和写操作设置不同的锁机制。

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockTest {
    public static void main(String[] args) {
    Cache cache = new Cache();
        for (int i = 0; i < 5; i++) {
            final  int temp = i;
            new Thread(()->{
                cache.write(temp,String.valueOf(temp));
            }).start();
        }
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(()->{
                cache.read(temp);
            }).start();
        }
    }
}

class Cache{
    private Map<Integer,String> map = new HashMap<>();
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    //写操作
    public void write(Integer key,String value){
        readWriteLock.writeLock().lock();
        System.out.println(key + "开始写入");
        map.put(key, value);
        System.out.println(key + "写入完毕");
        readWriteLock.writeLock().unlock();
    }

    //读操作
    public void read(Integer key){
        readWriteLock.readLock().lock();
        System.out.println(key + "开始读取");
        map.get(key);
        System.out.println(key + "读取完毕");
        readWriteLock.readLock().unlock();
    }

}

7.线程池

预先创建好一定数量的线程对象,存入缓冲池,需要用的时候直接从缓冲池中取出,用完后还回到缓冲池中,供下一次任务使用。

优点

  • 提高线程的利用率
  • 提高响应速度
  • 便于统一管理线程对象
  • 可控制最大并发量

工作流程

  • 初始化线程池,创建默认数量的线程对象
  • 当任务过多的时候,额外补充线程数量
  • 当任务趋于正常时,额外补充的线程会自动销毁

初始化线程数量

最大线程数量

线程池的实现(3种)

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Pool {
    public static void main(String[] args) {
//       //单例线程池,只有一个线程对象
//        ExecutorService executorService = Executors.newSingleThreadExecutor();
//        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //缓存线程池,不同性能的电脑分配的不一样
          ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName() + ":" + temp);
            });
        }
        executorService.shutdown();
    }
}
 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

ThreadPoolExecutor 线程池的原生类

核心参数

  • corePoolSize:核心池大小
  • maximumPoolSize:线程池最大线程数
  • keepAliveTime:空闲线程的存活时间
  • unit:时间单位
  • workQueue:阻塞队列
  • threadFactory:线程工厂
  • handler:拒绝策略
import java.util.concurrent.*;

public class Pool {
    public static void main(String[] args) {
    ExecutorService executorService = new ThreadPoolExecutor(2,5,1L, TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 2; i++) {
            executorService.execute(()->{
                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在办理业务");
            });
        }
        executorService.shutdown();
    }
}

更多推荐