java基础 —— 集合、异常、反射、io流、多线程

1.集合 (Collection)

Collection
list-有序可重复
set-无序不可重复
Arraylist-底层是数组,线程不安全
Linkedlist-底层是双向链表结构,线程不安全
Vector-底层是数组,线程安全
hashSet-基于哈希表储存,线程不安全
LinkedhashSet- hashSet的子类
TreeSet- 底层是树

list接口:储存有序可重复数据。

在使用list储存数据时,如果储存的是对象,那么对象多对应的类要重写equals()方法。

  • Arraylist:作为list接口的主要实现类;线程不安全,高效率;可以存储null值
  • Linkedlist:对于频繁的插入、删除操作,使用此类效率比Arraylist 高;底层使用双链表储存; 可以储存null值
  • Vector:作为list古老实现类;线程安全,效率低;底层使用Object[] elementData储存。

set接口:储存无序不可重复数据

在使用hashSet 、LinkedhashSet 储存数据时,如果储存的是对象,那么对象多对应的类要重写equals()方法 和 hashcode()方法。

  • hashSet :作为set主要实现类; 线程不安全;可以储存null值
    底层原理:在添加元素的时候,HashSet会先调用元素的hashcode方法得到元素的哈希值 ,然后通过元素的哈希值经过移位等运算,就可以算出该元素在哈希表中的存储位置
    1、如果算出的元素存储的位置目前没有任何元素存储,那么该元素可以直接存储在该位置上。
    2、如果算出的元素的存储位置目前已经存在有其他的元素了,那么还会调用该元素的equals方法
    与该位置的元素再比较一次,如果equals方法返回的是true,那么该位置上的元素视为重复元
    素,不允许添加,如果返回的是false,则允许添加。

  • LinkedhashSet:LinkedhashSet 是 hashSet的子类; 对于频繁的遍历操作,linkedhashSet效率高于hashSet。

  • TreeSet:可按照添加对象的指定属性进行排序;不可储存null值;

Collection使用迭代器遍历集合数据

Collection<String> collection = new ArrayList<String>();
    	collection.add("1223");
    	Iterator<String> iterator = collection.iterator();
    	while (iterator.hasNext()) {
    
    
			Object object = iterator.next();
			System.out.println(object.toString());
		}

Collection使用 foreach遍历集合数据

Collection<String> collection = new ArrayList<String>();
    	collection.add("1223");
    	for (String string : collection) {
    
    
			System.out.println(string);
		}

collection接口常用方法

  • 添加、删除、修改操作:
    Object add(Object obj):添加数据。
    Object remove(Object obj):移除对应数据。
    void clear():清空集合中所有数据。
  • 元素查询操作
    boolean contains(Object obj):判断元素是否存在。
    int size():返回集合中元素的个数。
    boolean isEmpty():判断当前集合是否为空。
    retainAll(Collection c) //保留两个集合的交集
    参考:https://blog.csdn.net/iruier_/article/details/80010308
Map
HashMap-线程不安全
LinkedHashMap-HashMap的子类
TreeMap

Map:映射关系集合,以key—value形式储存数据;其中的 key 是无序不可重复的

在HashMap中如果key是自定义的类,类中就要重写equals()方法和 hashcode()方法。

hashMap:

  • 作为Map的主要实现类,线程不安全,效率高;
  • key可以为null,当key为null是会返回0; 源码如下:

在这里插入图片描述
在这里插入图片描述
hashMap在put时会调用 hash()方法来计算key的hashcode值,可以从hash算法中看出当 key为null时返回的值是0。因此key为null时,hash算法返回值为0,不会调用key的hashcode()方法。

LinkedHashMap:

  • 保证在遍历Map元素时,可以按照添加顺序实现遍历。
  • 在原有的HashMap底层结构基础上添加了一对指针,指向前一个和后一个元素,对应频繁的遍历操作,执行效率高于HashMap

TreeMap:
保证按照添加的key-value对进行排序,实现排序遍历。

Hashtable:线程安全的,效率低;储存的key不能为null。

Map接口的常用方法

  • 添加、删除、修改操作:
    Object put(Object key,Object value):添加key - value 类型数据。
    void putAll(Map m):将m中所有数据存放到map中。
    Object remove(Object key):移除key所对应的value的值。
    void clear():清空map中所有数据。
  • 元素查询操作
    Object get(Object key):获取key所对应的value的值。
    boolean containsKey(Object key):判断key是否存在。
    boolean containsValue(Object value):判断value值是否存在。
    int size():返回map中key—value对 的个数。
    boolean isEmpty():判断当前map是否为空。
    boolean equals(Object obj):判断当前map和参数对象obj是否相等。

使用Collections工具类是用来操作collection接口

1.void copy(List dest , List src):将src中的类容复制到dest中。 例如:

      List list = new ArrayList();
    	list.add("abc");
    	System.out.println(list);
    	List nlist = Arrays.asList(new Object[list.size()]);
    	//将list中的类容复制到 nlist中
    	Collections.copy(nlist, list);
    	System.out.println(nlist);

2.使用Collections.synchronizedXXX(XXX) 可以解决多线程并发访问时的线程安全问题。例如

//dlist 线程不安全
    	List dList = new ArrayList();
    	//nlist 为线程安全的
    	List nlList = Collections.synchronizedList(dList);

2.异常

Throwable
Error
Exception
虚拟机错误-VirtualMachineError
内存溢出-outOfMemoryError
线程死锁-ThreadDeath
RuntimeException
io异常
SQL异常
空指针异常-NullPointerException
数组下标越界异常-ArrayIndexOutOfBoundsException
算数异常-ArithmeticException
类型转换异常-ClassCastException

捕获异常:使用 try…catch 语句,try中是有可能出现异常的代码,catch中使用 e.printStackTrace()方法打印异常信息;try…catch语句是在方法中使用

声明异常:使用 throws ,跟在方法的后面,可以接多个异常类

自定义异常

  • 创建类
  • 继承Exception类或者Exception的子类
  • 重写构造方法
public class MyServiceException extends RuntimeException {
    
    

	private static final long serialVersionUID = 1L;

	private String code;

    private String msg;

    public  MyServiceException(ResponseEnum e){
    
    
        super(e.getMsg());
        this.code = e.getCode();
        this.msg = e.getMsg();
    }

    public MyServiceException(String code, String msg) {
    
    
        super(msg);
        this.code = code;
        this.msg = msg;
    }
}

手动抛出异常

  • 找到一个合适的在这里插入代码片异常类
  • 创建这个异常类的对象
  • 抛出这个对象
 public void orderTask(){
    
      	
    	//使用throw手动抛出异常 (MySericeExcption是自定义异常)
    	throw new MyServiceException("11","fff");  	     
    }

参考视频https://www.bilibili.com/video/BV1nC4y1s7EY?from=search&seid=4430483164806649866

3.反射

1、什么是反射

反射:是Java被视为动态语言的关键;反射机制允许程序在运行期间,借助反射API获取任何类的全部信息,并且能够直接操作这些类的属性和方法。

Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。

2、获取Class类的实例(三种方式)

  1. 通过类直接获取
Class c3 = Student.class;
  1. 通过类的一个实例,调用getClass()方法获取
People people = new Student();   
Class c1 = people.getClass();
  1. 通过类的全路径获取(包名+类名)
Class c2 = Class.forName("ThreeClass.Student");

3、Class类的常用方法

方法名 说明
Objecet newInstance() 创建Class类型的一个实例
getName() 获取Class对象所表示的实例名称
Class getSuperClass() 返回当前Class对象的父类的Class对象
Class[] getinterfaces() 获取当前Class对象的接口
ClassLoader getClassLoader() 获取该类的类加载器

4、反射获取类的属性、方法和构造器

在这里插入图片描述

1. 获取类的属性

        Class people = Class.forName("People");
        Class user = Class.forName("user");
        
        /*
         * 1、获取类的属性
         */
        //获取当前类和父类的所有public属性
        Field[] fields = people.getFields();
        for (Field field : fields) {
    
    
            System.out.println(field);
        }
        //获取当前类的所以public和private属性
        Field[] f2 = user.getDeclaredFields();
        for (Field field : f2) {
    
    
            System.out.println("Field暴力获取"+field);
        }
        //获取指定属性
        Field name = user.getField("name"); //获取public属性
        System.out.println(name);
        Field age = user.getDeclaredField("age");  //获取私有的属性
        System.out.println(age);

2. 获取类的方法

        /*
         *  2、获取类的方法
         */
        //获取当前类和父类的所有public方法
        Method[] m1 = user.getMethods();
        for (Method method : m1) {
    
    
            System.out.println(method);
        }
        //获取当前类所有的public和private方法
        Method[] m2 = user.getDeclaredMethods();
        for (Method method : m2) {
    
    
            System.out.println("暴力获取"+method);
        }
        //获取指定的方法
        Method test = user.getMethod("test"); //获取public方法
        System.out.println("获取public方法"+test);
        Method ptest = user.getDeclaredMethod("ptest"); //获取privata方法
        System.out.println("获取private方法"+ptest);

3. 获取类构造器

        /*
         * 3、获取构造器
         */
        //获取当前类public构造器
        Constructor[] c1 = people.getConstructors();
        for (Constructor constructor : c1) {
    
    
            System.out.println(constructor);
        }
        //获取当前类public和private构造器
        Constructor<?>[] c2 = people.getDeclaredConstructors();
        for (Constructor<?> constructor : c2) {
    
    
            System.out.println("暴力获取"+constructor);
        }
        //获取指定的构造器
        Constructor c3 = user.getConstructor(String.class, Integer.class);
        System.out.println("指定public构造器"+c3);
        Constructor c4 = user.getDeclaredConstructor(null);
        System.out.println("获取指定的private"+c4);

5、反射操作类的属性、方法

1. 反射创建对象

        Class user = Class.forName("user");
        Class people = Class.forName("People");
        /*
         * 1、通过反射创建对象
         */
        //默认调用的无参构造
        user u1 = (user) user.newInstance();
        System.out.println(u1);
        //使用有参构造
        Constructor<?> constructor = user.getDeclaredConstructor(String.class, Integer.class);
        user ul2 = (user) constructor.newInstance("小婉", 17);
        System.out.println(ul2);

2. 反射执行方法

        /*
         *2、通过反射执行方法
         * invoke(实例化的对象,方法中的参数)
         */
        Method m1 = user.getDeclaredMethod("test");
        m1.invoke(u1);
        Method m2 = user.getDeclaredMethod("ptest");
        m2.setAccessible(true); //ptest()方法为private的 ,要关闭安全检查才能执行
        m2.invoke(u1);

3. 反射操作属性

        /*
         * 3、通过放射操作属性
         * set(实例化的对象,属性值)
         */
        Field f1 = people.getDeclaredField("pname");
        People peo = (People) people.newInstance();

        f1.setAccessible(true); //pname方法为private的 ,要关闭安全检查设置值
        f1.set(peo,"小红");
        System.out.println(peo.getPname());

注意点:
操作私有属性和方法时,要关闭安全权限检查,设置setAccessible(true),否则执行不了。

4.io流

1、File类

1.1、file类的三种构造方法

  • File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
 File f1 = new File("D:/下载/JavaSE.png");
  • File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File实例。
File f2 = new File("D:/","下载/JavaSE.png");
  • File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例
 File file = new File("D:/");
 File f3 = new File(file,"下载/JavaSE.png");

1.2、file类的常用方法

获取功能的方法

  • public String getAbsolutepath(); 获取绝对路径名字符串
  • public String getpath(); 获取相对路径字符串
  • public String getName(); 获取文件名或文件夹名
  • public long length(); 文件大小的字节长度 ( 文件夹没有大小为 0 )
  • public long lastModified(); 获取最后一次的修改时间
File file = new File("a.txt");
System.out.println(file.getPath());   //获取相对路径
System.out.println(file.getAbsolutePath());   //获取绝对路径
System.out.println(file.getName());  //获取文件名或文件夹名
System.out.println(file.length());  //获取文件大小的字节长度(文件夹没有大小默认是0)
long l1 = file.lastModified();  //获取最后修改的时间
Date date = new Date(l1);
String format1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
System.out.println(format1);

运行结果:
在这里插入图片描述

判断功能的方法

  • public boolean isDirectory(); 判断是否是目录
  • public boolean isFile(); 判断是否是文件
  • public boolean exists(); 判断是否存在
  • public boolean canRead(); 判断是否可读
  • public boolean canwrite(); 判断是否可写
  • public boolean isHidden(); 判断是否隐藏

创建删除功能的方法

  • public boolean createNewFile(); 当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件。
  • public boolean mkdir(); 创建由此抽象路径名命名的目录。
  • public boolean mkdirs(); 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录
  • public boolean delete(); 删除由此抽象路径名表示的文件或目录。

遍历文件夹目录

  • String[] list() 返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录。
  • File[] listFiles() 返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件。
//list遍历返回的是字符数组
 File file = new File("D:/下载");
 String[] list = file.list();
 for (String s : list) {
    
    
 System.out.println(s);

没有遍历文件夹的文件,遍历不完全
在这里插入图片描述

//listfiles遍历返回的是字符数组
public static void getAllfile(String string){
    
    
    File file = new File(string);
    File[] files = file.listFiles();
    for (File file1 : files) {
    
    
        if (file1.isDirectory()){
    
    
            getAllfile(file1.getPath());
        }else {
    
    
           System.out.println(file1); //默认调用的是getputh
        }
    }
}

遍历结果:全部遍历了
在这里插入图片描述
例题:遍历D:/下载 下的所有.java文件

  • 第一种
       File file = new File(string);
        File[] files = file.listFiles();
        for (File file1 : files) {
    
    
            if (file1.isDirectory()){
    
    
                getAllfile(file1.getPath());
            }else {
    
    
                if (file1.getPath().endsWith(".java")){
    
    
                    System.out.println(file1);
                }
            }
        }

遍历结果:
在这里插入图片描述

  1. 第二种:实现过滤器
 public static void getAlltofilter(String string){
    
    
        File file = new File(string);
        File[] files = file.listFiles(new FileFilter() {
    
    
            @Override
            public boolean accept(File pathname) {
    
    
                if (pathname.isDirectory()){
    
    
                    return true;
                }else if(pathname.getPath().endsWith(".java")){
    
    
                    return true;
                }else {
    
    
                    return false;
                }
            }
        });
        for (File file1 : files) {
    
    
            if (file1.isDirectory()){
    
    
                getAlltofilter(file1.getPath());
            }else {
    
    
                System.out.println(file1);
            }
        }
    }

遍历结果:
在这里插入图片描述

2、IO流

在这里插入图片描述

2.1、字节流

一切文件数据(文本、图片、视频等)都是以二进制的形式保存

字节输入流和输出流

OutputStream和InputStream是抽象类,其具体实现要看其子类

InputStream常用方法:

  • abstract int read() 从输入流读取数据的下一个字节。
  • void close() 关闭此输入流并释放与流相关联的任何系统资源。
  • int read(byte[] b) 从输入流读取一些字节数,并将它们存储到缓冲区 b 。
  • int read(byte[] b, int off, int len) 从输入流读取最多 len字节的数据到一个字节数组。

OutputStream常用方法:

  • void close() 关闭此输出流并释放与此流相关联的任何系统资源。
  • void flush() 刷新此输出流并强制任何缓冲的输出字节被写出。
  • void write(byte[] b) 将 b.length字节从指定的字节数组写入此输出流。
  • void write(byte[] b, int off, int len) 从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流。
  • abstract void write(int b) 将指定的字节写入此输出流。

1、FileInputStream和FileOutputStream 文件操作流

FileInputStream

构造方法:

  • FileInputStream(File file) ; 通过打开与实际文件的连接创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
  • FileInputStream(String name) ; 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

FileOutputStream

构造方法:

  • FileOutputStream(File file) ; 创建文件输出流以写入由指定的 File对象表示的文件。
  • FileOutputStream(String name) ;创建文件输出流以指定的名称写入文件。
  • FileOutputStream(File file, boolean append) ;创建文件输出流以写入由指定的 File对象表示的文件,append设置为true 可追加写。
  • FileOutputStream(String name, boolean append) ;创建文件输出流以指定的名称写入文件,append设置为true 可追加写。

测试代码:

//1.FileInputStream 读取 a.txt 文件
        FileInputStream f1 = new FileInputStream("a.txt");
        FileOutputStream f2 = new FileOutputStream("b.txt", true);
        byte[] bytes= new byte[1024];
        int len;
        while ((len =(f1.read(bytes)))!= -1){
    
    
            System.out.println(new String(bytes,0,len));
            //2.FileOutputStream 向b.txt 中写入数据
            //append :true  表示不会覆盖文件数据,可以继续向文件中写入数据
            f2.write(bytes,0,len);
        }
        //3.关闭流资源
        f1.close();
        f2.close();

2、ObjectInputStream和ObjectOutputStream 序列化流

ObjectOutputStream 序列化

构造方法:

  • ObjectOutputStream(OutputStream out)
    创建一个写入指定的OutputStream的ObjectOutputStream。

特有方法:

  • void writeObject(Object obj) 将指定的对象写入ObjectOutputStream。

ObjectInputStream 反序列化

构造方法:

  • ObjecInputStream(InputStream in)
    创建一个写入指定的InputStream的ObjectInputStream。

特有方法:

  • void readObject(Object obj) 将指定的对象写入ObjectInputStream。

实例代码:

       User user = new User("小王",12);
        //1、将对象序列化储存到 c.txt 文件中
        ObjectOutputStream o1 = new ObjectOutputStream(new FileOutputStream("c.txt"));
        o1.writeObject(user);
        o1.close();

        //2、将 c.txt 文件中存储的对象反序列化,得到对象
        ObjectInputStream o2 = new ObjectInputStream(new FileInputStream("c.txt"));
        User o = (User)o2.readObject();
        System.out.println(o);

在这里插入图片描述
注意点:
1、序列化的对象,其实体类要实现 Serializable 接口
2、当某个属性不想被序列化时,可以用 transient 修饰

2.2、字符流

字符输入流和输出流

Reader 和 Writer 是抽象类,其具体实现要看其子类

Reader常用方法:

  • abstract void close() 关闭流并释放与之相关联的任何系统资源。
  • int read() 读一个字符
  • int read(char[] cbuf) ; 将字符读入数组
  • read(char[] cbuf, int off, int len) ; 将字符读入数组的一部分。
  • read(CharBuffer target) ; 尝试将字符读入指定的字符缓冲区。

Writer常用方法:

  • abstract void close() ; 关闭流,先刷新。
  • abstract void flush() ; 刷新流。
  • void write(char[] cbuf) ; 写入一个字符数组。
  • abstract void write(char[] cbuf, int off, int len) ; 写入字符数组的一部分。
  • void write(String str) ; 写一个字符串
  • void write(String str, int off, int len) ; 写一个字符串的一部分。

1、InputStreamReader和OutputStreamWriter 转换流

InputStreamReader

构造方法:

  • InputStreamReader(InputStream in) ;创建一个使用默认字符集的InputStreamReader。
  • InputStreamReader(InputStream in, String charsetName) ; 创建一个使用命名字符集的InputStreamReader。

OutputStreamWriter

构造方法:

  • OutputStreamWriter(OutputStream out) ; 创建一个使用默认字符编码的OutputStreamWriter。
  • OutputStreamWriter(OutputStream out, Charset cs) ; 创建一个使用给定字符集的OutputStreamWriter。
public static void main(String[] args) throws Exception {
    
    
        InputStreamReader ir1 = new InputStreamReader(new FileInputStream("b.txt"),"UTF-8");
        //charsetName 设置文件编码格式 append:true 设置可追加写入
        OutputStreamWriter or1 = new OutputStreamWriter(new FileOutputStream("d.txt",true),"GBK");
        //1、InputStreamReader读取b.txt 文件
        int len = 0;
        char[] chars = new char[1024];
        while ((len = (ir1.read(chars))) != -1){
    
    
            String s = new String(chars, 0, len);
            System.out.println(s);
            //2、OutputStreamWriter 将读取到的b.txt 文件的内容,写入到d.txt
            or1.write(s);
        }
        //3、关闭资源
        or1.close();
        ir1.close();
    }

控制台输出:
在这里插入图片描述
b.txt 文件
在这里插入图片描述
d.txt 文件
在这里插入图片描述

2.3、缓冲流

1、BufferedInputStream和 BufferedOutputStream 字节缓冲流

BufferedInputStream构造方法:

  • BufferedInputStream(InputStream in) ; 创建一个 BufferedInputStream并保存其参数,输入流 in ,供以后使用。
  • BufferedInputStream(InputStream in, int size) ; 创建 BufferedInputStream具有指定缓冲区大小,并保存其参数,输入流 in ,供以后使用。

测试代码:

BufferedInputStream b1 = new BufferedInputStream(new FileInputStream("a.txt"));
        int len = 0;
        byte[] bytes = new byte[1024];
        while ((len=(b1.read(bytes))) != -1){
    
    
            String s = new String(bytes, 0, len);
            System.out.println(s);
        }
        b1.close();

在这里插入图片描述

BufferedOutputStream构造方法:

  • BufferedOutputStream(OutputStream out) ; 创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
  • BufferedOutputStream(OutputStream out, int size) ; 创建一个新的缓冲输出流,以便以指定的缓冲区大小将数据写入指定的底层输出流。

测试代码:

public static void main(String[] args) throws Exception {
    
    
        BufferedOutputStream b1 = new BufferedOutputStream(new FileOutputStream("a.txt", true));
        b1.write("妖精的尾巴".getBytes());
        b1.flush();
        b1.close();
    }

在这里插入图片描述

2、BufferedReader和BufferedWriter 字符缓冲流

BufferReader构造方法:

  • BufferedReader(Reader in) ;创建使用默认大小的输入缓冲区的缓冲字符输入流。
  • BufferedReader(Reader in, int sz) ;创建使用指定大小的输入缓冲区的缓冲字符输入流。

特有方法:

  • String readLine() 读一行文字, 读取到末尾,返回值不是-1 而是 null。

测试代码:

        BufferedReader b1 = new BufferedReader(new FileReader("a.txt"));
        String len;
        while ((len = b1.readLine()) != null){
    
    
            System.out.println(len);
        }
        b1.close();

结果:
在这里插入图片描述

BufferedWriter构造方法:

  • BufferedWriter(Writer out) ; 创建使用默认大小的输出缓冲区的缓冲字符输出流。
  • BufferedWriter(Writer out, int sz) ; 创建一个新的缓冲字符输出流,使用给定大小的输出缓冲区。

特有方法:

  • newLine() 写入一个行分隔符

测试代码:

 BufferedWriter b1 = new BufferedWriter(new FileWriter("a.txt", true));
        b1.newLine(); //换行
        b1.write("风吹云雾见真容");

        b1.flush();
        b1.close();

结果:
在这里插入图片描述
学习视频:https://www.bilibili.com/video/BV1Y4411P7Ey
参考视频:https://www.bilibili.com/video/BV1v5411W7ZE

5.多线程

1、创建线程的三种方式

  • 继承Thread类
//线程实现方式一
public class ThreadDome01 extends Thread{
    
    

    @Override
    public void run() {
    
    
        for (int i=0;i<20;i++){
    
    
            System.out.println("看小说"+i);
        }
    }
    public static void main(String[] args) {
    
    
        //开启ThreadDome01线程
        new ThreadDome01().start();
        for (int i=0;i<2000;i++){
    
    
            System.out.println("主线程执行"+i);
        }
    }
}
  • 实现Runnable接口
public class RunnableDome01 implements Runnable{
    
    
    @Override
    public void run() {
    
    
        for(int i = 0;i<20;i++){
    
    
            System.out.println("runnable实现线程"+i);
        }
    }

    public static void main(String[] args) {
    
    
        //开启实现Runnable接口的线程
        RunnableDome01 dome01 = new RunnableDome01();
        new Thread(dome01).start();
        for (int i = 0;i<200;i++){
    
    
            System.out.println("main线程"+i);
        }
    }
}
  • 实现Callable接口
    例1:
public class CallableDome01 implements Callable<Boolean> {
    
    
    @Override
    public Boolean call(){
    
    
       for(int i = 0;i<20;i++){
    
    
            System.out.println("runnable实现线程"+i);
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        CallableDome01 t1 = new CallableDome01();

        //1、开启服务
        ExecutorService executor = Executors.newFixedThreadPool(3); //3为开启线程个数

        //2、开启线程
        Future<Boolean> f1 = executor.submit(t1);
      
        //3、获取返回值
        Boolean aBo1 = f1.get();

        //4、关闭服务
        executor.shutdown();
    }
}

例2:

public class CallableDome02 {
    
    
    public static void main(String[] args) {
    
    
        FutureTask futureTask = new FutureTask(new MyThread());
        new Thread(futureTask).start();
        try {
    
    
            Integer integer = (Integer) futureTask.get();
            System.out.println(integer);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}
class MyThread implements Callable{
    
    
    @Override
    public Integer call() throws Exception {
    
    
        return 100;
    }
}

2、线程方法和状态

在这里插入图片描述

  • sleep() 线程休眠
    线程休眠,不会释放锁,会阻塞

  • yield() 礼让方法
    对于同一个对象,开启的两个线程,进入cup中的线程调用yield()方法,会改变线程为就绪状态, 下一次两个线程谁先执行看cup心情

  • setpriority() 设置线程的优先级
    优先级默认是5,权重大的优先执行

  • join() 线程插队
    可以使插队的线程优先主线程执行

  • setDaemon(true) 设置线程为守护线程
    Thread thread = new Thread(dome02);
    thread.setDaemon(true);

3、线程同步

3.1、线程同步中死锁问题

  • 什么是死锁:多个线程持有对方的资源,形成僵持
  • 产生死锁的四个条件:
    1、互斥条件:一个资源每次只能被一个线程访问
    2、请求与保持条件:一个进程因请求资源而阻塞式,对以获得的资源保持不放
    3、不剥夺条件:进程获得的资源,在未使用完成之前,不能被强行剥夺
    4、循环等待条件 :若干个进程之间形成一种头尾相接的循环等待资源关系
    线程死锁代码:
public class DeadLock {
    
    
    public static void main(String[] args) {
    
    
        new Thread(new TestThread(1)).start();
        new Thread(new TestThread(0)).start();
    }

}
class A{
    
    }
class B{
    
    }

class TestThread implements Runnable{
    
    
    static A a = new A();
    static B b = new B();
    private int flag;
    public TestThread(int flag){
    
    
        this.flag = flag;
    }
    @Override
    public void run() {
    
    
        if (flag == 1){
    
    
            synchronized (a){
    
                  System.out.println(Thread.currentThread().getName()+"获取的 a 对象的锁");
                try {
    
    
                    Thread.sleep(1000); //模拟延迟,不让1一下拿到两个对象的锁
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                //1、死锁
                /*synchronized (b){
                    System.out.println(Thread.currentThread().getName()+"获取的 b 对象的锁");
                }*/
            }
            //2、解决死锁的方法
            synchronized (b){
    
    
                System.out.println(Thread.currentThread().getName()+"获取的 b 对象的锁");
            }
        }else if (flag == 0){
    
    
            synchronized (b){
    
    
                System.out.println(Thread.currentThread().getName()+"获取的 b 对象的锁");
                try {
    
    
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                //1、死锁
                /*synchronized (a){
                    System.out.println(Thread.currentThread().getName()+"获取的 a 对象的锁");
                }*/
            }
            //2、解决死锁
            synchronized (a){
    
    
                System.out.println(Thread.currentThread().getName()+"获取的 a 对象的锁");
            }
        }
    }
}

3.2、实现线程同步

  • synchronized 同步代码块
    synchronized 锁的是对象,一般是公共资源,可用于方法。
//使用同步代码块来解决Arraylist线程不安全问题
public class ListTest {
    
    
    public static void main(String[] args) {
    
    
        //arraylist是线程不安全的
        List<String> list = new ArrayList<>();

        synchronized (list){
    
    
            for (int i = 0; i < 1000; i++) {
    
    
                new Thread(()->{
    
    
                    list.add(Thread.currentThread().getName());                
                }).start();
            }
        }
        try {
    
    
            Thread.sleep(8000);
            System.out.println(Thread.currentThread().getName());
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}
  • lock锁
    Reentrantlock 可重入所
public class LockDome {
    
    
    public static void main(String[] args) {
    
    
        Test01 test01 = new Test01();
        new Thread(test01).start();
        new Thread(test01).start();
        new Thread(test01).start();
    }
}

class Test01 implements Runnable{
    
    

    private int i = 10;
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
    
    
        while (true){
    
    
            try {
    
    
                lock.lock();
                if (i>0){
    
    
                    try {
    
    
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }                 System.out.println(Thread.currentThread().getName()+"拿到了第"+i--+"票");
                }else {
    
    
                    break;
                }
            }finally {
    
    
                lock.unlock();
            }
        }
    }
}

4、线程通信

  • 通过缓冲区(管程法)
/**
 * 测试生产者和消费者模型—> 利用缓冲区解决:管程法
 * 生产者、消费者、产品、缓存区
 */
public class TestPC {
    
    
    public static void main(String[] args) {
    
    
        SynContainer container = new SynContainer();
        new Thread(new Productor(container)).start();
        new Thread(new Consumer(container)).start();
    }
}

//生产者
class Productor implements Runnable{
    
    
    SynContainer container;
    Productor(SynContainer container){
    
    
        this.container = container;
    }
    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            container.push(new Chicken(i));
            System.out.println("生产了第"+i+"只鸡");
        }
    }
}

//消费者
class Consumer implements Runnable{
    
    
    SynContainer container;
    Consumer(SynContainer container){
    
    
        this.container = container;
    }
    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            container.pop(new Chicken(i));
            System.out.println("消费了第"+i+"只鸡");
        }
    }
}

//产品
class Chicken{
    
    
    private int id; //编号
    Chicken(int id){
    
    
        this.id = id;
    }
}

//缓存区
class SynContainer{
    
    
    //设置容器大小
    Chicken[] chickens = new Chicken[10];
    //容器计数器
    int conut = 0;

    //生产者生产放入产品
    public synchronized void push(Chicken chicken){
    
    
        if (conut == chickens.length){
    
    
            //通知消费者消费
            try {
    
    
                this.wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        chickens[conut] = chicken;
        conut++;
        //通知消费者消费
        this.notifyAll();
    }

    //消费者消费取出产品
    public synchronized Chicken pop(Chicken chicken){
    
    
        if (conut == 0){
    
    
            //通知生产者生产
            try {
    
    
                this.wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        conut--;
        Chicken chicken1= chickens[conut];

        //吃完通知生产者生产
        this.notifyAll();
        return chicken1;
    }
}

5、线程池

public class ThreadPool {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        //1、创建线程池  newFixedThreadPool 参数为池子大小
        ExecutorService service = Executors.newFixedThreadPool(5);

        //2、执行线程
        service.submit(new MyThread01());
        service.submit(new MyThread01());
        service.execute(new MyThread01());
        Future submit = service.submit(new MyThread02());
        Integer number = (Integer) submit.get();
        System.out.println(number);

        //3、关闭线程池
        service.shutdown();

    }
}

class MyThread01 implements Runnable{
    
    

    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName()+"MyThread01");
    }
}

class MyThread02 implements Callable {
    
    

    @Override
    public Integer call() throws Exception {
    
    
        System.out.println(Thread.currentThread().getName()+"MyThread02");
        return 11;
    }
}

注意点:

  • 线程池submit()方法 既可以执行Runnable接口 线程,也可执行 Callable 接口线程;
  • 线程池execute()方法只能执行 Runnable接口 线程

视屏:https://www.bilibili.com/video/BV1V4411p7EF?p=27

猜你喜欢

转载自blog.csdn.net/Start1234567/article/details/107045259