线程不安全:负数 卖了两次
package com.itheima._01卖票案例;
/**
目标:能够开启多个线程同时进行卖票
讲解:
1. 模拟火车站卖票,实现多个窗口同时卖票(假设总票数为100张)
2. 实现步骤分析
* 定义变量记录总票数
* 自定义卖票线程类实现Runnable接口:重写run方法
* 创建多个线程模拟多个窗口开始卖票
3. 卖票逻辑分析
* 要保证票能够被卖完
* 使用死循环卖票:保证票能够卖完
* 判断是否有剩余票数,有则卖一张,如果没有了则提示用户并退出循环。
小结:
卖票案例的实现步骤:
* 定义变量记录总票数
* 自定义卖票线程类实现Runnable接口:重写run方法
* 创建多个线程模拟多个窗口开始卖票
*/
public class Demo01 {
public static void main(String[] args) {
// 创建Runnable接口实现类对象
TicketThread target = new TicketThread();
// 创建两个线程:模拟两个窗口同时卖票
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
// 设置线程名称
t1.setName("美女A");
t2.setName("美女B");
// 开启线程
t1.start();
t2.start();
}
}
package com.itheima._01卖票案例;
/**
自定义卖票线程类实现Runnable接口:重写run方法
*/
public class TicketThread implements Runnable{
// 定义变量记录总票数
private int tickets = 100;
@Override
public void run() {
// 使用死循环卖票:保证票能够卖完
while (true){
// 判断是否有剩余票数,有则卖一张
if (tickets > 0){
try {
// 模拟网络延时
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
" 卖了一张票,还剩 "+(--tickets)+" 张");
} else {
// 如果没有了则提示用户并退出循环
System.out.println("票卖完了......");
break;
}
}
}
}
格式:synchronized (锁对象){ }//编写操作共享资源的代码
原理:能够保证同一时间只有一个线程执行代码块的代码
注意事项:
锁对象可以使用任意类型的对象
锁对象必须唯一:要被所有线程共享
package com.itheima._03线程安全_同步代码块;
/**
目标:使用同步代码块实现线程安全
讲解:
1. 同步代码块格式
synchronized(锁对象){
// 编写操作共享资源的代码
}
2. 同步代码块的原理
* 能够保证同一时间只有一个线程执行代码块中的代码
3. 锁对象的使用注意事项
* 锁对象可以使用任意类型的对象
* 锁对象必须唯一:要被所有线程共享
小结:
1. 同步代码块的格式
synchronized(锁对象){
操作共享资源的代码
}
2. 同步代码块的原理
能够保证同一时间只有一个线程执行代码块的代码
*/
public class Demo03 {
public static void main(String[] args) {
// 创建Runnable接口实现类对象
TicketThread target = new TicketThread();
// 创建两个线程:模拟两个窗口同时卖票
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
// 设置线程名称
t1.setName("美女A");
t2.setName("美女B");
// 开启线程
t1.start();
t2.start();
}
}
package com.itheima._03线程安全_同步代码块;
/**
目标:使用同步代码块实现线程安全
*/
public class TicketThread implements Runnable{
// 定义变量记录总票数
private int tickets = 100;
// 创建锁对象
// private Object lockObj = new Object();
@Override
public void run() {
// 使用死循环卖票:保证票能够卖完
while (true){
// 使用同步代码块实现线程安全
synchronized (this){
// 判断是否有剩余票数,有则卖一张
if (tickets > 0){
try {
// 模拟网络延时
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
" 卖了一张票,还剩 "+(--tickets)+" 张");
continue;
}
}
// 如果没有了则提示用户并退出循环
System.out.println("票卖完了......");
break;
}
}
}
package com.itheima._04线程安全_同步方法;
/**
目标:使用同步方法实现线程安全
讲解:
1. 同步方法的格式
修饰符 synchronized 返回值类型 方法名(参数列表){
操作共享资源的代码
}
2. 同步方法的原理
能够保证同一个时间只有一个线程进入方法体执行
3. 同步方法的锁对象
* 静态同步方法:锁对象是:类名.class
* 非静态同步方法:锁对象是:this
小结:
1. 同步方法的格式
修饰符 synchronized 返回值类型 方法名(){ }
2. 同步方法的原理
同一时间只能有一个线程进入方法体
*/
public class Demo04 {
public static void main(String[] args) {
// 创建Runnable接口实现类对象
TicketThread target = new TicketThread();
// 创建两个线程:模拟两个窗口同时卖票
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
// 设置线程名称
t1.setName("美女A");
t2.setName("美女B");
// 开启线程
t1.start();
t2.start();
}
}
package com.itheima._04线程安全_同步方法;
/**
目标:使用同步方法实现线程安全
*/
public class TicketThread implements Runnable{
// 定义变量记录总票数
private int tickets = 100;
@Override
public void run() {
// 使用死循环卖票:保证票能够卖完
while (tickets > 0){ // ticket = 0
// 使用同步方法实现线程安全
this.saleTicket();
}
// 如果没有了则提示用户并退出循环
System.out.println("票卖完了......");
}
// 同步方法:每调用1次就卖一张票
// 静态同步方法:锁对象是:类名.class
// 非静态同步方法:锁对象是:this(方法调用者)
// 每个类都会有给Class对象:字节码文件对象
// 每个类的Class对象是唯一的,只有一个(单例对象)
public synchronized void saleTicket(){
// 判断是否有剩余票数,有则卖一张
if (tickets > 0){
try {
// 模拟网络延时
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
" 卖了一张票,还剩 "+(--tickets)+" 张");
}
}
public void saleTicket01(){
synchronized (this){
// 判断是否有剩余票数,有则卖一张
if (tickets > 0){
try {
// 模拟网络延时
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
" 卖了一张票,还剩 "+(--tickets)+" 张");
}
}
}
}
Lock接口常用方法:
Lock接口常用实现类
使用注意
package com.itheima._05线程安全_Lock接口;
/**
目标:使用Lock接口提供的方法实现线程安全
讲解:
1. Lock接口常用方法
void lock(); 获取锁
void unlock(); 释放锁
2. Lock接口常用实现类
ReentrantLock 互斥锁
3. Lock方法使用注意事项
* 获取锁和释放锁的代码必须成对出现:获取一次就释放一次
小结:
1. Lock接口用于实现线程安全的方法是哪两个?
void lock()
void unlock()
2. Lock实现线程安全的正确格式
lock.lock();
try{
// 操作共享资源的代码
} finally{
lock.unlock();
}
问题1:synchronized关键字和Lock接口的选择?
Lock接口是JDK1.5新特性
如果资源竞争不激烈(线程数量少),则选择synchronized和Lock接口效率几乎一致。
如果资源竞争很激烈(线程数量多),则Lock接口的效率远远高于synchronized。
问题2:如何判断哪些代码应该锁住?
所谓的共享资源就是变量
判断变量是成员变量还是局部变量,如果是局部变量就不用加锁。
如果是成员变量,则判断是否有多个线程同时执行修改操作,如果是则需要保证线程安全。
*/
public class Demo05 {
public static void main(String[] args) {
// 创建Runnable接口实现类对象
TicketThread target = new TicketThread();
// 创建两个线程:模拟两个窗口同时卖票
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
// 设置线程名称
t1.setName("美女A");
t2.setName("美女B");
// 开启线程
t1.start();
t2.start();
}
}
package com.itheima._05线程安全_Lock接口;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
目标:使用Lock接口方法实现线程安全
*/
public class TicketThread implements Runnable{
// 定义变量记录总票数
private int tickets = 100;
// 创建互斥锁对象
private Lock lockObj = new ReentrantLock();
@Override
public void run() {
// 使用死循环卖票:保证票能够卖完
while (true){
// 获取锁
lockObj.lock();
try {
// 判断是否有剩余票数,有则卖一张
if (tickets > 0){
// 模拟网络延时
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() +
" 卖了一张票,还剩 "+(--tickets)+" 张");
continue;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lockObj.unlock();
}
// 如果没有了则提示用户并退出循环
System.out.println("票卖完了......");
break;
}
}
}
必须由锁对象调用
必须在同步代码块或同步方法中调用
小结:
1. wait方法的作用:让当前线程释放CPU,进入无限等待状态
2. notify方法的作用:随机唤醒一个正在等待的线程
package com.itheima._07线程等待与唤醒案例;
import java.util.ArrayList;
/**
包子铺线程
*/
public class BaoZiPuThread implements Runnable{
// 集合对象
private ArrayList<String> list;
public BaoZiPuThread(ArrayList<String> list) {
this.list = list;
}
int index = 0;
// 生产包子
@Override
public void run() {
while (true){
// 使用同步代码块实现线程安全
synchronized (list){
// 判断已经生产了包子,没有则生产一个
if (list.isEmpty()){
try {
// 生成包子
String bz = "肉包子:" + index++;
// 将包子添加到集合中
list.add(bz);
System.out.println("生成了一个包子:" + bz);
// 唤醒吃货吃包子
list.notify();
// 让当前线程进入等待状态:等待吃货吃完包子
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
package com.itheima._07线程等待与唤醒案例;
import java.util.ArrayList;
/**
吃货线程:吃包子,从集合中获取包子
*/
public class ChiHuoThread implements Runnable {
// 集合对象
private ArrayList<String> list;
public ChiHuoThread(ArrayList<String> list) {
this.list = list;
}
// 吃包子,从集合中获取包子
@Override
public void run() {
while (true) {
synchronized (list){
// 判断是否有包子
if(list.isEmpty()){
try {
// 判断如果没有包子,则等待
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 有则吃一个:将包子从集合中删除
String bz = list.remove(0);
System.out.println("吃了一个包子:" + bz);
// 通知包子铺生产包子
list.notify();
}
}
}
}
1.线程的六种状态分别是什么
NEW:新建状态:刚刚创建处理,还没有调用start方法
Runnable:可运行状态,只要在该中状态的线程才有资格去抢夺CPU,抢夺到就进入运行状态,没有抢夺就一直抢
Blocked:阻塞状态,没有获得锁对象时进入的状态
Waiting:无线等待状态,线程内部调用了wait()方法进入状态,需要被唤醒
Timed_waiting:计时等待状态:线程内部调用了sleep 或者wait (参数) 等方法进入
Terminated:死亡状态:线程任务正常执行完毕或调用stop方法进入状态
submit(Runnable r):提交Runnable任务
void shutdowm():销毁线程池,在实际开发中一般不销毁
package com.itheima._10线程池_Runnable任务;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
目标:使用线程池执行Runnable任务
讲解:
1. 如何创建线程池
通过Executors工具类的静态方法创建线程池对象,该方法如下:
public static ExecutorService newFixedThreadPool(int nThreads)
创建线程池对象并指定线程数量。
2. 线程池常用方法
Future submit(Runnable r); 提交Runnable任务
Future submit(Callable task); 提交Callable任务
void shutdown(); 销毁线程池:在实际开发中一般不销毁
小结:
1. 提交Runnable任务到线程池的实现步骤
* 创建线程池对象并指定线程数量
* 自定义类实现Runnable接口,重写run方法:封装线程任务的代码
* 创建Runnable接口实现类对象
* 调用线程池对象的submit方法传递实现类对象:内部会自动从线程池中获取线程
执行run方法
* 调用线程池对象的shutdown方法销毁线程池。
*/
public class Demo10 {
public static void main(String[] args) {
// 创建线程池对象
ExecutorService tp = Executors.newFixedThreadPool(3);
// 创建Runnable接口实现类对象
RunnableTask task01 = new RunnableTask();
// 提交任务
tp.submit(task01);
tp.submit(task01);
tp.submit(task01);
// 销毁线程池:在实际开发中一般不销毁
tp.shutdown();
}
}
package com.itheima._10线程池_Runnable任务;
/**
* Runnable任务
*/
public class RunnableTask implements Runnable{
@Override
public void run() {
System.out.println("子线程..." + Thread.currentThread().getName());
}
}
V Call() throws Exception
等价于Runnable 接口run方法,用来封装线程任务代码
package com.itheima._11线程池_Callable任务;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
目标:使用线程池执行Callable任务
讲解:
1. Callable接口方法介绍
* V call() throws Exception;
* 等价于Runnable接口run方法,用来封装线程任务代码
2. 提交Callable任务的步骤
* 创建线程池对象并指定线程数量
* 自定义类实现Callable接口,重写call方法:封装线程任务代码
* 创建Callable接口实现类对象
* 调用线程池对象的submit方法:传递实现类对象
3. Callable接口的好处
* 执行完毕任务之后可以有返回值,并且call方法可以声明异常。
4. Future接口概述
Future接口使用用来封装Callable任务执行完毕的结果
Future接口常用方法:
V get(); 获得任务执行完毕的结果
5. Runnable和Callable接口的选择
* 如果任务执行完毕需要有返回值,则只能选择Callable任务,否则可以随便选择。
小结:
1. 提交Callable任务到线程池的实现步骤
* 创建线程池对象并指定线程数量
* 自定义类实现Callable接口,重写call方法:封装线程任务代码
* 创建Callable接口实现类对象
* 调用线程池对象的submit方法:传递实现类对象
2. 如何获得Callable任务执行后的返回值
* 通过Future对象的get方法获得
*/
public class Demo11 {
public static void main(String[] args) throws Exception {
// 创建线程池对象并指定线程数量
ExecutorService tp = Executors.newFixedThreadPool(2);
// 创建Callable接口实现类对象
CallableTask task = new CallableTask();
// 调用线程池对象的submit方法:传递实现类对象
Future<String> f = tp.submit(task);
// 获得任务执行完毕的结果
System.out.println(f.get()); // abc
// 销毁线程池
tp.shutdown();
}
}
package com.itheima._11线程池_Callable任务;
import java.util.concurrent.Callable;
/**
* Callable任务
*/
public class CallableTask implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("子线程..." + Thread.currentThread().getName());
return "abc";
}
}
package com.itheima._12线程池练习;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
需求说明:使用线程池方式执行任务:求1到n的和
实现步骤:
1.自定义类实现Callable并指定泛型变量为Integer
2.自定义类定义成员变量:接收参数n
3.自定义类中重写call方法:计算1到n的和
4. 创建线程池对象指定线程数量
5. 创建自定义类对象传递参数
6. 调用submit方法传递实现类对象并获得Future对象
7. 调用Future对象的get方法获得返回值
8. 销毁线程池
*/
public class Demo12 {
public static void main(String[] args) throws Exception{
// 4. 创建线程池对象指定线程数量
ExecutorService tp = Executors.newFixedThreadPool(2);
// 5. 创建自定义类对象传递参数
SumCallable task = new SumCallable(100);
// 6. 调用submit方法传递实现类对象并获得Future对象
Future<Integer> f = tp.submit(task);
// 7. 调用Future对象的get方法获得返回值
System.out.println(f.get());
// 8. 销毁线程池:会等待线程池中的任务执行完毕才销毁
tp.shutdown();
// 立即销毁线程池:不会等到线程池中的任务执行完毕
// tp.shutdownNow();
}
}
// 1.自定义类实现Callable并指定泛型变量为Integer
class SumCallable implements Callable<Integer>{
// 2.自定义类定义成员变量:接收参数n
private int num;
public SumCallable(int num) {
this.num = num;
}
// 3.自定义类中重写call方法:计算1到n的和
@Override
public Integer call() throws Exception {
// 定义求和变量
int result = 0;
for (int i = 1; i <= num; i++) {
result += i;
}
return result;
}
}
package com.itheima._13死锁概念;
/**
目标:理解死锁的概念
讲解:
1. 什么是死锁
指多个线程在执行任务过程中因争夺资源而造成一种相互等待的现象。
2. 产生死锁的条件
* 有多个线程
* 有多把锁
* 有同步代码块嵌套
3. 如何避免死锁
* 使用单个线程
* 使用1把锁
* 没有同步代码块嵌套
小结:
1. 什么是死锁:多个线程执行过程中因争夺资源(锁)而造成一种相互等待的线程
2. 如何避免死锁
使用单线程或使用1把锁或没有同步嵌套
*/
public class Demo13 {
public static void main(String[] args) {
// 创建接口实现类对象
ThreadDeadLock target = new ThreadDeadLock();
// 创建两个线程
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
// 设置线程名称
t1.setName("线程1");
t2.setName("线程2");
// 开启线程
t1.start();
t2.start();
}
}
package com.itheima._13死锁概念;
/*
* 有多把锁
* 有同步代码块嵌套
*/
public class ThreadDeadLock implements Runnable {
// 创建两个锁对象
private Object lockA = new Object();
private Object lockB = new Object();
@Override
public void run() {
// 有同步代码块嵌套
synchronized (lockA){
System.out.println(Thread.currentThread().getName() + "...lockA");
synchronized (lockB){
System.out.println(Thread.currentThread().getName() + "...lockB");
}
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName() + "...lockB");
synchronized (lockA){
System.out.println(Thread.currentThread().getName() + "...lockA");
}
}
}
}
package com.itheima._14Lambda概述和入门案例;
/**
目标:能够理解函数式编程相对于面向对象的优点
讲解:
1. Lambda表达式概述
使用Lambda表达式编程就是函数式编程
JDK1.8新特性,作用:简化匿名内部类的语法。
核心思想:只专注做什么,而不是怎么做。
2. 面向对象语法的弊端
* 1.定义类 2.创建对象 3.使用对象 代码冗余
3. 示例:开启一个线程执行任务,任务是在控制台中输出:我是程序猿我骄傲
* 使用面向对象语法实现
* 使用lambda表达式实现
小结:
函数式编程相对于面向对象的优点:
1. 面向对象: 要先创建类,然后在类中定义方法,最后创建类的对象调用方法,代码冗余
2. 函数式编程: 只专注做什么,而不是怎么做。
*/
public class Demo14 {
public static void main(String[] args) {
// 使用面向对象语法实现
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "1我是程序猿我骄傲");
}
}).start();
// 使用lambda表达式实现
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "2我是程序猿我骄傲");
}).start();
}
}
package com.itheima._14Lambda概述和入门案例;
/**
目标:能够理解函数式编程相对于面向对象的优点
讲解:
1. Lambda表达式概述
使用Lambda表达式编程就是函数式编程
JDK1.8新特性,作用:简化匿名内部类的语法。
核心思想:只专注做什么,而不是怎么做。
2. 面向对象语法的弊端
* 1.定义类 2.创建对象 3.使用对象 代码冗余
3. 示例:开启一个线程执行任务,任务是在控制台中输出:我是程序猿我骄傲
* 使用面向对象语法实现
* 使用lambda表达式实现
小结:
函数式编程相对于面向对象的优点:
1. 面向对象: 要先创建类,然后在类中定义方法,最后创建类的对象调用方法,代码冗余
2. 函数式编程: 只专注做什么,而不是怎么做。
*/
public class Demo14 {
public static void main(String[] args) {
// 使用面向对象语法实现
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "1我是程序猿我骄傲");
}
}).start();
// 使用lambda表达式实现
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "2我是程序猿我骄傲");
}).start();
}
}
package com.itheima._16Lambda练习;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
/**
目标:能够编写有参数有返回值的Lambda表达式
需求说明:
1. 定义一个学生类,成员变量有:姓名,年龄,成绩。
2. 创建多个学生对象添加到集合中,对按年龄对集合学生对象进行排序(升序和降序)
小结:
1. Lambda有参数有返回值的格式:
(参数列表) -> {
return 返回值;
}
*/
public class Demo08 {
public static void main(String[] args){
// 1. 创建集合对象用来多个学生对象
ArrayList<Student> list = new ArrayList<>();
// 2. 创建学生对象添加到集合中
list.add(new Student("张三",20,90));
list.add(new Student("李四",21,80));
list.add(new Student("王五",18,70));
list.add(new Student("赵六",30,99));
// 3. 按照年龄升序排序学生
// 使用匿名内部类实现
/*
Collections.sort(list, new Comparator() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge();
}
});*/
// 使用lambda表达式简化匿名内部类实现
Collections.sort(list, (Student o1, Student o2)->{
return o2.getAge() - o1.getAge();
});
for (Student student : list) {
System.out.println(student);
}
}
}
package com.itheima._16Lambda练习;
/**
1. 定义一个学生类,成员变量有:姓名,年龄,成绩。
*/
public class Student {
private String name;
private int age;
private int score;
public Student(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
package com.itheima._17Lambda省略格式;
import com.itheima._16Lambda练习.Student;
import java.util.ArrayList;
import java.util.Collections;
/**
目标:掌握Lambda表达式省略规则
讲解:
1. Lambda表达式省略规则
参数列表中的参数类型可以省略:不管有多少个参数
当参数列表有且只有一个参数时:小括号可以省略
当方法体有且只有一条语句时:大括号可以省略,如果省略大括号则return关键字和分号必须要省略。
小结:
Lambda表达式省略规则
有且只有一个参数时可以省略小括号
有且只有一条语句时可以省略大括号
参数类型可以随时省略
*/
public class Demo17 {
public static void main(String[] args){
// 1. 创建集合对象用来多个学生对象
ArrayList<Student> list = new ArrayList<>();
// 2. 创建学生对象添加到集合中
list.add(new Student("张三",20,90));
list.add(new Student("李四",21,80));
list.add(new Student("王五",18,70));
list.add(new Student("赵六",30,99));
// 3. 使用lambda表达式的标准格式实现
// Comparator接口抽象方法 int compare(T o1,T o2)
Collections.sort(list, (Student o1,Student o2)->{
return o1.getAge() - o2.getAge();
});
// 4. 使用lambda表达式的省略格式实现
// 方法引用:简化lambda表达式
Collections.sort(list, (o1,o2) -> o1.getAge() - o2.getAge());
}
}
package com.itheima._18集合处理数据的弊端;
import java.util.ArrayList;
import java.util.List;
/**
目标:理解遍历操作集合的弊端
讲解:
一个ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰,
需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据
小结:
1. 遍历操作集合的弊端:每次都需要对集合进行遍历,代码冗余
*/
public class Demo11 {
public static void main(String[] args){
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
// 创建集合存储姓张的
ArrayList<String> zList = new ArrayList<>();
// 1.拿到所有姓张的
for (String name : list) {
// 判断是否是姓张
if (name.startsWith("张")){
zList.add(name);
}
}
// 创建集合存储名字三个字
ArrayList<String> threeList = new ArrayList<>();
// 2.拿到名字长度为3个字的
for (String name : zList) {
// 判断名字是否是三个字
if(name.length() == 3){
threeList.add(name);
}
}
// 3.打印这些数据
for (String name : threeList) {
System.out.println(name);
}
// 使用stream流操作集合元素
list.stream()
.filter(name->name.startsWith("张"))
.filter(name->name.length() == 3)
.forEach(name->System.out.println(name));
}
}
package com.itheima._19Stream流介绍;
/**
目标:能够理解Stream流的作用
讲解:
1. 集合的主要作用:存储元素,不适合对集合元素进行处理。
2. Stream流概述
JDK1.8新特性,可以理解为流水线:通过流水线上的一道道工序(方法)对集合元素进行加工处理
小结:
Stream流的作用:对集合元素进行加工处理
*/
public class Demo19 {
}
package com.itheima._20Stream流的获取方式;
import java.util.*;
import java.util.stream.Stream;
/**
目标:能够通过集合、映射或数组方式获取Stream流
讲解:
1. 单列集合流的获取
* 集合对象.stream();
* Stream stream()
2. 数组流的获取
* 通过Stream接口静态方法获得,方法如下:
* static Stream of(T...t)
小结:
1. Stream流的获取方法有哪些?
单列集合对应的流:集合对象.stream();
数组对应的流:Stream.of(数组);
双列集合对应的流:不能直接获取,需要转换为单列集合,然后调用stream()方法
// 获得键集合对应的流
Stream keyStream = map.keySet().stream();
// 获得值集合对应的流
Stream valueStream = map.values().stream();
// 获得Entry集合对应的流
Stream> entryStream = map.entrySet().stream();
*/
public class Demo20 {
public static void main(String[] args) {
// List集合
List<String> list = new ArrayList<String>();
// 获得流对象
Stream<String> listStream = list.stream();
System.out.println(listStream);
// Set集合
Set<String> set = new HashSet<>();
Stream<String> setStream = set.stream();
System.out.println(setStream);
// Map集合
Map<String,String> map = new HashMap<>();
// 获得键集合对应的流
Stream<String> keyStream = map.keySet().stream();
// 获得值集合对应的流
Stream<String> valueStream = map.values().stream();
// 获得Entry集合对应的流
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
// 数组
String[] strs = {"a"};
Stream<String> stringStream = Stream.of(strs);
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4);
}
}
package com.itheima._21Stream流常用方法;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;
/**
目标:掌握Stream流的forEach方法的作用
讲解:
1. forEach方法的声明:
void forEach(Consumer action)
* Consumer:是一个接口,该接口的实现类对象称为消费者对象
* 遍历流的元素,将每个元素传递给指定的消费者
2. Consumer接口抽象方法
* void accept(T t);
* 接收一个参数,方法内部对参数进行处理。
3. 使用Stream流操作数据的步骤
* 获得stream流对象
* 调用stream流对象的方法处理元素
小结:
1. Stream的forEach方法的作用:遍历流的元素并传递指定消费者对象
*/
public class Demo14_01_ForEach {
public static void main(String[] args){
// 创建集合
List<String> list = new ArrayList<>();
// 添加元素
Collections.addAll(list, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
// 获得stream流对象
Stream<String> stream = list.stream();
// 对流中元素进行遍历
// 1. 使用匿名内部创建消费者对象
/*stream.forEach(new Consumer() {
@Override
public void accept(String s) {
System.out.println(s);
}
});*/
// 2. 使用lambda表达式简化
// void accept(T t);
stream.forEach( name -> System.out.println(name));
}
}
package com.itheima._21Stream流常用方法;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
目标:掌握Stream流的filter方法的作用
讲解:
1. filter方法声明如下:
* Stream filter(Predicate p)
* 对流中的元素进行过滤,会产生一个新的流
* Predicate是一个接口:用来封装过滤条件
2. Predicate接口抽象方法
* boolean test(T t);
* 用来执行过滤条件,返回true则代表元素会被过滤到新流中,否则不会被过滤。
小结:
1. Stream的filter方法的作用:将流中元素满足筛选条件的存储到新流中
*/
public class Demo14_02_Filter {
public static void main(String[] args){
// 创建集合
List<String> list = new ArrayList<>();
// 添加元素
Collections.addAll(list, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
// 获得stream流对象
Stream<String> stream = list.stream();
// 筛选名字是三个字的
// boolean test(T t);
// 1. 使用匿名内部类实现
/* Stream newStream = stream.filter(new Predicate() {
@Override
public boolean test(String name) {
return name.length() == 3;
}
});*/
// 2. 使用lambda简化
Stream<String> newStream = stream.filter(name -> name.length() == 3);
// 输出新流中的元素
newStream.forEach(name->System.out.println(name));
}
}
package com.itheima._21Stream流常用方法;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
/**
目标:掌握Stream流的limit方法的作用
讲解:
1. limit方法声明如下:
Stream limit(long n);
* 将当前流中的前n个元素获取到新流中
* n == 0:则会产生一个空流
* n 必须大于等于0
小结:
1. Stream的limit方法的作用:获得前n个元素到新流中
*/
public class Demo14_03_Limit {
public static void main(String[] args){
// 创建集合
List<String> list = new ArrayList<>();
// 添加元素
Collections.addAll(list, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
// 获得stream流对象
Stream<String> stream = list.stream();
// 获得前3个元素到新流中
Stream<String> newStream = stream.limit(3);
newStream.forEach(name->System.out.println(name));
}
}
package com.itheima._21Stream流常用方法;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
/**
目标:掌握Stream流的skip方法的作用
讲解:
1. skip方法声明如下:
Stream skip(long n)
* 将当前流中的前n个之后的元素获取到新流中
* n 必须大于等于0
* 如果n等于当前流元素个数,则会产生一个空流。
小结:
1. Stream的skip方法的作用:跳过前n个元素
*/
public class Demo14_04_Skip {
public static void main(String[] args){
// 创建集合
List<String> list = new ArrayList<>();
// 添加元素
Collections.addAll(list, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
// 获得stream流对象
Stream<String> stream = list.stream();
// 获得前3个元素之后的元素到新流中
Stream<String> newStream = stream.skip(3);
newStream.forEach(name->System.out.println(name));
}
}
package com.itheima._21Stream流常用方法;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;
/**
目标:掌握Stream流的map方法的作用
讲解:
1. map方法声明如下:
Stream map(Function f)
* 将当前流的元素从一种类型转换为另一种类型并存储到一个新流中
* Function是一个接口:将元素从一种类型换行为另一种类型
2. Function接口中抽象房方法
* R apply(T t) 执行类型转换操作:将参数t从类型T转换为R类型
小结:
1. Stream的map方法的作用:将元素从一种类型转换为另一种类型并存储新流中
*/
public class Demo14_05_Map {
public static void main(String[] args){
// 创建集合
List<String> list = new ArrayList<>();
// 添加元素
Collections.addAll(list, "11", "22", "33", "44", "55", "66");
// 获得stream流对象
Stream<String> stream = list.stream();
// 需求:将stream中的元素类型转换为整型存储到一个新流中
// R apply(T t)
/*Stream newStream = stream.map(new Function() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s);
}
});*/
Stream<Integer> newStream = stream.map(s -> Integer.parseInt(s));
newStream.forEach(num -> System.out.println(num + 1));
}
}
package com.itheima._21Stream流常用方法;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
/**
目标:掌握Stream流的concat方法的作用
讲解:
1. concat方法声明如下:
static Stream concat(Stream a,Stream b);
* 将流a和流b进行合并产生新流
小结:
1. Stream的concat方法的作用:合并流
*/
public class Demo14_06_Concat {
public static void main(String[] args){
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥");
List<String> two = new ArrayList<>();
Collections.addAll(two, "苏星河", "老子", "庄子", "孙子");
// 获得one集合的stream流对象
Stream<String> oneStream = one.stream();
// 获得two集合的stream流对象
Stream<String> twoStream = two.stream();
// 合并流
Stream<String> newStream = Stream.concat(oneStream, twoStream);
newStream.forEach(name->System.out.println(name));
}
}
package com.itheima._21Stream流常用方法;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
目标:掌握Stream流的count方法的作用
讲解:
1. count方法声明如下:
long count();
* 获得流中元素个数
小结:
1. Stream的count方法的作用:获得元素个数
*/
public class Demo14_07_Count {
public static void main(String[] args){
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥");
System.out.println(one.stream().count());// 2
}
}
package com.itheima._22Stream流注意事项;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
/**
目标:掌握stream流使用的注意事项
讲解:
1. 终结方法和非终结方法概述
终结方法:方法返回值不再是Stream则称为终结方法
非终结方法:方法返回值还是Stream则称为非终结方法
2. Stream流使用注意事项
流调用终结方法之后就不能在使用了。
流调用非终结方法产生新流,则后面只能操作新流了
小结
1.Stream流使用的注意事项有哪些
流调用终结方法之后就不能在使用了。
流调用非终结方法产生新流,则后面只能操作新流了
*/
public class Demo22 {
public static void main(String[] args){
// 创建集合
List<String> list = new ArrayList<>();
// 添加元素
Collections.addAll(list, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
// 获得stream流对象
Stream<String> stream = list.stream();
// 获得流元素个数
// System.out.println(stream.count()); // 6
// 遍历stream流元素
// stream.forEach(name->System.out.println(name));
// 获得前3个元素
Stream<String> newStream = stream.limit(3);
// 输出newStream流的元素个数
System.out.println(newStream.count()); // 3
// 输出stream流的元素个数
// System.out.println(stream.count()); // 6
}
}
package com.itheima._23Stream流综合案例;
/**
* @author pkxing
* @version 1.0
* @Package com.itheima._14Stream流常用方法
* @date 2019/4/23 下午12:00
*/
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
package com.itheima._23Stream流综合案例;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
需求说明:
现在有两个ArrayList集合存储队伍当中的多个成员姓名,
要求如下:
1. 使用传统的for循环(或增强for循环)依次进行以下若干操作步骤
1.第一个队伍只要名字为3个字的成员姓名;
2.第一个队伍筛选之后只要前3个人;
3.第二个队伍只要姓张的成员姓名;
4.第二个队伍筛选之后不要前2个人;
5.将两个队伍合并为一个队伍;
6.根据姓名创建 Person 对象;
7.打印整个队伍的Person对象信息。
*/
public class Demo231 {
public static void main(String[] args){
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公", "乔大峰", "欧阳锋");
List<String> two = new ArrayList<>();
Collections.addAll(two, "古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");
// 1. 使用传统的for循环(或增强for循环)依次进行以下若干操作步骤
// 创建集合存储满足条件元素
ArrayList<String> oneList = new ArrayList<>();
// 1.第一个队伍只要名字为3个字的成员姓名;
for (String name : one) {
if (name.length() == 3){
oneList.add(name);
}
}
ArrayList<String> oneoneList = new ArrayList<>();
// 2.第一个队伍筛选之后只要前3个人;
for (int i = 0; i < 3; i++) {
oneoneList.add(oneList.get(i));
}
ArrayList<String> twoList = new ArrayList<>();
// 3.第二个队伍只要姓张的成员姓名;
for (String name : two) {
if (name.startsWith("张")){
twoList.add(name);
}
}
ArrayList<String> twotwoList = new ArrayList<>();
// 4.第二个队伍筛选之后不要前2个人;
for (int i = 2; i < twoList.size(); i++) {
twotwoList.add(twoList.get(i));
}
// 5.将两个队伍合并为一个队伍;
ArrayList<String> allList = new ArrayList<>();
allList.addAll(oneoneList);
allList.addAll(twotwoList);
// 创建集合对象存储Person对象
ArrayList<Person> persons = new ArrayList<>();
// 6.根据姓名创建 Person 对象;
for (String name : allList) {
// 创建Person对象
Person p = new Person(name);
// 添加到集合中
persons.add(p);
}
// 7.打印整个队伍的Person对象信息。
for (Person person : persons) {
System.out.println(person);
}
}
}
package com.itheima._23Stream流综合案例;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
/**
需求说明:
现在有两个ArrayList集合存储队伍当中的多个成员姓名,
要求如下:
1. 使用传统的for循环(或增强for循环)依次进行以下若干操作步骤
2. 使用Stream方式依次进行以下若干操作步骤
1.第一个队伍只要名字为3个字的成员姓名;
2.第一个队伍筛选之后只要前3个人;
3.第二个队伍只要姓张的成员姓名;
4.第二个队伍筛选之后不要前2个人;
5.将两个队伍合并为一个队伍;
6.根据姓名创建 Person 对象;
7.打印整个队伍的Person对象信息。
*/
public class Demo23 {
public static void main(String[] args){
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公", "乔大峰", "欧阳锋");
List<String> two = new ArrayList<>();
Collections.addAll(two, "古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");
// 1.第一个队伍只要名字为3个字的成员姓名;
// 2.第一个队伍筛选之后只要前3个人;
// Predicate接口的抽象方法:boolean test(T t)
Stream<String> oneStream = one.stream().filter(name -> name.length() == 3).limit(3);
// 3.第二个队伍只要姓张的成员姓名;
// 4.第二个队伍筛选之后不要前2个人;
Stream<String> twoStream = two.stream().filter(name -> name.startsWith("张")).skip(2);
// 5.将两个队伍合并为一个队伍;
// 6.根据姓名创建 Person 对象
// Function接口抽象方法:Person apply(String t)
// 7.打印整个队伍的Person对象信息。
Stream.concat(oneStream, twoStream)
.map(name-> new Person(name))
.forEach(p -> System.out.println(p));
}
}
package com.itheima._24收集Stream流结果;
import java.util.*;
import java.util.function.IntFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
目标:收集Stream流的结果
讲解:
我们使用Stream可以对数据进行处理.处理后怎么办? count,forEach
将Stream流中的数据重新放回集合或数组中。
1. 将Stream流结果收集到集合中
* 收集到List集合中: 流对象.collect(Collectors.toList());
* 收集到Set集合中: 流对象.collect(Collectors.toSet());
2. 将Stream流结果收集到数组中
* 流对象.toArray()
* 流对象.toArray(数据类型[]::new)
*/
public class Demo24 {
public static void main(String[] args){
// 创建集合
List<String> list = new ArrayList<>();
// 添加元素
Collections.addAll(list, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
// 获得stream流对象
Stream<String> stream = list.stream();
// 收集流中元素到List集合中
// List newList = stream.collect(Collectors.toList());
// 收集流中元素到Set集合中
// Set set = stream.collect(Collectors.toSet());
// System.out.println(set.size());
// set.forEach(name -> System.out.println(name));
// 收集到数组中:对象数组
// Object[] objs = stream.toArray();
// System.out.println(Arrays.toString(objs));
// 收集到数组中:字符串数组
// toArray(IntFunction generator);
/* String[] strs = stream.toArray(new IntFunction() {
@Override
public String[] apply(int value) {
return new String[value];
}
});*/
// 使用lambda表达式简化匿名内部类
// String[] strs = stream.toArray(value -> new String[value]);
// 使用方法引用简化lambda表达式
// 构造方法引用
String[] strs = stream.toArray(String[]::new);
System.out.println(Arrays.toString(strs));
}
}
- 能够解释安全问题的出现的原因
多个线程同时对一个资源进行操作
- 能够使用同步代码块解决线程安全问题
synchronized(锁对象){
操作共享资源的代码
}
锁对象必须唯一,被所有线程共享
- 能够使用同步方法解决线程安全问题
修饰符 synchronized 返回值类型 方法名(){
操作共享资源的代码
}
静态同步方法锁对象:类名.class
非静态同步方法锁对象:this
- 能够说出线程6个状态的名称
新建状态
可运行状态
阻塞状态
无限等待状态
计时等待状态
死亡状态
- 能够理解等待唤醒案例
wait: 让当前线程释放CPU使用权,进入无限等待状态
notify: 随机唤醒一个正在等待的线程
- 能够描述Java中线程池运行原理
* 程序启动时创建一定数量的线程存储容器中
* 当有任务需要执行时从容器中获得线程使用
* 当线程执行完毕任务将线程放回池中等待复用
- 能够理解函数式编程相对于面向对象的优点
lambda表达式:专注做什么,而不是怎么做
- 能够掌握Lambda表达式的标准格式
(数据类型 变量名,...) -> {
return 返回值;
}
- 能够掌握Lambda表达式的省略格式与规则
规则:数据类型可以省略,只有一个参数时小括号可以省略
只有一条语句时,大括号可以省略,如果省略的大括号,则return关键字和分号必须省略。
- 能够明确Lambda的两项使用前提
必须是接口
接口有且只有一个抽象方法
接口必须是函数式接口
- 能够理解流与集合相比的优点
流的作用:对集合元素进行加工处理
集合的作用:用于存储元素
- 能够通过集合、映射或数组方式获取流
集合对象.stream();
Stream.of(数组)
- 能够掌握常用的流操作
forEach 遍历
limit 获取前n个
skip 跳过前n个
count 获得个数
map 类型转换
concat 流合并
filter 筛选
- 能够将流中的内容收集到集合中
流对象.collect(Collectors.toList())
流对象.collect(Collectors.toSet())
- 能够将流中的内容收集到数组中
Object[] 流对象.toArray();
数据类型[] 流对象.toArray(数据类型[]::new);
## 26.函数式接口 @FunctionalInterface 这个接口有且只有一个抽象方法 可以使用lambda
点赞评论哦O(∩_∩)O