集合进阶
集合体系结构
- Collection(单列集合):添加的元素是有序、可重复、有索引
是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。
- Map(双列集合):添加的元素是无序、不重复、无索引

Collection的遍历方式
- 迭代器遍历
- 增强for遍历
- Lambda表达式遍历
迭代器遍历
1 2 3 4 5
| Iterator<String> it = list.iterator(); while (it.hasNext()) { String str = it.next(); System.out.println(str); }
|
迭代器在Java中的类是Iterator,迭代器是集合专用的遍历方式。
细节注意点:
- 迭代器在遍历集合时不依赖索引。
- 如果当前位置没有元素,还要强行获取,报错NoSuchElementException。
- 迭代器遍历完毕,指针不会复位。
- 迭代器遍历时,不能用集合的方法进行增加或删除。
列表迭代器遍历
增强for遍历
增强for的底层就是迭代器,为了简化迭代器的代码书写的。
JDK5之后出现,其内部原理就是一个iterator迭代器。
所有的单列集合和数组才能用增强for进行遍历。
格式:
1 2 3
| for (String s : list) { System.out.println(s); }
|
细节:修改增强for中的变量,不会改变集合中原本的数据。它是一个第三方变量。
Lambda表达式遍历
1
| coll.forEach(s -> System.out.println(s));
|

普通for循环(因为List集合存在索引)
泛型
是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
好处:
- 统一数据类型。
- 把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来。
细节:
- 只能支持引用数据类型,不能写基本数据类型。
- 指定泛型的具体类型后,传递数据时,可以传入该类型或者其子类类型。
- 如果不写泛型,类型默认是Object。
泛型可以在很多地方进行定义。

多线程
并发:在同一时刻,有多个指令在单个CPU上交替执行。
并行:在同一时刻,有多个指令在单个CPU上同时执行。
多线程的三种实现方式
继承Thread类的方式
- 自己定义一个类继承Thread
- 重写run方法
- 创建子类的对象,并启动线程
1 2 3 4 5 6 7 8 9
| public class MyThread extends() Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + "HelloWorld"); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class ThreadDemo { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.setName("Thread 1"); t2.setName("Thread 2"); t1.start(); t2.start(); } }
|
实现Runnable接口的方式
- 自定定义一个类,实现Runnable接口
- 重写里面的run方法
- 创建这个类的对象
- 创建一个Thread类的对象,并开启线程
1 2 3 4 5 6 7 8 9 10 11
| public class MyRun implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { Thread t = Thread.currentThread(); System.out.println(t.getName() + "HelloWorld"); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class ThreadDemo { public static void main(String[] args) { MyRun mr = new MyRun(); Thread t1 = new Thread(mr); Thread t2 = new Thread(mr); t1.setName("Thread 1"); t2.setName("Thread 2"); t1.start(); t2.start(); } }
|
利用Callable接口和Future接口方式实现(作为对前两种的补充)
- 创建一个类MyCallable实现Callable接口
- 重写call(有返回值,表示多线程运行的结果)
- 创建MyCallable的对象(表示多线程要执行的任务)
- 创建Future接口的实现类FutureTask的对象(作用:管理多线程运行的结果)
- 创建Thread类的对象,并启动(表示线程)
1 2 3 4 5 6 7 8 9 10 11
| public class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { int sum = 0; for (int i = 1; i <= 100; i++) { sum = sum + 1; } return sum; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class ThreadDemo { public static void main(String[] args) { MyCallable mc new MyCallable(); FutureTask<Integer> ft = new FutureTask<>(mc); Thread t1 = new Thread(ft); t1.start(); Integer result = ft.get(); System.out.println(result); } }
|
三种实现方式的对比
- 前两种方式无法获取多线程运行的结果,第三种则可以。

Thread的常见成员方法

线程的优先级
计算机中线程的调度方式有两种:
- 抢占式调度 - 随机性。
- 非抢占式调度 - 轮流。
Java采用的是抢占式调度,存在随机性。

最小值为1,默认值为5,最大值为10。
守护线程

当其他的非守护线程执行完毕后,守护线程会陆续结束。
出让线程 / 礼让线程
出让当前CPU的执行权。(会让线程交替均匀一点,但不是绝对的。用的比较少。)

插入线程 / 插队线程

线程的生命周期
- sleep()方法会让线程睡眠,睡眠时间到了之后,会立即执行后面的代码吗?
不会。

线程的安全问题
同步代码块
关键字:synchronized
格式:
1 2 3
| synchronized (锁对象) { }
|
特点:
- 锁默认打开,有一个线程进去了,锁自动关闭。
- 里面的代码全部执行完毕,线程出来,锁自动打开。
注意:
- 锁对象必须是唯一的。
一般写当前类的字节码文件对象:当前类.class
。
如果要写定义的Object类对象,那一定要加static修饰。
- synchronized的同步代码块不能写在循环外面。
同步方法
把synchronized关键字加到方法上。
格式:
1
| 修饰符 synchronized 返回值类型 方法名(方法参数) {...}
|
特点:
- 锁住方法中的所有代码。
- 锁对象不能自己指定。
方法是非静态的:this。
方法是静态的:当前类的字节码文件对象。
多线程写法
- 循环
- 同步代码块 synchronized (锁对象) {}
- if (到了末尾)
- if (还没到末尾) { 执行核心逻辑 }
1 2 3 4 5 6 7 8 9 10 11 12
| whlie (true) { synchronized (MyRunnable.class) { if (ticket == 100) { break; } else { ticket++; System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票。"); } } }
|
Lock锁

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| static Lock lock = new ReentrantLock();
public void run() { while (true) { lock.lock(); try { if (ticket == 100) { break; } else { Thread.sleep(10); ticket++; System.out.println(...); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } }
|
死锁
等待唤醒机制(生产者和消费者)
基本写法

1 2 3 4 5 6 7 8 9 10 11 12
| public class ThreadDemo { public static void main(String[] args) { Cook c = new Cook(); Foodie f = new Foodie(); c.setName("厨师"); c.setName("吃货"); c.start(); f.start(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class Cook extends Thread { @Override public void run() { while (ture) { synchronized (Desk.lock) { if (Desk.count == 0) break; else { if (Desk.foodFlag == 1) { try { Desk.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { System.out.println("厨师做了一碗面条"); Desk.foodFlag = 1; Desk.lock.notifyAll(); } } } } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class Foodie extends Thread { @Override public void run() { while (ture) { synchronized (Desk.lock) { if (Desk.count == 0) break; else { if (Desk.foodFlag == 0) { try { Desk.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { Desk.count--; System.out.println("吃货在吃面条,还能再吃 " + Desk.count + "碗"); Desk.lock.notifyAll(); Desk.foodFlag = 0; } } } } } }
|
1 2 3 4 5 6 7 8 9 10
| public class Desk { public static int foodFlag = 0; public static int count = 10; public static Object lock = new Object(); }
|
阻塞队列实现等待唤醒机制

1 2 3 4 5 6 7 8 9 10 11 12 13
| public class ThreadDemo { public static void main(String[] args) { ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1); Cook c = new Cook(queue); Foodie f = new Foodie(queue); c.start(); f.start(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Cook extends Thread { ArrayBlockingQueue<String> queue; public Cook(ArrayBlockingQueue<String> queue) { this.queue = queue; } @Override public void run() { while (true) { try { queue.put("面条"); System.out.println("厨师放了一碗面条"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Foodie extends Thread { ArrayBlockingQueue<String> queue; public Foodie(ArrayBlockingQueue<String> queue) { this.queue = queue; } @Override public void run() { while (true) { try { String food = queue.take(); System.out.println(food); } catch (InterruptedException e) { e.printStackTrace(); } } } }
|
注意:这里可能出现连续打印同一个线程的语句的情况,这是因为打印语句是写在锁的外面,不用担心对共享数据的安全产生影响,只是说阅读起来不太方便罢了。
线程状态

- 新建状态 NEW -> 创建线程对象
- 就绪状态 RUNNABLE -> start方法
- 阻塞状态 BLOCKED -> 无法获得锁对象
- 等待状态 WAITING -> wait方法
- 计时等待状态 TIMED_WAITING -> sleep方法
- 结束状态 TERMINATED -> 全部代码运行完毕
线程池
核心原理
- 创建一个空的池子。
- 提交任务时,池子会创建新的线程对象,任务执行完毕,线程就归还给池子,下一次提交任务时,就不需要创建新的线程,直接复用已有的线程即可。
- 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待。
自定义线程池

不断地提交任务,会有以下三个临界点:
① 当核心线程满时,再提交任务就会排队;
② 当核心线程满、队伍满时,就会创建临时线程;
③ 当核心线程满、队伍满、临时线程满时,就会触发任务拒绝策略。
1
| ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量, 最大线程数量, 空闲线程最大存活时间, 任务队列, 创建线程工厂, 任务的拒绝策略);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| ThreadPoolExecutor pool = new ThreadPoolExecutor(
ThreadPoolExecutor pool = new ThreadPoolExecutor( 3, 6, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), ThreadPoolExecutor ); )
|
线程池多大才合适
- CPU密集型运算:最大并行数 + 1
- I/O密集型运算:最大并行数 * 期望CPU利用率 * (CPU计算时间 + CPU等待时间) / CPU计算时间
反射
反射允许对封装类的字段(成员变量)、方法和构造函数的信息进行编程访问。

获取class对象的三种方式
Class.forName("全类名")
最常用。
2. 类名.class
更多的是作为参数进行传递。
3. 对象.getClass()
当我们有了这个类的对象时,才可以使用。

不能用获得的构造方法 / 反射直接创建对象,如果要这么做,要先调用一个方法:
1 2
| con.setAccessible(true);
|
反射获取构造方法

反射获取成员变量

反射获取成员方法

注意:getMethods()获取的方法包括父类中所有的公共方法。
Object invoke(Object obj, Object… args):运行方法
参数1:用obj对象调用该方法
参数2:调用方法传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)
动态代理
思想分析
- 为什么需要代理?
代理可以无侵入式地给代码增加其他功能。
调用者 -> 代理 -> 对象
代理长什么样?
代理里面就是对象要被代理的方法。
Java通过什么来保证代理的样子?
通过接口保证,后面的对象和代理需要实现同一个接口。接口中就是被代理的所有方法。
代码实现

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Main { public static void main(String[] args) { InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method); if (method.getName().equals("morning")) { System.out.println("Good morning, " + args[0]); } return null; } }; Hello hello = (Hello) Proxy.newProxyInstance( Hello.class.getClassLoader(), new Class[] { Hello.class }, handler); hello.morning("Bob"); } }
interface Hello { void morning(String name); }
|