Java高级

JAVA高级阶段技术总结

Java高级在我看来就是认识,了解,掌握并熟练使用Java中各种常用的类。思维代码能力体现不强,主要需要我们记忆,在哪种情况下,需要怎样。

String字符串

String是一个类,属于数据类型中的引用类型。

Java中一切使用""引起来的内容,都是这个类的实例,称为字符串对象。

在频繁更改字符串时,不要使用String类变量。

字符串String类中的常用方法
方法名返回值作用
length()int得到字符串的长度
toLowerCase()String转换为小写
toUpperCase()String转换为大写
trim()String去除字符串首尾的所有空格
isEmpty()boolean判断字符串是否为空白字符串""
getBytes()byte[]将字符串转换为字节数组
toCharArray()char[]将字符串转换为字符数组
equalsIgnoreCase(String str)boolean忽略大小写判断两个字符串是否相同
equals(String str)boolean判断两个字符串是否相同
charAt(int index)char得到字符串指定索引上的字符
indexOf(String str)int得到字符串中某个子字符串第一次出现的索引,如果不存在,返回-1
lastIndexOf(String str)int得到字符串中某个子字符串最后一次出现的索引,如果不存在,返回-1
contains(字符序列)boolean判断某个子字符串是否在原字符串中出现
concat(String str)String将参数字符串拼接到原字符串末尾
startsWith(String str)boolean判断是否以指定字符串开头
endsWith(String str)boolean判断是否以指定字符串结尾
substring(int begin)String从指定索引开始截取字符串至末尾
substring(int being,int end)String截取[begin,end)区间内的字符串
split(String regex)String[]按执行字符串或正则表达式切分原字符串。如果指定内容不再末尾,n个指定字符能得到n+1个子串;如果指定内容在末尾,n个指定字符能得到n个子串(不包含末尾的无效字符)
replace(char oldChar,char newChar)String将原字符串中的所有指定字符替换为新字符
String.valueOf(参数)String将任意参数转换为字符串。通常用于原始类型转换为字符串。
String.formart(String 格式,Object… obj)String根据指定格式转换参数。常用于将浮点数保留小数。如String.format(“%4.2f”,10.0/3)表示将计算的结果四舍五入保留2位小数转换为字符串;如果最终数据所占位置小于4,原样输出,大于4在最前补充空格。

StringBuilder类

用于表示可变字符串的一个类,是非线程安全的,建议在单线程环境下使用。

StringBuffer类

用于表示可变字符串的一个类,是线程安全的,建议在多线程环境下使用。

StringBuilder和StringBuffer中的方法都一致,只不过StringBuffer中的方法使用了synchoronized关键字修饰,表示是一个同步方法,在多线程环境下不会出现问题。

这里以StringBuilder为例

常用构造方法作用
StringBuilder()创建一个大小为16的字符串数组,表示一个空白字符。类似于String str=“”;
StringBuilder(String str)创建一个str长度+16的字符数组后,将str添加到其中。类似于String str=“初始值”;
常用方法作用
append(Object obj)将任意类型的参数添加到原可变字符串末尾
delete(int start,int end)删除[start,end)区间内的字符
deleteCharAt(int index)删除index索引上的字符
insert(int index,Object obj)在索引index上插入obj
replace(int start,int end,String str)将[start,end)区间内的字符替换为str
reverse()反转字符串

System类

这个类中包含了一些系统相关的信息和一些方法。其中的属性和方法都是静态的。

该类不能创建对象,不是因为它是一个抽象类,而是因为它的构造方法是私有的。

常用属性和方法
System.out获取打印输出流PrintStream对象,用于控制台打印信息。
System.in获取输入流InputStream对象,用于获取输入的信息
System.err获取打印输出流PrintStream对象,用于控制台打印异常信息。
System.exit(int statues)终止虚拟机运行,参数0表示正常终止。
System.currentTimeMillis()获取从1970.1.1 0:0:0至今进过了多少毫秒。中国是UTC(+8),所以是从1970.1.1 8:0:0至今经过了多少毫秒。返回long类型。
System.arraycopy(原数组,原数组起始位置,目标数组,目标数组起始位置,原数组要复制的元素数量)将原数组中指定长度的元素复制到新数组中

RunTime类

Runtime类的对象,表示程序运行时对象(程序运行环境对象)。

包含了程序运行环境相关的信息。常用于获取运行环境信息(如虚拟机内存)或执行某个命令。

特点

这个类不是一个抽象类,但不能创建对象,因为它的构造方法是私有的。

这个类提供了一个静态方法getRuntime(),通过这个方法,可以获取一个Runtime类的对象。

这是Java中的一种设计模式–单例模式(一个类只能有一个创建对象)。

public class Runtime {
    //定义了私有的一个静态成员:当前类的对象
    //由于静态成员只在类加载时执行一次,所以这里只会创建唯一一个当前类的对象
    private static Runtime currentRuntime = new Runtime();

    //定义了一个公共的静态方法,用于获取创建的唯一的当前类的对象
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    //构造方法是私有的,不能在当前类之外创建对象
    private Runtime() {}
}

Date类

用于表示日期时间的类,位于java.util包下

常用构造方法说明
Date()创建当前瞬间对应的日期对象
Date(long l)创建指定瞬间对应的日期对象
Date(int year,int month,int day)该构造方法已过时。创建指定年月日的日期对象(年是1900年起经过的年数,月用0-11表示1到12月)
常用方法作用
getTime()得到对应Date对象表示的毫秒数
setTime(long l)设置Date对象的毫秒数
after(Date when)判断调用日期对象是否在when之后
before(Date when)判断调用日期对象是否在when之前

SimpleDateFormat类

用于格式化日期的类。

常用构造方法作用
SimpleDateFormat(String pattern);创建一个指定日期模板的格式化日期对象

日期模板

特殊字符作用
yyyy年份
MM月份
dd日期
HH小时
mm分钟
ss
E星期
以上两个字母都可以写成一个,如月份5M:5,MM:05
yyyy/MM/dd HH:mm:ss E2022/11/24 16:24:09 星期四
常用方法返回值作用
format(Date date)String将Date对象按日期模板转换为字符串
parse(String str)Date将满足日期模板的字符串转换为Date对象
package com.hqyj.test3;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class SimpleDateFormatTest {
    public static void main(String[] args) throws ParseException {
        //定义格式化日期类所需的时间模板
        /*
        * yyyy  年
        * MM    月份
        * dd    日期
        * HH    24小时制
        * hh    12小时制
        * mm    分钟
        * ss    秒
        * E     星期
        *
        * 两个字母都可以写成一个,如月份MM和M
        * MM       5月实际为05
        * M       5月实际为5
        * */
        String patten = "yyyy/MM/dd HH:mm:ss E";//年/月/日 时:分:秒 星期
        //创建格式化日期类对象,参数为日期模板
        SimpleDateFormat sdf = new SimpleDateFormat(patten);
        //创建当前日期对象
        Date now = new Date();
        //调用格式化日期对象的format(Date date),将Date对象转换为指定日期格式的字符串
        String format = sdf.format(now);
        //输出
        System.out.println(format);
        //parse(String str)将指定日期模板的字符串转换为Date对象
        Date date = sdf.parse("2000/5/3 2:1:3 星期一");

        System.out.println(date);


    }
}

Calendar类

表示日历的类,包含了很多日历相关的信息。

是一个抽象类,无法创建对象。可以通过静态方法getInstance()获取该类的一个实例。

//获取Calendar类的对象
Calendar cal = Calendar.getInstance();

日历字段

在Calendar类中,定义了很多被final和static修饰的常量,称为日历字段,实际一个数字,用于获取指定信息

作用
Calendar.YEAR年份
Calendar.MONTH月份(0-11表示1-12月)
Calendar.DATE日期
Calendar.DAY_OF_WEEK星期(1-7表示周天到周六)
Calendar.HOUR12进制小时
Calendar.HOUR_OF_DAY24进制小时
Calendar.MINUTE分钟
Calendar.SECOND
Calendar.DAY_OF_MONTH本月第几天
Calendar.DAY_OF_YEAR本年第几天
Calendar.WEEK_OF_MONTH本月第几周
Calendar.WEEK_OF_YEAR本年第几周
常用方法作用
get(int field)根据日历字段获取对应的值
getTime()获取对应的Date对象(Calendar对象转换为Date对象)
getMaximum(int field)获取指定日历字段支持的最大值,如Calendar.DATE最大31
getActualMaximum(int field)获取指定日历字段在当前日期下的实际最大值,如11月,Calendar.DATE最大30
set(int field,int value)将指定的日历字段设置为指定值
set(int year,int month,int date)同时设置日历对象的年月日
setTime(Date date)将Date对象作为参数设置日历对象的信息

异常类

当程序没有按开发人员的意愿正常执行,中途出现错误导致程序中断,出现这种情况,就称为异常。学习异常就是认识异常的种类,如何处理异常和避免异常出现。

异常的产生

异常在程序中以对象的形式存在。当代码执行过程中出现异常,虚拟机会自动创建一个异常对象,如果没有对象该异常对象进行处理,就会导致程序中断,不再执行后续代码。

异常的分类

异常在程序中以对象的形式存在,就有相应的类。所有的异常类,组成了一个"异常家族"

在这里插入图片描述

集合框架(集合家族)

在这里插入图片描述
在这里插入图片描述

所有集合的根接口为Collection接口和Map接口,位于java.util包中。图上的所有实现类,都是非线程安全的,在多线程的环境下使用以上任意集合,都会产生不确定的结果。

Collection接口

该接口有两个核心子接口:List和Set。

这两个接口都可以保存一组元素,List接口保存元素时,是有序可重复的;Set接口保存元素时,是无序不重复的。

常用方法返回值作用
add(Object obj)boolean将元素添加到集合中
size()int获取集合中的元素数量
isEmpty()boolean判断集合是否为空
clear()void清空集合
contains(Object obj)boolean判断集合中是否存在指定元素
remove(Object obj)boolean移除集合中的指定元素
toArray()Object[]将集合转换为数组
iterator()Iterator获取集合的迭代器对象,用于遍历集合

ArrayList实现类(掌握)

  • 采用数组实现的集合
  • 可以通过索引访问元素,可以改变集合大小。如果要在其中插入或删除元素时,会影响后续元素
  • 该集合中保存的都是引用类型,即便保存了数组123,也保存的是Integer类型的123,而不是int类型的123
  • 该集合查询效率高,中途增加和删除元素效率低

LinkedList实现类

  • 采用双向链表实现的集合
  • 集合中保存的每个元素也称为节点,除首尾节点外,其余节点都保存了自己的信息外,还保存了其前一个和后一个节点的地址
  • 如果在双向链表的数据结构中插入和删除操作节点时,不会影响其他节点的位置。如添加时新节点时,只需要重写定义新节点的前后节点位置即可
  • 如果要查询某个节点时,需要从头结点或尾结点开始一步步得到目标节点的位置
  • 双向链表在中间插入和删除的效率高,随机读取的效率低****

ArrayList和LinkedList的区别

  • 这两个类都是List接口的实现类,保存的元素有序可重复,允许保存null
  • ArrayList采用数组实现,随机读取效率高,插入删除效率低,适合用于查询
  • LinkedList采用双向链表实现,插入删除时不影响其他元素,效率高,随机读取效率低,适合用于频繁更新集合

Set接口(无序不重复)

无序集合,元素不可以重复,允许保存null,没有索引。

Set接口中没有自己定义的方法,都是继承于Collection接口中的方法

HashSet实现类

  • 采用哈希表实现
  • 元素不能重复,无序保存,允许保存一个null
  • 本质是一个HashMap对象
  • 使用HashSet集合时,通常要重写实体类中的equals和hashcode方法

TreeSet实现类

  • 特殊的Set实现类,数据可以有序保存,可以重复,不能添加null
  • 采用红黑树(自平衡二叉树)实现的集合
    • 二叉树表示某个节点最多有两个子节点
    • 某个节点右侧节点值都大于左侧节点值
    • 红黑树会经过不停的"变色"、"旋转"达到二叉树的平衡
  • 只能添加同一种类型的对象且该类实现了Comparable接口
    • 实现Comparable接口后必须要重写compareTo()方法
    • 每次调用添加add(Object obj)方法时,就会自动调用参数的compareTo()方法
  • compareTo()方法的返回值决定了能否添加新元素和新元素的位置
    • 如果返回0,视为每次添加的是同一个元素,不能重复添加
    • 如果返回正数,将新元素添加到现有元素之后
    • 如果返回负数,将新元素添加到现有元素之前
  • 添加的元素可以自动排序

Map接口

Map称为映射,数据以键值对的形式保存。保存的是键与值的对应关系。

键称为Key,值称为Value,键不能重复,键允许出现一个null作为键,值无限制。

键和值都是引用类型。

常用方法作用
size()得到键值对的数量
clear()清空所有键值对
put(Object key,Object value)向集合中添加一组键值对
get(Object key)在集合中根据键得到对应的值
remove(Object key)/remove(Object key,Object key)根据键或键值对移除
keyset()获取键的集合
values()获取值的集合
containsKey(Object key)判断是否存在某个键
containsValue(Object value)判断是否存在某个值
entrySet()得到键值对的集合

HashMap实现类(掌握)

  • JDK1.8之后,HashMap采用"数组+链表+红黑树"实现
    • 当没有哈希冲突时,元素保存到数组中
    • 如果出现哈希冲突,在对应的位置上创建链表,元素保存到链表中
    • 如果链表的长度大于8,将链表转换为红黑树
  • 数据采用键值对key-value的形式保存,键不能重复,能用null作为键;值没有限制,键和值都是引用类型
  • 向HashMap集合中添加元素时,原理同HashSet

Collections集合工具类

  • Collection是集合的根接口,定义了集合操作元素的方法
  • Collections是集合的工具,定义了集合操作元素的静态方法
常用方法说明
Collections.shuffle(List list)打乱List集合中元素的顺序
Collections.sort(List list)对List集合中的元素进行排序,元素必须实现Comparable接口
Collections.swap(List list,int a,int b)交换List集合中元素的索引
Collections.replaceAll(List list,Object oldObj,Object newObj)替换List集合中的旧元素为新元素
Collections.reverse(List list)将List集合中的元素反转
Collections.fill(List list , Object obj)使用指定元素填充List集合
Collections.rotate(List list , int n)将集合中最后n个元素放在最前
Collections.max(Collection col)/min(Collection col)得到集合中的最大/最小值,集合中的元素必须实现Comparable接口

文件类File

Java中的File类,表示本地硬盘中的文件(文件和目录)的一个类。

通过这个类创建的对象,可以操作对应的文件。

构造方法

常用构造方法说明
File(String pathName)根据文件的完整路径创建File对象
File(String parent,String child)根据文件的父目录路径和自身路径创建File对象
File(File parent,String child)根据文件的父目录对应的File对象和自身路径创建File对象
//如想要表示 “F:\221001\笔记\面向对象部分回顾.pdf”  这个文件

//File(String pathName)
File file1 = new File("F:\\221001\\笔记\\面向对象部分回顾.pdf");

//File(String parent,String child)
File file2 = new File("F:\\221001\\笔记", "面向对象部分回顾.pdf");

//File(File parent,String child)
File parent = new File("F:\\221001\\笔记");
File file3 = new File(parent, "面向对象部分回顾.pdf");

//file1、file2、file3都表示同一个文件
常用方法说明
exists()判断文件是否存在
isFile()判断是否为文件
isDirectory()判断是否为目录
getName()获取文件名
getPath()获取文件相对路径
getAbsolutePath()获取文件绝对路径
getParent()获取父目录的名称
getParentFile()获取父目录对象
lastModified()获取最后一次修改时间对应的毫秒数
length()获取文件所占字节
isHidden()判断文件是否隐藏
delete()删除文件或空目录
renameTo(File newFile)将原文件重命名且移动到指定目录
mkdir()创建目录
list()获取某个目录下的第一层子文件的名称的数组
listFiles()获取某个目录下的第一层子文件对象的数组

流的分类

Java中将流定义为类,以对象的形式表现流。流有"四大家族",是所有流的父类。

字节输入流InputStream

FileInpuStreamObjectInputStream

字节输出流OutputStream

FileOutputStreamObjectOutputStream

字符输入流Reader

FileReader、BufferedReader、OutputStreamWriter

字符输出流Writer

FileWriter、BufferedWriter、InputStreamReader

按方向分类

  • 输入流:InputStream、Reader

    • 将硬盘中的数据读取到内存中
  • 输出流:OutputStream、Writer

    • 将内存中的数据写入到硬盘中

按类型分

  • 字节流:InputStream、OutputStream
    • 读写非文本类型文件。如图片、音视频、其他文件等。
  • 字符流:Reader、Writer
    • 读写纯文本类型文件。如txt、md等

如要将硬盘中某个txt文件中的内容读取到程序中,使用Reader

如要将硬盘中的某个图片读取到程序中,使用InputStream

如要将程序中的文本写入到硬盘中为txt类型文件时,使用Writer

如要将程序中的数据写入到硬盘中为非文本文件时,使用OutputStream

流的四个父类的特点

  • 这四个父类都是在java.io包下,都是抽象类,不能直接创建其对象,使用其子类创建对象
  • 这四个父类中都定义了close()方法,用于关闭流对象,释放资源
  • 输入流(InputStream和Reader)都有read()方法读取数据到内存中,输出流都有write()方法写入数据到硬盘中
  • 输出流(OutputStream和Writer)都有flush()方法,用于将流中的数据冲刷到硬盘中
    • 在使用输出流对象时,一定要调用flush()或close()方法后,才能真正将数据写入到硬盘中
  • 所有的流中,以Stream结尾,都是字节流,数据以字节传输;以Reader或Writer结尾的,都是字符流,数据以字符传输
  • 读取硬盘中的数据,使用输入流,读取的文件必须存在;将数据写入到硬盘中,使用输出流,文件可以不存在,但父目录必须存在。
  • 读入或写入文本时,使用字符流;读取或写入非文本时,使用字节流

文件类的综合练习:

package homework;

import java.io.IOException;
import java.io.*;
import java.util.ArrayList;

public class Main {
    static ArrayList<Person> list = new ArrayList<>();
    public void fun(File source) {
        if (source.isDirectory()) {
            //将其展开
            for (File child : source.listFiles()) {
                //因为子文件有可能是目录,继续调用本方法
                fun(child);
            }
        }
        else {
            String s=source.getName();
            String ID=s.substring(0,s.indexOf(" "));
            String name=s.substring(s.indexOf(" ")+1,s.indexOf("."));
            Person p=new Person();
            p.setNo(ID);
            p.setName(name);
            list.add(p);
        }

    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Main main1=new Main();
        File file = new File("C:\\Users\\hnn\\Desktop\\信息");
        main1.fun(file);
//创建OutStream的实现类,设置写入的文件路径
        OutputStream os = new FileOutputStream("C:\\Users\\hnn\\Desktop\\123.txt");
//创建对象输出字节流,参数为OutStream类型
        ObjectOutputStream oos = new ObjectOutputStream(os);
//调用writeObject(Object obj)方法,将对象写入到硬盘中(序列化)
        oos.writeObject(list);
        oos.close();
//创建对象输入字节流,将上一步保存的文件进行反序列化
        ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("C:\\Users\\hnn\\Desktop\\123.txt"));
//使用readObject()方法,将写入的文件进行读取(反序列化)
        ArrayList<Person> pList = (ArrayList<Person>) ois.readObject();
        for (Person person : pList) {
            System.out.println(person);
        }
        ois.close();
    }
}

网络编程

InetAddress类

表示IP对象的一个类

public static void main(String[] args) throws UnknownHostException {
    //获取本机的ip对象
    // InetAddress ip = InetAddress.getLocalHost();
    //获取域名
    // System.out.println(ip.getHostName());
    //获取真实ip地址
    // System.out.println(ip.getHostAddress());

    //getByName(域名)  得到域名对应的ip对象
    //localhost域名表示本机,对应的ip地址为127.0.0.1
    InetAddress ip = InetAddress.getByName("localhost");
    //获取域名
    System.out.println(ip.getHostName());
    //获取ip地址
    System.out.println(ip.getHostAddress());
}

Socket类和ServerSocket类

都属于Socket(套接字)对象,表示网络中的某个端点

  • Socket指普通端
  • ServerSocket指服务器端

进程和线程

进程Process

进程就是操作系统中执行的程序。一个程序就是一个执行的进程实体。

每个运行中的进程,都有属于它独立的内存空间,各个进程互不影响。

线程Thread

线程是一个进程中的执行单元,一个进程中可以有多个线程。

多个线程,可以访问同一个进程中的资源。

每个线程都有一个独立的栈空间,这些线程所在的栈空间位于同一个进程空间中。

多线程

如果一个进程中,同时在执行着多个线程,就称为多线程。

多线程可以提高程序执行效率。如多个窗口卖票,可以加快卖票的效率。

其实每个执行的Java程序,都是多线程执行,main方法称为主线程,还有gc线程(守护线程)在同时运行。

如有一个工厂,工厂中有很多车间,每个车间有很多流水线。

工厂就是内存,车间就是各个进程,每个流水线都是一个进程中的一个线程。

并行和并发

并行

各个进程同时执行,称为并行。

并发

多个线程同时执行,称为并发。

同步和异步

同步

所有的任务排队执行,称为同步执行。

异步

在执行任务A的同时,执行任务B,称为异步执行。

Java中的线程Thread类

Java中,线程以对象的形式存在。

Thread类表示线程类

获取线程对象

  • 获取当前正在运行的线程对象

    Thread ct = Thread.cuurentThread();
    
  • 创建一个线程对象

    常用构造方法说明
    Thread()创建一个默认的线程对象
    Thread(String name)创建一个指定名称的线程对象
    Thread(Runnable target)将一个Runnable对象包装为线程对象
    Thread(Runnable target,String name)将一个Runnable对象包装为线程对象同时设置线程名
方法作用
getId()获取线程id
getName()获取线程名,默认Thread-n
getPriority()获取线程优先级,默认为5
getState()获取线程状态
setName(String str)设置线程名
setPriority(int priority)设置线程优先级,范围在1-10,值越大越优先执行
isDaemon()判断线程是否为守护线程
setDaemon(boolean f)参数为true表示设置线程为守护线程
start()让线程进入就绪状态
run()线程获得执行权时执行的方法(线程要做的事情)
Thread.sleep(long m)设置当前线程休眠m毫秒
Thread.currentThread()获取当前执行的线程对象
Thread.yield()线程让步

线程的生命周期

线程的初始化到终止的整个过程,称为线程的生命周期。

img

新生状态

当线程对象被创建后,就进入了新生状态。

就绪状态

当某个线程对象调用了start()方法后,就进入了就绪状态。

在这个状态下,线程对象不会做任何事情,只在等他CPU调度。

运行状态

当某个线程对象得到CPU时间片(CPU执行这个线程的机会所给的时间),则进入运行状态,开始执行run()方法。

不会等待run()方法执行完毕,只会在指定的时间内尽可能地执行run()方法。只要调用玩run()方法后,就会再进入就绪状态。

阻塞状态

如果某个线程遇到了sleep()方法或wait()方法时,就会进入阻塞状态。

sleep()方法会在指定时间后,让线程重新就绪。

wait()方法只有在被调用notify()或notifyAll()方法唤醒后才能重新就绪。

终止状态

当某个线程的run()方法中的所有内容都执行完,就会进入终止状态,意味着该线程的使命已经完成。

守护线程

如果将一个线程设置setDeamon(true),表示该线程为守护线程。

守护线程会随着其他非守护线程终止而终止。

package com.hqyj.DaemonTest;
/*
* Test类是一个自定义线程类,死循环输出
* */
public class Test implements Runnable {
    public static void main(String[] args) {

        Thread thread = new Thread(new Test());
        //将自定义线程类设置为守护线程
        thread.setDaemon(true);
        thread.start();

        //main线程终止,守护线程也会终止
        for (int i = 0; i < 100; i++) {
            System.out.println("main方法中的循环执行中");
        }


    }

    @Override
    public void run() {
        while (true) {
            System.out.println("守护线程执行中。。。");
        }
    }
}

多线程访问同一个资源

可能出现的问题

如银行存款100,同一时刻在手机和ATM一起取出,如果用多线程模拟,可能会出现两个线程都取出100的情况。要避免这种情况发生。

本应该大于售出后再减,再打印剩余,由于线程A在打印"售出一张"后,还没来得及执行后续内容,其他线程就开始执行了。

出现问题的原因

由于线程调用start()方法后,就进入就绪状态。如果获得了CPU时间片,就开始调用run()方法,调用run()方法后,就会再次进入就绪状态,不会等待run()方法执行完毕,所以在线程A执行run()方法的时候,线程B也开始执行了,这样就会出现数据共享的问题。

因为现在所有的线程都是异步(同时)执行。

如何解决

让线程同步(排队)执行即可。这样一来,某个线程执行run()方法的时候,让其他线程等待run()方法的内容执行完毕。

synchronized关键字

这个关键字可以修饰方法或代码块

修饰方法

写在方法的返回值之前,这时该方法就称为同步方法。

public synchronized void fun(){
    //会排队执行的代码
}
修饰代码块

写在一个独立的{}前,这时该段内容称为同步代码块。

synchronized(要同步的对象或this){
    //会排队执行的代码
}
原理

每个对象默认都有一把"锁",当某个线程运行到被synchronized修饰的方法时,该对象就会拥有这把锁,在拥有锁的过程中,其他线程不能同时访问该方法,只有等待其结束后,才会释放这把锁。

使用synchronized修饰后的锁称为"悲观锁"。

方法被synchronized修饰后,称为同步方法,就会让原本多线程变成了单线程(异步变为同步)。

死锁的解决方式

方式一

让两个线程获取资源的顺序保持一致。

如两个线程都先获取knife,再获取fork

@Override
public void run() {
    synchronized (knife) {
        System.out.println(Thread.currentThread().getName() + "获取了knife,3s后获取fork");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (fork) {
            System.out.println(Thread.currentThread().getName() + "获取了fork,可以吃饭了");
        }
    }
}

方式二

让两个线程在获取资源A和B之前,再获取第三个资源,对第三个资源使用synchronized进行同步,这样某个线程在获取第三个资源后,将后续内容执行完毕,其他线程才能开始执行。

如在获取knife和fork之前,先获取paper对象

@Override
public void run() {
    //先获取paper,再进行后续操作
    synchronized (paper) {
        synchronized (knife) {
            System.out.println(Thread.currentThread().getName() + "获取了knife,3s后获取fork");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (fork) {
                System.out.println(Thread.currentThread().getName() + "获取了fork,可以吃饭了");
            }
        }
    }
}

总结

Java高级包含的东西很多很多,并不是短期内就能学习完,完全掌握的,需要我们在以后的开发之路上不断探索,不断学习,不断掌握,不断总结的,越来越觉得自己的探索之路任重而道远。加油!!!
() + “获取了fork,可以吃饭了”);
}
}
}


### 方式二

让两个线程在获取资源A和B之前,再获取第三个资源,对第三个资源使用synchronized进行同步,这样某个线程在获取第三个资源后,将后续内容执行完毕,其他线程才能开始执行。

如在获取knife和fork之前,先获取paper对象

```java
@Override
public void run() {
    //先获取paper,再进行后续操作
    synchronized (paper) {
        synchronized (knife) {
            System.out.println(Thread.currentThread().getName() + "获取了knife,3s后获取fork");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (fork) {
                System.out.println(Thread.currentThread().getName() + "获取了fork,可以吃饭了");
            }
        }
    }
}

总结

Java高级包含的东西很多很多,并不是短期内就能学习完,完全掌握的,需要我们在以后的开发之路上不断探索,不断学习,不断掌握,不断总结的,越来越觉得自己的探索之路任重而道远。加油!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值