复习
集合结构总结
Collection : 集合层次的跟接口
List : 有序 有下标 可重复
- ArrayList:底层结构是数组,内存空间是连续的 常用于下标操作,查询较快
- LinkedList:底层结构是链表,内存空间是不连续的 常用于首尾操作,增删较快
Set : 无序 无下标 不可重复,常用于去重
-
Map : 键值对, key&value, <k,v>, 映射关系,Entry
Map.Entry<,>
-
map中的key不允许重复,value可以重复
-
HashMap
-
结构是数组+链表 或者 数组+红黑树 的形式
-
HashMap底层的Entry[ ]数组,初始容量为16,加载因子是0.75f,扩容按约为2倍扩容
-
存放数据时,会根据hash(key)%n算法来计算数据的存放位置,n就是数组的长度,其实也就是集合的容量
-
当计算到的位置之前没有存过数据的时候,会直接存放数据
-
当计算的位置,有数据时,会发生hash冲突/hash碰撞
解决的办法就是采用链表的结构,在数组中指定位置处以后元素之后插入新的元素
也就是说数组中的元素都是最早加入的节点
-
如果链表的长度>8且数组长度>64时,链表会转为红黑树,当链表的长度<6时,红黑树会重新恢复成链表
-
package cn.tedu.review;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/**一 本类用于练习map案例 统计字符串中 字符的个数
*
* 需求:用户输入aabbcc 输出 {a=2,b=2,c=2}**/
public class TestMap {
public static void main(String[] args) {
//1.提示用户输入字符串并接收
System.out.println("请输入字符串");
String input = new Scanner(System.in).nextLine();
//2.要统计字符串中 字符的个数,存在映射关系 用map集合
//为什么要使用字符作为key? 因为map中的key不可以重复
//也就是说 字符不允许重复 但字符出现的次数可以重复
Map<Character,Integer> map = new HashMap<>();
System.out.println(map);//map中现在没有数据 需要存入
//3.准备向map中存入数据
//3.1遍历用户输入的字符串 拿到每个字符
for(int i= 0;i<input.length();i++){
char key = input.charAt(i);
System.out.println(key);//打印查看每轮循环获取的字母对不对 可以在每一个小任务完成以后输出一下进行测试
//3.2 根据刚刚获取到的key拿到对应的value
Integer value = map.get(key);
if(value==null){//之前这个字母没有出现过,次数还是integer类型的默认值null
map.put(key,1);//没有出现过 次数设置为1
}else{//value不是null 说明之前已经存过次数了
map.put(key,value+1);//出现过 次数就设置为之前次数+1
}
}
System.out.println("各个字符出现的次数为:"+map);
}
}
进程 线程
进程
-
概念:
进程就是正在运行的程序 , 给程序加入了时间概念, 是动态的,由CPU进行执行与计算。
-
特点:
- 独立性
- 动态性
- 并发性
-
什么是程序? 进程与程序的区别?
答: 程序:数据和指令的集合
区别: 进程动态的 程序静态的
线程 Thread
-
概念:
线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.
-
进程与线程关系
一个进程可以包含多个线程,也可以包含一个线程
只有一个线程的进程叫做单线程程序
-
每个线程在共享同一个进程中的内存的同时,又有自己独立的内存空间.
多线程的特性
-
随机性
一个CPU【单核】只能执行一个进程中的一个线程。
因为CPU以纳秒级别甚至是更快的速度高效切换着,超过了人的反应速度,这使得各个进程从看起来是同时进行的,也就是说,宏观层面上,所有的进程看似
【同时运行】,但是微观层面上是串行的【同一时刻,一个CPU只能处理一件事】。
-
串行与并行
串行是指同一时刻一个CPU只能处理一件事,类似于单车道
并行是指同一时刻多个CPU可以处理多件事,类似于多车道
-
cpu分时调度
-
时间片,即CPU分配给各个线程的一个时间段,称作它的时间片,即该线程被允许运行的时间,如果在时间片用完时线程还在执行,那CPU将被剥夺并分配给另
一个线程,将当前线程挂起,如果线程在时间片用完之前阻塞或结束,则CPU当即进行切换,从而避免CPU资源浪费,当再次切换到之前挂起的线程,恢复现场,继
续执行。
注意:我们无法控制OS选择执行哪些线程,OS底层有自己规则,如:
-
FCFS(First Come First Service 先来先服务算法) : 先来的就先处理完在处理后来的
-
SJS(Short Job Service短服务算法) : 不管多会来的,只要需要的时间片比正在执行的进程剩余所需的时间片短就先执行
-
-
线程的状态
线程的三种基础状态及其转换,简称”三态模型” :
-
就绪(可运行)状态:线程已经准备好运行,只要获得CPU,就可立即执行
-
执行(运行)状态:线程已经获得CPU,其程序正在运行的状态
-
阻塞状态:正在运行的线程由于某些事件(I/O请求等)暂时无法执行的状态,即线程执行阻塞
-
就绪 → 执行:为就绪线程分配CPU即可变为执行状态"
执行 → 就绪:正在执行的线程由于时间片用完被剥夺CPU暂停执行,就变为就绪状态
执行 → 阻塞:由于发生某事件,使正在执行的线程受阻,无法执行,则由执行变为阻塞
(例如线程正在访问临界资源,而资源正在被其他线程访问)
反之,如果获得了之前需要的资源,则由阻塞变为就绪状态,等待分配CPU再次执行
-
-
我们可以再添加两种状态:
-
创建状态:线程的创建比较复杂,需要先申请PCB,然后为该线程运行分配必须的资源,并将该线程转为就绪状态插入到就绪队列中
- 终止状态:等待OS进行善后处理,最后将PCB清零,并将PCB返回给系统
-
线程状态与代码对照
线程生命周期,主要有五种状态:
-
新建状态(New) : 当线程对象创建后就进入了新建状态.如:Thread t = new MyThread();
-
就绪状态(Runnable):当调用线程对象的start()方法,线程即为进入就绪状态.
处于就绪(可运行)状态的线程,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了t.start()此线程立即就会执行
-
运行状态(Running):当CPU调度了处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态
就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态状态执行,先得处于就绪状态
-
阻塞状态(Blocked):处于运状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执
行.
-
根据阻塞状态产生的原因不同,阻塞状态又可以细分成三种:
-
等待阻塞:运行状态中的线程执行wait()方法,本线程进入到等待阻塞状态
-
同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态
-
其他阻塞:调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态.当sleep()状态超时.join()等待线程终止或者超时或者I/O处理完毕时线程重新转入就绪状态
-
-
-
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
多线程代码创建方式1:继承Thread
步骤:
- 定义自己的多线程类TicketThread,并且继承Thread
- 重写父类的run(),里面是我们自己的业务
- 创建多个自定义线程类对象
- 通过线程对象.start()将线程加入到就绪队列中
- 查看多线程抢占资源的效果
构造方法摘要:
- Thread() 创建一个新的线程对象,名字是系统自定义的
- Thread(String name) 与上面功能一致,还可以自定义线程名
- 可以通过调用父类Thread的含参构造Thread(String name)
- 给自定义线程对象起名字,调用方式:super(name);
多线程代码创建方式2:实现Runnable
-
定义自己的业务类,并且实现接口Runnable
-
在业务类中添加接口里的抽象方法run(),并实现业务
-
创建唯一的业务类对象
-
创建多个Thread类的对象,作为多个线程对象
并将刚刚的业务对象传入
-
使用多个线程类对象调用start(),将线程加入到就绪队列之中
-
查看多线程抢占资源的效果
构造方法摘要
- Thread(Runnable target) 创建一个线程对象,参数为Runnable实现类的对象
- Thread(Runnable target, String name) 与上面功能一致,还可以自定义线程名