1.List的LinkedList子实现类
特点:线程不安全,执行效率高。底层用链表实现,查询慢,增删快。
特有功能:
-
public void addFirst(Object e):在列表开头插入元素
-
public void addLast(Object e):将元素追加到列表的末尾
-
public Object getFirst():获取列表的第一个元素
-
public Object getLast():获取列表的最后一个元素
-
public Object removeFirst(): 删除列表的第一个元素,并获取第一个元素
-
public Object removeLast():删除列表的最后一个元素,并获取最后一个元素
常根据其功能特点,模拟栈的结构特点
import java.util.LinkedList;
//泛型:定义类上,定义在方法上,定义接口上
public class MyStack<T> {
//成员变量,声明LinkedList类型变量
private LinkedList<T> link ; //String
//提供无参构造方法
public MyStack(){
//给link初始化
link = new LinkedList<>() ;
}
//提供添加的功能,利用LinkedList的addFirst()
public void set(T t){//String
link.addFirst(t);//每一次往列表的开头添加元素
}
//提供删除的功能,利用LinkedList的removeFirst():删除并返回第一个元素
public <T> T get(){//将泛型定义在方法上 //获取的元素 String类型
return (T) link.removeFirst(); //removeFirst()---->本身Object类型
}
//提供一个判断功能
public boolean isEmpty(){
return link.isEmpty() ;
}
}
2.泛型的高级通配符
<?> :任意Java类型,包括Object <? super E> : 向上限定:E类型以及E父类 <? extends E> : 向下限定:E以及它的子类 ## 3.Set集合 特点:无序(存储和取出不一致),能够保证元素唯一。 ### 3.1 HashSet:底层数据结构基于哈希表(桶结构) 线程不安全的类,即不同步,执行效率高。 JDK8以后;提供了juc(并发包:java.util.concurrent):ConcurrentHashMap-
public static void shuffle(List<?> list):随机置换
public class CollectionsDemo1 {
public static void main(String[] args) {
//创建List集合
List<Integer> list = new ArrayList<>() ;
//添加元素
list.add(10) ;
list.add(50) ;
list.add(15) ;
list.add(25) ;
list.add(5) ;
list.add(12) ;
System.out.println(list);
System.out.println("---------------------------------");
//public static <T extends Comparable<? super T>> void sort(List<T> list):
Collections.sort(list);
System.out.println(list);
System.out.println("----------------------------------");
//public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T>
Integer max = Collections.max(list);
System.out.println(max);
System.out.println("----------------------------------");
System.out.println(Collections.min(list));
System.out.println("-----------------------------------");
Collections.reverse(list);//反转
System.out.println(list);
System.out.println("------------------------------------");
// public static void shuffle(List<?> list):随机置换
Collections.shuffle(list);
System.out.println(list);
}
}
9.利用Collection集合和Map集合模拟斗地主发牌
import java.util.*;
public class PokerTest {
public static void main(String[] args) {
String[] strNum = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
String[] strPattern = {"♥","♠","♣","♦"};
//牌盒
Map<Integer, String> map = new HashMap<>();
int index = 0;
for (int x = 0 ; x < strNum.length ; x ++ ){
for (int y = 0 ; y < strPattern.length ; y ++){
map.put(index,strNum[x].concat(strPattern[y]));
index++;
}
}
map.put(index,"小王") ;
map.put(index+1,"大王") ;
//洗牌
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 54; i++) {
list.add(i);
}
Collections.shuffle(list);
//发牌
Set<Integer> player1 = new TreeSet<>();
Set<Integer> player2 = new TreeSet<>();
Set<Integer> player3 = new TreeSet<>();
Set<Integer> dipai = new TreeSet<>();
for (int i = 0 ; i < list.size()-3; i+=3){
player1.add(list.get(i));
player2.add(list.get(i+1));
player3.add(list.get(i+2));
}
dipai.add(list.get(list.size()-1));
dipai.add(list.get(list.size()-2));
dipai.add(list.get(list.size()-3));
//看牌
lookPoker("player1",player1,map) ;
lookPoker("player2",player2,map) ;
lookPoker("player3",player3,map) ;
lookPoker("底牌",dipai,map) ;
}
private static void lookPoker(String name,Set<Integer> list,Map<Integer, String> map) {
System.out.print(name+"的牌:");
for (Integer key : list){
System.out.print(map.get(key)+" ");
}
System.out.println();
}
}
10.System类
System类不能被实例化!!!
System提供了包括标准输出、输入、错误输出流; 访问外部定义的属性和环境变量; 加载文件和库的方法; 以及用于快速复制阵列的一部分的实用方法。
-
public static final InputStream in:标准输入流
-
public static final PrintStream out:标准输出流
-
public static final PrintStream err:错误输出流(打印错误信息/一些信息需要用户引起注意:相关的日志)
-
System.exit(0) :jvm退出 ,0表示正常终止
-
public static void gc():手动开启垃圾回收器,会去回收内存中没有更多引用的对象!
等
public class SystemDemo {
public static void main(String[] args) throws IOException {
// System.err.println("这是错误输出流");
InputStream in = System.in ; //字节输出流
//键盘录入的就可以用到Scanner / BufferedRead 字符缓冲输入流:读数据
Scanner sc = new Scanner(in) ; //里面传递参数InputStream参数
System.out.println("helloworld");
System.out.println("--------------------------------------");
PrintStream ps = System.out ; //字节打印流
//println(任何数据类型):PrintStream流中的一些工
ps.println(10);
ps.println(12.56);
System.out.println("----------------------------------------");
//创建Person对象
Person p = new Person("高圆圆",41) ;
System.out.println(p.getName()+"---"+p.getAge());
p = null ;
//手动垃圾回收器(不需要手动开启垃圾回收器,自动回收)!
System.gc() ;
//protected void finalize() throws Throwable:需要通过jvm启动垃圾回收器回收没有更多引用的对象
/*int num = Runtime.getRuntime().availableProcessors();
System.out.println(num);*/
// Runtime.getRuntime().exec("notepad") ;
// Runtime.getRuntime().exec("calc") ;
}
}
11.Java异常
异常指程序出现错误或者警告异常信息,表明当前书写程序存在语法或逻辑错误!
java的异常类,Throwable:包含所有的错误以及异常!是所有异常类的超类(父类)
11.1.异常类别:
error:
非常严重问题 (跟代码没有太大有关系)OOM Out Of Memory:内存溢出 (严重问题)
例如在生活中 “地震了,不可抗力的因素”,表示计算机中一般由硬件条件所引起的异错误。
Exception:
编译时期异常和运行时期异常(RuntimeException):程序在运行过程中出现问题(代码书写不严谨)只要不是RuntimeException的子类都是属于编译时期异常。
编译时期异常: 在程序,运行前需要检查的! 在生活中 “长途旅行之前,检查你的车胎情况”
运行时期异常:在程序.程序代码逻辑问题(代码不严谨) 在生活中 "too young ,too simple "
11.2.异常的处理方式
标准格式:try...catch...finally
变形格式: try{
//可能出现问题的代码
}catch(异常类名 变量名){
//处理异常
}
try{
//可能出现问题的代码
}catch(异常类名 变量名){
//处理异常1
}catch(异常类名 变量名){
//处理异常2
}
//多线程:jdk5以后:Lock:接口 (锁:可重复入的互斥锁)
try{
//可能出现问题的代码
}finally{
//释放资源(系统资源)
}
throws:抛出
public class ExceptionDemo1 {
public static void main(String[] args) {
//定义两个变量
/* int a = 10 ;
int b = 0 ;
System.out.println(a/b); //jvm在内存中会创建当前异常类的对象 new ArithmeticException()*/
//使用try...catch进行捕获异常
try{
//可能出现问题代码
int a = 10 ;
int b = 0 ; //直接获取到的,以后可能值---->通过一些方法获取到的值
System.out.println(a/b);
System.out.println("over");
}catch(ArithmeticException e){ //捕获异常:可以使用大的Exception,但是捕获:具体异常具体捕获
System.out.println("除数不能为0");
}
}
}
11.3.用try…catch去处理多个异常
jdk7以后提供捕获多个异常时的操作
try{
//可能出现问题的代码
}catch(异常类名1 | 异常类名2 | 异常类名3 …变量名){ //异常类名必须为同级别
//处理异常
}
public class ExceptionDemo2 {
public static void main(String[] args) {
//method1() ;
// method2() ;
//method3() ;
method4() ;
}
private static void method4() {
try {
int a = 10;
int b = 0;
int[] arr = {4, 5, 6};
System.out.println(a / b);
System.out.println(arr[0]);
System.out.println(arr[3]);
} catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {//(ArithmeticException算数异常 / ArrayIndexOutOfBoundsException数组索引越界异常)
System.out.println("程序出问题了...");
}
}
//使用trt...catch...catch...catch...不能将大的异常放在最前面
private static void method3() {
try {
int a = 10;
int b = 1; //可能为0
int[] arr = {4, 5, 6};
arr = null; //空值
System.out.println(a / b);
System.out.println(arr[0]);
System.out.println(arr[3]);
} catch (Exception e) {//包含了所有 编译时期/运行时期异常
System.out.println("程序出问题了...");
}
/* }catch (ArrayIndexOutOfBoundsException e){
System.out.println("访问了数组中不存在的索引");
}catch(ArithmeticException e){// 通过jvm创建当前异常类对象,如果当前e类型匹配就会catch语句
System.out.println("除数不能为0");
}*/
}
//针对多个异常进统一处理,try...catch...catch
private static void method2() {
try{
int a = 10;
int b = 1 ; //可能为0
int[] arr = {4,5,6} ;
arr = null ; //空值
System.out.println(a/b);
System.out.println(arr[0]);
System.out.println(arr[3]);
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("访问了数组中不存在的索引");
}catch(ArithmeticException e){// 通过jvm创建当前异常类对象,如果当前e类型匹配就会catch语句
System.out.println("除数不能为0");
}catch (Exception e){
System.out.println("程序出问题了...");
}
}
//分别针对可能出现问题的代码,进行try...catch(不推荐)
private static void method1() {
try{
int a = 10;
int b = 0 ;
System.out.println(a/b);
}catch (ArithmeticException e){
System.out.println("除数不能为0");
}
//创建一个数组
try{
int[] arr = {4,5,6} ;
System.out.println(arr[0]);
System.out.println(arr[3]);
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("访问了数组中不存在索引");
}
}
}
11.4.编译时期和运行时期异常的区别?
RuntimeException:运行时期异常
子类包括:NullPointException、ClassCastException、ArrayIndexOfOutofBoundsException…
一般程序员逻辑结构不严谨导致的问题,调用者可以进行显示处理(try…catch…/throws)也可以不进行显示处理,通过逻辑语句进行处理!
编译时期异常:
调用者必须显示处理,不处理,编译通过不了,程序运行不了。 如果在当前方法中已经去捕获了try…catch…,调用者无序进行处理, 但是如果在方法中抛出异常的,调用者必须处理(捕获/抛出throws)。
建议:首先使用try…catch…其次在使用throws
11.5关于try…catch…和throws
1)有的时候没有办法去抛出,继承关系中,如果子类继承父类, 父类的该方法没有异常,子类重写该方法的时候,某段代码有异常情况,则只能try…catch
2)子类继承父类,如果父类的该方法本身抛出异常了,那么子类重写该方法的时候,要么跟父类的方法的异常类名一致,要么是该异常的类子类!
public class ExceptionDemo4 {
public static void main(String[] args) {
}
}
class Father{
public void show(){}
public void method() throws ArrayIndexOutOfBoundsException{}
}
class Son extends Father{
/* @Override
public void method() throws Exception { //要么跟他父类的方法异常类名一致,要么是异常类名的子类
}*/
@Override
public void method() throws ArrayIndexOutOfBoundsException { //要么跟他父类的方法异常类名一致,要么是异常类名的子类
}
public void show() {
//将String--->Date
try{
String source = "2021-8-3" ;
//创建SimpleDateFormat对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd") ;
//解析
Date date = sdf.parse(source) ;
System.out.println(date);
}catch (ParseException e){
System.out.println("解析出问题了...");
}
}
}
11.6throws和throw的区别(面试)
共同点:都是抛出。
区别
用法不同:
1)使用位置不同
throws: a)将异常抛出在方法声明上。
b)在方法名的后面可以跟多个异常类名,中间逗号隔开!
throw:a)在方法的语句体中某个逻辑语句中
b)它后面只能跟异常对象,而不是类名
2)调用者是否处理不同
throws:调用者必须进行显示处理(try...catch/throws(继续抛出)),否则报错
throw:调用者无须显示处理,一般情况都是在逻辑语句进行处理
3)出现异常是否肯定性
throws:在方法上的,执行某个方法的代码中,可能有问题(表示出现异常的一种可能性)
throw:执行某段代码一定会执行这个异常(表示出现异常的一种肯定性)
4)处理方式不同
throws:将具体的处理交给jvm---通过jvm吧异常信息打印控制台上。显示的底层源码 而且会显示当前错误消息字符串
throw:程序 中某段代码有问题:只是打印异常类名(jvm处理)
public class ExceptionDemo5 {
public static void main(String[] args) throws ParseException {
//
// try {
// method();
// } catch (ParseException e) {
// System.out.println("解析问题了...");
// }
method();
// method2();
}
//throw
private static void method2() throws ArithmeticException {
int a = 10 ;
int b = 0 ;
//逻辑语句
if(b!=0){
System.out.println(a/b);
}else{
//抛出一个对象
//throw 匿名对象 new XXXException() ;
throw new ArithmeticException() ;
}
}
//throws
private static void method() throws ParseException {
//将String--->Date
String source = "2021-8-3" ;
//创建SimpleDateFormat对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd ") ;
//解析
Date date = sdf.parse(source) ;
System.out.println(date);
}
}
11.7异常实现底层原理
不管使用throws/try…catch…finally:都要通过jvm调用Throwable类当中的功能完成错误信息的打印
try{
//可能出现问题的代码 //首先加载这些代码 (语法校验)
}catch(异常类名 变量名){
//如果存在问题,那么jvm在内存中创建异常的实例,实例是否为catch语句中类型的实例
//类似于 a instanceOf 引用类型:如果是就会执行catch语句
异常处理
//要么手动处理/要么通过jvm进行处理(打印错误日志信息)
}finally{
释放资源...
}
Exception继承自Throwable
public String getMessage()获取详细消息字符串
同义的方法:public String getLocalizedMessage()
public String toString():打印出当前异常信息的简短描述:
格式: 内存中异常对象的类名:(全限定名称:包名.类名) 换行
“:”(一个冒号和一个空格) 换行
public String getMessage():消息字符串
//public void printStackTrace():跟踪堆栈:包含toString以及底层原码(某行代码出现问题)以及本类中的代码问题,都是体现方法上(将当前跟方法相关的底层方法全部跟踪一遍)
finally:不能单独使用,它是结合try...catch....finally:异常的标准格式
finally用法特点:
释放相关的资源,代码一定执行的。除非在执行finally,jvm退 出了!
11.8.面试题,如果在某个方法中捕获异常,但是该方法有返回值类型,如果在catch语句出现return语句,finally代码还会执行吗?
finally会执行,目前这个代码,在catch语句已经形成返回路径,它会记录并在执行完finally当中的语句后按照记录的最终返回路径返回结果;finally是去释放资源用的。除非jvm退出(System.exit(0))。
public class Test {
public static void main(String[] args) {
int num = getNum(10) ;// i=10
System.out.println(num);
}
private static int getNum(int i) {
try{
i = 20 ; //i = 20 ;
System.out.println(i/0); //除数为0
}catch (ArithmeticException e){
i = 30 ; //i =30
return i ; // return i = return 30 :已经在catch语句形成返回的路径 返回结果就是30
}finally { //finally代码一定会执行,除非jvm退出了
i = 40 ; // i = 40
// return i ;
}
return i; //30
}
}
11.二分法查找
数组高级查找算法之二分搜索算法
前提条件数组必须有序,如果数组本身无序,让我们查询元素,先排序再去查找!(有条件需求先排序,再查如果没有条件需求,只能使用基本元素查找法:从头查找到尾)
手动书写或者通过Arrays的sort(任何数组进行二分法查找)
Arrays工具类:
里面提供排序sort(任何数组进行排序) :元素升序排序 Integer,String
里面提供二分搜素法binarySerach(任何类型进行查询 ,int key)
手动实现
public class BinarySearch {
public static void main(String[] args) {
//已知数组,静态初始化
int[] arr = {11,22,33,44,55} ;
//调用二分搜索方法查询
int index = binarySearch(arr, 22);
System.out.println(index);
int index2 = binarySearch(arr,66) ;
System.out.println(index2);
int index3 = binarySearch(arr,44) ;
System.out.println(index3);
System.out.println("-----------------------------------");
int index4 = Arrays.binarySearch(arr, 55);
System.out.println(index4);
int index5 = Arrays.binarySearch(arr, 66);
System.out.println(index5);
}
//返回值int
//方法参数:数组,查询的元素
public static int binarySearch(int[] arr,int target){
//防止空指针异常
if(arr!=null){
//定义数组的最小索引:
int min = 0 ;
//定义最大索引
int max = arr.length -1 ;
//使用循环while
while(min<=max){
//计算中位点索引
int mid = (min+max)/2 ;
//如果当前中位点对应的元素小于要要查找的元素
if(target < arr[mid]){
//左半区域:继续折半
max = mid -1 ;
}else if(target > arr[mid]){
//右边区域:继续折半
min = mid + 1 ;
}else{
//查询到了
return mid ;
}
}
}
//循环结束之后,还没有找,则返回-1
return -1 ;
}
}
12.线程和进程
线程是依赖于进程的
进程:
能够调用的系统资源的独立单位!
理解:计算机---->打开任务管理器---->客户端软件---->应用进程
计算机开启启动 ---->服务进程(后台进程)
现在的计算机----"多进程计算机"意义?
主要为了提高CPU的使用率,
在玩游戏的同时,还可以听音乐----->开启游戏的进程,音乐软件的进程 是同时的吗?
不是同时,在一点点(时间片),在两个进程进行高效切换!
线程 :
属于程序中执行的最小单元(进程中的一条任务线)
一个进程有多个线程组成,多个线程----->线程组(ThreadGroup)
开启一个360软件---->360进程
杀毒的同时,还可以清理垃圾/安装系统/卸载软件...
多线程的意义?
多线程的特点:具有随机性
多个线程在抢占CPU的执行权
举例:
1v3 打篮球,只能3个人抢占篮球的几率大,并不一定这个3个人就一直 能够抢占篮球
有可能1个人抢占的篮球几率大(线程的执行具有随机性)
面试题:
jvm是多线程吗?
是多线程:
至少有两条线程
用户线程main,以及创建对象的时候,当对象使用完毕,需要被垃圾回收器回收;
jvm针对没有更多引用对象,开启一条垃圾回收线程!
java语言能够开启多线程?
开启线程---->开启进程----Java语言不能够开启进程---借助于底层语言C语言开启进程
封装成本地方法 ----->jdk提供类:Thread 里面封装好的方法
开启线程:start()
线程有几种状态
六种:创建,就绪,运行,阻塞(等待/超时等待),死亡。
Thread类内部枚举类
public enum State{
NEW, 新建 (未执行)
RUNNABLE, 运行状态 (可能出现阻塞的情况)
BLOCKED, 线程阻塞状态(wait,sleep…)
WAITTING, 死死等待(底层调用wait()方法)
TIMED_WAITTING, 超时等待(直接调用wait(long timeout))
TERMINATED; 线程终止死亡状态(线程执行完毕)
}
12.1线程的方法和优先级
Thread类的构造方法:
Thread(String name…(其他参数)):创建线程类对象,设置名称
Thread类的成员方法
public final String getName():获取线程名称
public final void setName(String name):设置线程名称
线程的优先级
Thread类中静态常量字段(成员变量field)
public static final int MAX_PRIORITY 10 最大优先级
public static final int MIN_PRIORITY 1 最小优先级
public static final int NORM_PRIORITY 5 默认优先级
public final void setPriority(int newPriority):设置线程的优先级
public final int getPriority():获取优先级
优先级越大的:抢占CPU的执行权越大
优先级小的:抢占CPU的执行权越小
默认优先级:随机性大一些
public class ThreadDemo2 {
public static void main(String[] args) {//用户线程
//创建MyThread2类的对象
MyThread2 t1 = new MyThread2() ;
MyThread2 t2 = new MyThread2() ;
MyThread2 t3 = new MyThread2() ;
// public final void setName(String name):设置线程名称
t1.setName("洪学佳") ;
t2.setName("张俊杰") ;
t3.setName("高圆圆");
t1.setPriority(10); //最大优先级
t2.setPriority(1);//最小优先级
int num1 = t1.getPriority();
int num2 = t2.getPriority();
int num3 = t3.getPriority();
System.out.println(num1+"---"+num2+"---"+num3);
//启动线程
t1.start();
t2.start();
t3.start();
}
}
12.2.守护线程、join线程、yield礼让线程
public final void setDaemon(boolean on)
参数为true,表示标记当前线程为守护线程,当正在运行的线程如果都是守护线程,则jvm自动退出这个方法必须在启动线程之前调用(start()之前)
守护线程会在其他线程运行完毕之后,随着jvm的退出而自动退出。
public final void join() throws InterruptedException:
等待该线程终止!简而言之,如果该线程出现,就像是vip一样,插队运行,只有当join线程运行完毕之后,其他线程才可以运行。但是需要在线程启动之后设置。
public static void yield():暂停当前正在执行的线程,礼让线程。
当有线程来的时候,当前线程如果是礼让线程,则就暂定当前的线程,二者继续发生抢占,有时候也会出现礼让不成功的情况(给你机会你也不中用啊!!!)
12.3线程的实现方式
方式1:
1)将一个类声明为Thread的子类
- 这个子类应该重写Thread类的run方法
3)然后可以分配并启动子类的实例。启动线程用的start()而不是run()
//线程1
public class MyThread extends Thread {
//重写Thread类的方法
@Override
public void run() {
//run方法里面:一般情况耗时的操作
for(int x = 0 ; x < 200 ; x ++){
System.out.println(x);
}
}
}
//线程2
public class MyThread2 extends Thread {
//t1,t2
@Override
public void run() {
for(int x = 0 ; x < 100 ; x ++){
//获取线程名称
System.out.println(this.getName()+":"+x);
}
}
//测试类
public class ThreadDemo {
public static void main(String[] args) {
//3)创建Thread类的子类对象
MyThread my1 = new MyThread() ;//第一个线程对象
MyThread my2 = new MyThread() ; //第二个线程对象
//4)启动
//my1.run();
//my2.run();
/* my1.start();
my1.start();
my1只是代表一个线程对象
my1将start方法调用两次---->IllegalThreadStateException:非法线程状态异常
start()原码: 校验当前线程状态:如果线程已经启动了,就不能再启动
*/
my1.start();//start():有jvm调用底层run方法,出现并发执行
my2.start();
}
}
方式2:
1)自定义类实现Runnable接口
2)重写Runnable接口的run方法
3)在main用户线程中可以分配类的实例(创建类的实例)
4)创建当前类对象,然后创建Thread类对象,将当前类对象作为参数来传递
5)分别启动线程即可
//线程
public class MyRunnable implements Runnable {
@Override
public void run() {
//耗时的操作
for(int x = 0 ; x < 100 ; x ++){
//public static Thread currentThread()
System.out.println(Thread.currentThread().getName()+":"+x);
}
}
}
//测试类
public class ThreadDemo {
public static void main(String[] args) {
//可以分配类的实例(创建类的实例)
MyRunnable my = new MyRunnable() ; //资源类:被多线程共享//具体类new 具体类
//创建两个线程类对象
Thread t1 = new Thread(my,"张俊杰") ;
Thread t2 = new Thread(my,"高圆圆") ;
//分别启动线程
t1.start();
t2.start();
}
}
12.4静态代理
特点:真实角色和代理角色必须实现同一个接口,真实角色:专注于自己的功能, 代理角色:完成对真实角色功能的"增强"。
例如: 结婚这个这个情况,真实角色:You 你自己,代理角色:WeddingCompany 婚庆公司,你只管在婚礼现场进行仪式即可,然后付款。结婚现场布置,灯光,酒席等婚庆公司完成即可。
12.5安全问题
线程运行过程中一般需要进行加锁,尤其是要进行修改共享数据的时候,不加锁则会造成严重安全问题,例如,售卖火车票时,火车票就是各个售卖窗口的共享资源,不加锁就会出现同票的情况。但是在读取数据的时候,共享数据是不变的,就不必进行加锁。
检验线程安全的标准
1)是否是多线程环境
2)是否存在共享数据
3)是否存在多条语句对共享数据的操作
解决方式:
Java提供同步机制:同步代码块 将多条对共享数据包裹起来
synchronized(锁对象){
将多条对共享数据的操作包裹起来
}
锁对象:必须要多个线程使用的同一个锁对象,而不是分别自己的锁对象!一般是Object对象。
//资源类
public class SellTicket implements Runnable {
//成员变量;100张票
public static int tickests = 100 ;
//创建一个锁对象:
public Object obj = new Object() ;
//t1,t2,t3
@Override
public void run() {
//模拟一直票
while(true){
//t1,t2,t3
//解决方案:
//将多条语句对共享数据的操作包裹起来
//synchronized (new Object()){ //锁对象 :三个线程分别使用自己的锁
//必须为是同一个锁对象
synchronized (obj){
//模拟网络延迟
//判断
if(tickests>0){//100>0
//t1先进来,睡眠150毫秒,t1已经睡完了,执行下面的操作
//t3先进来,睡眠150毫秒,t3醒来之后
//t2最后抢占到,醒来之后
try {
Thread.sleep(100); //单位为毫秒数
} catch (InterruptedException e) {
e.printStackTrace();
}
//输出窗口信息
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickests--)+"张票");
}
}
/**
* 出现同票: 线程的原子性操作(++,--:最简单的操作)
* 原子性:记录原始数据值,然后再对数据自增或者自减
* t1执行的操作, 窗口1正在出售第 23张票 ,当准备--的时候,t3将t1里面tickets--还没执行的时候,就已经打印了
* 窗口3正在出售第23张票
*
* t2进来之后,tickets-- 动作完成了 22张
* 窗口2正在出售第22张票
*
*
* 出现负票: 线程的执行具有随机性, ---加入延迟!
*
* 窗口1正在出售第1张票,延迟睡眠150秒 t3醒来之后
* 窗口3正在出售第0张票, --
* t2也在某一刻同时醒来并执行语句---- 窗口2正在出售第-1张票
*
*/
}
}
}
//测试类
public class SellTicketTest {
public static void main(String[] args) {
//创建资源类对象SellTicket
SellTicket st = new SellTicket() ;
//创建三个线程类对象
Thread t1 = new Thread(st,"窗口1") ;
Thread t2 = new Thread(st,"窗口2") ;
Thread t3 = new Thread(st,"窗口3") ;
//分别启动线程
t1.start();
t2.start();
t3.start();
}
}
12.6面试题,wait()方法/notify()方法这些方法为什么不定义在Thread类中呢,而是定义Object类中呢?
它和锁对象有关系,而锁对象:可以任何的java类对象,因为是对象,所以定义在Object(顶层父类)
syncronized(锁对象){
锁对象.wait()
锁对象.notify()
}
s12.6.1leep和wait的区别
)调用方法是是否会释放锁
sleep(long mills)线程睡眠,调用等待睡眠时间到了,继续线程执行,不会去释放锁
wait的方法调用会立即释放锁对象,目的就是为了让锁对象调用notify()来唤醒其他线程…
2)来源不同
sleep方法来源于Thread类
属于线程的功能---->线程执行过程中可能产生阻塞 (睡眠一段时间内是阻塞的)
wait方法来源于Object类
它和锁对象有关系,线程处于等待过程中 使用锁对象.wait()
3)共同点:都会抛出一个异常:InterruptedException:中断异常
12.7同步方法
什么是同步方法? 如果一个方法的方法体的第一句话就是同步代码块。可以将synchronized关键字提取到方法声明上,跟在权限修饰符的后面。
权限修饰符 synchronized 返回值类型 方法名(形式列表){ //非静态的同步方法
业务逻辑…
}
12.8死锁问题
线程安全问题:可以通过同步方法或者是同步代码块去解决,但是执行过程中出现互相等待的情况就是死锁。
解决方案:
多个线程之间的通信:必须使用的是一个资源类对象,而不能是每一个线程在使用自己的资源类对象!使用生成者和消费者模式思想去解决,前提条件:生成者线程和消费者线程 必须操作的同一个资源类对象!
例如:卖包子和买包子,只有当商家生产出来包子,消费者才可以买
//生产包子
public class SetBun implements Runnable {
//声明这个包子类
private StuffBun stu ;
public SetBun(StuffBun stu){
this.stu = stu ;
}
//定义一个统计变量
int x = 0 ;
@Override
public void run() {
//产生包子
/* StuffBun stu = new StuffBun() ;
stu.name = "肉包子" ;
stu.bunType = "大类型";*/
//不断的产生数据
while(true){
synchronized (stu){
if(x % 2 == 0){//t1
stu.name = "肉包子" ;
stu.bunType = "大包子";
}else{
stu.name = "菜包子" ;
stu.bunType = "小包子" ;
}
}
x ++ ;
}
}
}
//买包子
public class GetBun implements Runnable {
//声明包子类的变量stb
private StuffBun stb ;
public GetBun( StuffBun stb){
this.stb = stb ;
}
@Override
public void run() {
//模拟要使用数据
// StuffBun stb = new StuffBun() ;
//不断使用数据
while(true){
synchronized (stb){
System.out.println(stb.name+"---"+stb.bunType);
}
}
}
}
//共享资源 卖家和买家操作同一个东西
public class StuffBun {
//成员变量不私有化
String name ;//包子的类型(肉包子,菜包子)
String bunType ;//大包子/小包子
}
//测试
public class StuffBun {
//成员变量不私有化
String name ;//包子的类型(肉包子,菜包子)
String bunType ;//大包子/小包子
}
12.8.1信号灯法解决买卖包子
//买包子
public class GetBun implements Runnable {
//声明包子类的变量stb
private StuffBun stu ;
public GetBun( StuffBun stu){
this.stu = stu ;
}
@Override
public void run() {
//模拟要使用数据
// StuffBun stb = new StuffBun() ;
//不断使用数据
while(true){
stu.get();//获取包子数据
}
}
}
//生产包子
public class SetBun implements Runnable {
//声明这个包子类
private StuffBun stu ;
public SetBun(StuffBun stu){
this.stu = stu ;
}
//定义一个统计变量
int x = 0 ;
@Override
public void run() {
//产生包子
/* StuffBun stu = new StuffBun() ;
stu.name = "肉包子" ;
stu.bunType = "大类型";*/
//不断的产生数据
while(true){
stu.set();//生产包子
}
}
//共享资源
public class StuffBun {
//成员变量不私有化
private String name ;//包子的类型(肉包子,菜包子)
private String bunType ;//大包子/小包子
//定义标记:表示是否存在包子数据
private boolean flag ; //默认false,没有数据
//提供给包子数据进行赋值的方法
public synchronized void set(String name,String bunType){ //锁对象是this:非静态的同步方法
//如果当前生产者没有语句,需要等待生成产生数据
if(this.flag){
//锁对象调用wait发那个发
try {
this.wait();//释放锁对象...
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//赋值
this.name = name ;
this.bunType = bunType ;
//如果现在有数据了
//改变信号
this.flag = true ;//有数据类
//通知(唤醒)消费者线程,赶紧使用数据
this.notify(); //唤醒对方线程
}
//提供方法:获取包子的数据
public synchronized void get(){ //非静态的同步方法:锁对象 this
//如果当前消费资源类中存在包子数据,先等待消费使用完毕数据
if(!this.flag){
//等待使用完毕数据
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.name+"---"+this.bunType);
//改变信号值
//如果包子消费完毕
this.flag = false ;
//唤醒对方线程(生产者资源类,别等了,产生数据)
this.notify();
}
}
//测试
public class ThreadDemo {
public static void main(String[] args) {
//创建一个包子对象
StuffBun sbu = new StuffBun() ; //同一个对象
//创建生产资源类对象
SetBun sb = new SetBun(sbu) ;
//消费者资源类对象
GetBun gb = new GetBun(sbu) ;
//创建线程了对象
Thread t1 = new Thread(sb) ;//生产者资源类所在的生产者线程
Thread t2 = new Thread(gb) ;//消费者资源类所在的消费者线程
t1.start();
t2.start();
}
}
13.locks锁类
JDK5以后提供java.util.current.locks.Lock :提供比syncrhonized方法(/同步代码块)更具体的锁定操作
Lock是一个接口
void lock()获取锁
void unlock() 试图释放锁
提供具体的子实现类: ReentrantLock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicket implements Runnable {
//定义100张票
private static int tickets = 100 ;
//创建一个锁对象
Lock lock = new ReentrantLock() ;
@Override
public void run() {
//模拟一只有票
while(true){
//通过锁对象--->获取锁
lock.lock();
//try...catch...finaly:捕获异常
//使用try..finally
try{
//判断
if(tickets>0){
//睡眠100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
}else{
break ;
}
}finally {
//释放锁
lock.unlock();
}
}
}
}
//测试类
public class LockDemo {
public static void main(String[] args) {
//创建共享资源类对象
SellTicket st = new SellTicket() ;
//创建三个线程类对象
Thread t1 = new Thread(st,"窗口1") ;
Thread t2 = new Thread(st,"窗口2") ;
Thread t3 = new Thread(st,"窗口3") ;
//启动线程
t1.start();
t2.start();
t3.start();
}
}
14.线程组
线程组 ThreadGroup。线程组代表一组线程。 此外,线程组还可以包括其他线程组
Thread:----> public final ThreadGroup getThreadGroup() {//获取线程组
return group;
}
//给线程设置默认的线程组名称
//public Thread(ThreadGroup group, Runnable target) {
ThreadGroup:public final String getName() {:获取默认的线程组名称
return name;
}
构造方法;
public ThreadGroup(String name) {}
线程组: 将线程可以都添加一组中,方便管理,线程启动完毕之后,线程终止之后,不会将这个线程对象在内存中重复利用。
线程组就是一个任务队列(Queue),一个任务就是一个线程。
public class MyThread implements Runnable {
@Override
public void run() {
for(int x = 0 ;x < 100 ; x ++){
System.out.println(Thread.currentThread().getName()+":"+x);
}
}
}
public class ThreadGroupDemo {
public static void main(String[] args) { //jvm调用main方法
// method1();
method2() ;
}
//设置一个新的线程组名称
private static void method2() {
//创建一个线程组对象--同时设置线程组名称
ThreadGroup tg = new ThreadGroup("myMain") ;
//创建两条线程对象
MyThread my = new MyThread() ;
Thread t1 = new Thread(tg,my) ;
Thread t2 = new Thread(tg,my) ;
//获取线程组对象并同时线程组名称
String name1 = t1.getThreadGroup().getName();
String name2 = t2.getThreadGroup().getName();
System.out.println(name1+"---"+name2);
}
private static void method1() {
//创建两个线程
MyThread my = new MyThread() ;
Thread t1 = new Thread(my) ;
Thread t2 = new Thread(my) ;
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
String name1 = tg1.getName();
String name2 = tg2.getName();
System.out.println(name1+"---"+name2); //默认线程组名称都是main
}
}
15.线程池
import java.util.TreeMap;
import java.util.concurrent.Callable;
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
for(int x = 0 ; x < 100 ; x++){
System.out.println(Thread.currentThread().getName()+":"+x);
}
return null;
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
for(int x = 0 ; x < 100 ; x ++){
System.out.println(Thread.currentThread().getName()+":"+x);
}
}
}
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author Kuke
* @date 2021/8/6
* 线程池 属于 "池"化技术 ------>相似 数据库连接池(dbcp,c3po,druid(为监控而生))
*
* 特点:
* 在内存中创建一个固定可重用的线程数,当前线程执行完毕终止了,不会被回收掉,再次
* 回到线程池中,等待下一次利用!
*
* 弊端: 维护成本大
*
* ExecutorService---接口
* 通过 工厂类:
* Exceutors
* 创建一个固定的可重用的线程数,返回值就线程池对象
* public static ExecutorService newFixedThreadPool(int nThreads)
*
* ExecutorService
* 提交队列任务(多个线程并发执行)
* <T> Future<T> submit(Callable<T> task)
* 提交值返回任务以执行,并返回代表任务待处理结果的Future。
Future<?> submit(Runnable task)
submit的返回值:异步计算的结果,如果不做计算,无须返回结果!
*
*
*/
public class ThreadPoolDemo {
public static void main(String[] args) {
//通过工厂类创建线程池对象
ExecutorService threadPool = Executors.newFixedThreadPool(2);
//提交异步方法
//MyRunnable:打印x的值0-99之间的数据,不需要返回结果
// threadPool.submit(new MyRunnable()) ;
// threadPool.submit(new MyRunnable()) ;
// <T> Future<T> submit(Callable<T> task)
//Callable:提交异步计算---需要重写Callable的call来计算结果;如果没有结果,直接在call无须返回
threadPool.submit(new MyCallable()) ;
threadPool.submit(new MyCallable()) ;
//void shutdown()关闭线程池
threadPool.shutdown();
}
}