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(); } }