JavaSE 进阶-javase进阶(三)

简介: JavaSE 进阶-javase进阶

JavaSE 进阶-javase进阶(二)https://developer.aliyun.com/article/1469543


HashMap原理简单理解

HashMap重要属性:

hashMap构造器:

put方法:

新增方法中的hashCode算法:

计算位置的方法和entry对象:

经典面试题:

装填因子,负载因子,加载因子 为什么是0.75

装填因子设置为1:空间利用率得到了很大的满足,但是很容易碰撞,产生链表,查询效率边低

装填因子:0.5:碰撞几率低,扩容,产生链表几率低,查询快

于是HashMap做了个折中

主数组的长度为什么是2^n

原因1:

数组长度影响位置,H&length-1等效,等效的前提就是length必须是2的整数倍,

原因2:

防止哈希冲突,位置冲突

验证整数倍:

验证非正数倍

非整数倍,位置一样,会产生链表,导致效率变低

Collections工具类

Collections不支持创建对象

package CollectionsPrc;
import java.util.ArrayList;
import java.util.Collections;
public class test1 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("aa");
        list.add("bb");
        list.add("cc");
        Collections.addAll(list,"dd","ee","ff");
        System.out.println(list);
        //必须在有需要的集合查找
        //binarySearch
        Collections.sort(list);
        System.out.println(Collections.binarySearch(list, "cc"));
//        copy
        ArrayList<String> list2 = new ArrayList<>();
        Collections.addAll(list2,"dd1","e1e","f1f");
        Collections.copy(list,list2);
        System.out.println(list);
//        fill填充
        Collections.fill(list2,"yyy");
        System.out.println(list2);
    }
}

io流

file类

文件,目录

文件:内存中存放的数据计算机关机后会消失,要长久的保存数据,就要使用硬盘,光盘等,为了便于数据的管理和检索,引入了文件的概念,可以有一篇文章,一段视频,一个可执行程序。

目录(文件夹):

成千上万的文件,如果不分类的话,会很难管理,于是我们用文件夹去分类,管理。

操作系统:

如果需要查看文件/目录星系,右键属性就好了,

现在我想用java来操纵文件/目录,我该如何?

java程序最典型的特点,面向对象,java程序最擅长的就是操纵对象,盘符上的 文件目录,将它们的各种信息封装,封装成一个对象,

java最擅长就是操作对象,这个对象属于---》file类

盘符上的文件---》封装为对象---?对象属于file类的对象--》有了这个对象,我们的程序就可以直接操作这个对象,通过获取文件的各种信息,还可以对文件进行操作,创建或者删除等等等.

对文件的操作:

public static void main(String[] args) throws IOException {
        File f = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\src\\test.txt");
        File f1 = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\src\\test.txt");//跨平台建议使用这种
//   常用方法
        System.out.println("文件是否可读 : "+f.canRead());
        System.out.println("文件是否可写 : "+f.canWrite());
        System.out.println("文件的名字 : "+f.getName());
        System.out.println("文件的上级目录 :"+f.getParent());
        System.out.println("是否是一个目录 :"+f.isDirectory());
        System.out.println("是否是一个文件 :"+f.isFile());
        System.out.println("是否隐藏 :"+f.isHidden());
        System.out.println("文件大小 : "+f.length());
//        if (f.exists()){ 如果有那就删除,没有就创建
//                f.delete();
//        }else {
//            f.createNewFile();
//        }
        System.out.println(f==f1);
        System.out.println(f.equals(f1));
        //和路径相关的
        System.out.println("绝对路径 :"+f.getAbsolutePath());
        System.out.println("相对路径 :"+f.getPath());
        System.out.println("toString :"+f.toString());
        File file = new File("demo.txt");
        if (!file.exists()){
            file.createNewFile();//创建到了项目根目录里
        }
//        绝对路径指一个精确,完成的路径
        System.out.println("绝对路径 :"+file.getAbsolutePath());
//        相对路径有一个参会造物,相对这个·参照物的路径
//        相对这个位置,main方法的相对位置为项目根路径
        System.out.println("相对路径 :"+file.getPath());
//        toString的效果永远是相对路径
        System.out.println("toString :"+file.toString());
    }

对目录的操作

public static void main(String[] args) {
        File f = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\src");
        File f1 = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\src");//跨平台建议使用这种
//   常用方法
        System.out.println("文件是否可读 : "+f.canRead());
        System.out.println("文件是否可写 : "+f.canWrite());
        System.out.println("文件的名字 : "+f.getName());
        System.out.println("文件的上级目录 :"+f.getParent());
        System.out.println("是否是一个目录 :"+f.isDirectory());
        System.out.println("是否是一个文件 :"+f.isFile());
        System.out.println("是否隐藏 :"+f.isHidden());
        System.out.println("文件大小 : "+f.length());
        File f2 = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\src\\a\\b\\c");
//        f2.mkdir();创建单层目录
//        f2.mkdirs();创建多层目录
//        f2.delete();删除,只删除一层,如果是删除目录的话只删除没有内容的,如果有内容就不会删除
        String[] list = f.list();//遍历目录下的对应的名字
        for (String s : list) {
            System.out.println(s);
        }
        System.out.println("============");
        File[] files = f.listFiles();
        for (File file : files) {//作用相对上面发那个发,多了file对象,作用更加广泛
            System.out.println(file.getName());
        }
        }

file:封装文件/目录各种信息,对文件和目录进行操作,但是我们不可以获取到文件和目录的内容,

于是乎!io!!!流!!!

字符流

IO流体系结构:

I/O:input/output的缩写,用于处理设备之间的数据的传输

形象理解:

案例:通过java程序完成文件的复制

功能分解:使用到第一个流 file reader

功能分解1:初体验io流的文件内容读取

public static void main(String[] args) throws IOException {
//        文件--》程序
//        1.有一个文件---》创建file类对象
        File file = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\demo.txt");
//        2.利用filereader这个流将流连接到源文件--》file流对象
        FileReader fr = new FileReader(file);
//        3.进行操作 --》读取操作
//        下面的代码我们验证了,如果到了文件的结尾,那么读取的内容为-1
/*
        int i = fr.read();
        int i2 = fr.read();
        int i3 = fr.read();
        int i4 = fr.read();
        int i5 = fr.read();
        System.out.println(i);
        System.out.println(i2);
        System.out.println(i3);
        System.out.println(i4);
        System.out.println(i5);
        */
//方法一
//        int i = fr.read();
//        while (i!=-1){
//            System.out.println(i);
//            i = fr.read();
//        }
//        方法二
        int i =0;
        while ((i = fr.read())!=-1){
            System.out.print((char) i);
        }
//        4.关闭流--》关闭
//        流,数据库,网络资源,考jvm无法帮助我们关闭,必须程序元手动关闭
        fr.close();
    }

新需求,我每次读取5个,不够我在继续读,读到没有为止。

public static void main(String[] args) throws IOException {
        //        1.有一个文件---》创建file类对象
        File file = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\demo.txt");
//        2.利用filereader这个流将流连接到源文件--》file流对象
        FileReader fr = new FileReader(file);
//        3.进行操作 --》读取操作
        char [] ch = new char[5];
        int len = fr.read(ch);//一次读取五个,返回值是数组中的有效长度
        while (len!=-1){
/*            方法1
        for (int i = 0; i < len; i++) {
                System.out.println(ch[i]);
            }
            System.out.println("!");
            len = fr.read(ch);*/
            String str  = new String(ch,0,len);
            System.out.println(str);
            len = fr.read(ch);
        }
//        4.关闭操作
        fr.close();
    }

利用缓冲数组,做一个操作

功能分解2:程序输出文件,filewriter

一个一个向外输出

public static void main(String[] args) throws IOException {
//目标文件
       File file = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\demo01.txt");
//      fw连接到文件
        FileWriter fw = new FileWriter(file);
//开始操作
        String str = "你好,小朋友";
        for (int i = 0; i < str.length(); i++) {
            fw.write(str.charAt(i));
        }
//        关闭流
        fw.close();
    }

发现:如果目标文件不存在,会自动创建文件写入,如果存在,

new FileWriter(file);,相当于对源文件覆盖,而并不是追加

new FileWriter(file,true);为true的时候才会有追加操作

new FileWriter(file,false);为false的时候会是覆盖操作,默认也是覆盖

批量,使用缓冲数组向外输出,

package TestIO.com.hyc.IO01;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class Test03 {
    public static void main(String[] args) throws IOException {
//目标文件
       File file = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\demo01.txt");
//      fw连接到文件
        FileWriter fw = new FileWriter(file,true);//追加
//开始操作
        String str = "老子开心";
        char[] ch =str.toCharArray();
        fw.write(ch);
//        关闭流
        fw.close();
    }
}

接下来是合并操作!

使用reader和writer完成文件复制

package TestIO.com.hyc.IO01;
import java.io.*;
public class Test04 {
    public static void main(String[] args) throws IOException {
//        创建file类
//        一个复制本体,一个是接受复制
        File file = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\demo01.txt");
        File file2 = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\demo02.txt");
//        创建输出流
        FileReader reader = new FileReader(file);
//        FileReader reader1 = new FileReader(file2);
        FileWriter writer = new FileWriter(file2,true);//用追加的形式不用覆盖的形式
        //region 1.一个一个字符的形式
        int n = reader.read();
        while (n!=-1){
                writer.write(n);
                n = reader.read();
        }
        //endregion
        //region 2.缓冲数组的形式
        char[] ch = new char[5];
        int len = reader.read(ch);
        while (len!=-1){
            writer.write(ch,0,len);//输出缓冲数组有效的长度
            len = reader.read(ch);
        }
        //region 3.缓冲数组转成字符串的形式
        char[] ch1 = new char[5];
        int len1 = reader.read(ch1);
        while (len1!=-1){
            String s =new String(ch1,0,len1);
            writer.write(s);//输出缓冲数组有效的长度
            len1 = reader.read(ch1);
            System.out.println(len1);
        }
        //endregion
//关闭流,后用先关
        writer.close();
        reader.close();
    }
}

注意:不要用字符流去操作文本格式以外的文件

字节流

文本文件建议使用字符流,

非文本文件,如图片,视频等建议使用字节流。

以读取图片为例

public static void main(String[] args) throws IOException {
//        功能:利用字节流将文件内容读到程序中
        File file = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\image-20210702102548410.png");
        //创建一个字节流
        FileInputStream inputStream = new FileInputStream(file);
        /*
        * 细节1:文件是utf-8储存的,所以英文字母底层占1字节,中文底层占3字节
        * 细节2:如果是文本文件建议使用字符流,非文本文件使用字节流
        * 细节3:
        * read()读取的一个细节返回是int类型,不是byte类型
        * 这个方法底层做了处理,让返回的数据是正数
        * 就是为例避免如果字节返回的是-1,那到底是读入字节还是最后一个字符?
        * */
//       操作
        int read = inputStream.read();
        int count = 0;//计数器,计算多少个字节
        while (read!=-1){
            count++;
            System.out.println(read);
            read = inputStream.read();
        }
        System.out.println("count"+count);
//        关闭
        inputStream.close();
    }

缓冲数组输入

public static void main(String[] args) throws IOException {
        File file = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\image-20210702102548410.png");
        File file2 = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\hyc.png");
        FileInputStream inputStream = new FileInputStream(file);
        FileOutputStream OutputStream = new FileOutputStream(file2);
        byte[] b = new byte[1024*6];
        int len = inputStream.read(b);
        while (len!=-1){
             OutputStream.write(b,0,len);
            len = inputStream.read(b);
        }
        inputStream.close();
        OutputStream.close();
    }

缓数组读取

public static void main(String[] args) throws IOException {
        File file = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\image-20210702102548410.png");
        FileInputStream inputStream = new FileInputStream(file);
        byte[] b = new byte[1024*6];
        int len = inputStream.read(b);
        while (len!=-1){
            for (int i = 0; i < len; i++) {
                System.out.println(b[i]);
            }
            len = inputStream.read(b);
        }
        inputStream.close();
    }

缓冲字节流

public static void main(String[] args) throws IOException {
        File file = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\image-20210702102548410.png");
        File file2 = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\hyc1.png");
        FileInputStream inputStream = new FileInputStream(file);
        FileOutputStream OutputStream = new FileOutputStream(file2);
        BufferedInputStream bis = new BufferedInputStream(inputStream);
        BufferedOutputStream bos = new BufferedOutputStream(OutputStream);
        byte[] b = new byte[1024*6];
        int len = bis.read(b);
        while (len!=-1){
            bos.write(b,0,len);//底层帮助我们刷新过缓冲区了
            len = bis.read(b);
        }
        bis.close();
        bos.close();
    }

缓冲字符流

字符流特殊的功能,读一行

public static void main(String[] args) throws IOException {
        File file = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\demo02.txt");
        File file2 = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\demo03.txt");
        FileReader fl = new FileReader(file);
        FileWriter fw = new FileWriter(file2);
        BufferedReader bf = new BufferedReader(fl);
        BufferedWriter bw = new BufferedWriter(fw);
        char [] ch = new char[6];
/*        int len = bf.read(ch);
        while(len!=-1){
            bw.write(ch,0,len);
            len =bf.read(ch);
        }*/
        String s = bf.readLine();
        while (s!=null){
            bw.write(s);
            bw.newLine();
            s = bf.readLine();
        }
        bw.close();
        bf.close();
    }

转换流

作用:

  1. 作用:将字节流和字符流进行转换。
  2. 属于字节流还是字符流?,一般通过后缀查看。

InputStreamReader---》字节输入流---》字符的输入流

OutputStreamWriter--》字符输出流---》字节的输出流

理解图:

功能:将输入的字节流转换为输入的字符流,然后完成对程序的查看

功能实战:用转换流实现文件复制

public static void main(String[] args) throws IOException {
//        首先有一个源文件
      File f = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\demo.txt");
//      有一个输出的目标文件
      File f1 = new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\demo4.txt");
//输入方向
        FileInputStream fis = new FileInputStream(f);
        InputStreamReader isr = new InputStreamReader(fis, "utf-8");
//输出方向
        FileOutputStream fos = new FileOutputStream(f1);
        OutputStreamWriter osw = new OutputStreamWriter(fos,"utf-8");
//        开始操作文件
        char[] ch = new char[10];
        int len = isr.read(ch);
        while (len!=-1){
            osw.write(ch,0,len);
            len = isr.read(ch);
        }
        osw.close();
        isr.close();
    }

对象流,序列化与反序列化

对象流:用于存数和读取基本类型数据或对象的处理流,他的强大之处及时可以把java的对象写入到文件中,也可以从文件中还原。

序列化与反序列化:

序列化:

把内存中的java对象转换成与平台无关的二进制数据,从而把这种二进制数据持久的保存在磁盘上,或者通过网络将这种二进制数据的传输到另一个网络节点

反序列化:当其他程序获取了这种二进制数据,可以把数据恢复成java对象,---》反序列化

对象流代码:

public static void main(String[] args) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\demo.txt")));
//        将内存中的字符串写入到文件
        oos.writeObject("你好");
        oos.close();
    }

运行结果:

我们看不懂文件的内容,但是程序是可以看懂的

我们写一个程序来读取我们放到文件中的数据

public static void main(String[] args) throws IOException, ClassNotFoundException {
//        将文件中保存的字符串,读入到内存
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\java工程师\\javaSE代码\\javaSE源码\\javaSE\\demo.txt")));
            String s = (String) (ois.readObject());
        System.out.println(s);
        ois.close();
    }

运行结果

如果我们想要将自定义的类序列化,那么必须实现一个接口:Serializable

接口内部,什么都没有,这种接口叫标识接口,只有实现了这个接口的类才可以实现序列化,直接序列化会导致 程序异常

多线程

程序,进程,线程

程序:为了完成特定任务,用某种语言编写的一组指令的集合,他是静态的

进程:程序的一次执行过程,他是动态的,给正在运行的程序分配资源,进程的生命周期,:创建,存在,消亡的过程

线程:进程可以进一步细化为线程,是一个程序内部的执行途径,若一个进程同一时间并且执行多个线程,就是支持多线程

单核cpu:

多核cpu:

并行和并发:

并行:多个cpu同时执行多个任务

并发:一个cpu采用时间片切换的方式,“同时”执行多个任务,其实就是时间片很短,来回切换不同的任务,造成一个同时的假象

创建线程的多种方式

在学习,多线程前,我们的代码看起来是单线程,其实我们的代码是三个线程的

继承thread类

线程类----》线

继承thread类

线程类----》线程对象

子线程类:

public class TestThread extends Thread {
//    一会线程对象就要开始抢夺资源了,但是不是随便写就可以的
//    线程的任务或者逻辑要卸载run方法中
    @Override
    public void run() {
        for (int i = 0; i < 10;i++) {
            System.out.println(i);
        }
    }
}

测试类:

package Thread.com.hyc.Thread;
public class Test01 {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程1 :"+i);
        }
//        制造线程和主线程抢夺资源
        TestThread tt = new TestThread();
        //  tt.run();   想要执行run方法的任务--》run方法不能直接调用,直接调用会被当成普通方法
//    想要tt子线程有作用必须要开启线程
        tt.start();//是thread类中的方法
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程 :"+i);
        }
    }
}

执行原理图:

设置和读取线程名字的方法

  1. setname,getname方法来设置
  2. 线程类构造器。实际上是调用了父类的有参构造器

小案例:买火车票:

每一个窗口是一个对象

目前出现了重复的问题,我们用static修饰变量来解决,但是还是会有重复或者负数的出现

public class TestThread extends Thread{
//    每个窗口是i一个线程对象,每个窗口对象执行的方法放入run方法中
   static int ticketNum = 10;//多个对象共享十张票
    public TestThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i = 1; i <=100; i++) {
            if (ticketNum>0){//只有邮票的时候才买
                System.out.println(Thread.currentThread().getName()+" : "+"我买到了哈尔冰第"+ticketNum--+"票");
            }
        }
    }
}

用实现接口的方式创建线程

实现runnable接口

实现run方法,

package Thread.com.hyc.Thread.demo02;
public class TestThread implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <=10; i++) {
            System.out.println(Thread.currentThread().getName()+" : "+i);
        }
    }
}

简单输出:

package Thread.com.hyc.Thread.demo02;
public class test {
    public static void main(String[] args) {
        TestThread tt = new TestThread();
        Thread t = new Thread(tt);
        t.setName("线程10086");
        t.start();
        for (int i = 1; i <= 10; i++) {
            System.out.println(Thread.currentThread().getName()+" : "+i);
        }
    }
}

实际开发中,继承thread类和实现接口,那个更适用于开发---------》方式2

  1. 方式1的话有java的单继承局限性,因为继承了Thread就没办法继承其他类1了
  2. 方式2的共享资源能力会强一些,不需要非得用static去修饰

继承thread和实现接口有练习吗

关系图:

实现callable接口

对比第一第二中方法创建线程的方式发现,无论第一钟继承Thread类还是第二种实现runnable接口都需要一个run方法,这个run方法有不足

  1. 没有返回值
  2. 不能抛出异常

基于上面两个不足,jdk1.5之后出现了第三种创建线程的方法。

实现callble接口的好处:有返回值,可以抛出异常

缺点:创建线程相对麻烦

代码示例

package Thread.com.hyc.Thread.demo04;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test01 implements Callable<Integer> {
    /*
    * 1.实现callable是个泛型接口
    * 从call方法:又返回值可以抛出异常
    * */
    @Override
    public Integer call() throws Exception {
        return new Random().nextInt(10);
    }
    static class Test{
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            Test01 trn =new Test01();
            FutureTask ft = new FutureTask(trn);
            Thread t = new Thread(ft);
            t.start();
//            获取线程得到的返回值
            Object o = ft.get();
            System.out.println(o);
        }
    }
}

线程的生命周期:

线程声明周期:线程开始--》线程消亡

线程经历了什么

线程常见方法

  • start():启动当前线程,表面上调用start方法,
  • run():线程类继承thread类或者实现runnable接口,都要重新实现run,run方法写要实现的逻辑代码。
  • currentThread类中一个静态方法:获取当前正在执行的线程
  • setNmae:这只线程名字
  • getName:读取线程名字

设置线程优先级

  1. 同优先级的线程,采用的侧落就是先到先服务,谁先就先服务谁
  2. 如果优先级别高,被cpu调度的概率更高

Thread线程的优先级:1-10,默认是5

测试代码示例:

package Thread.com.hyc.Thread.demo05;
public class TestThread01 extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <=10; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}
class testThread02 extends Thread{
    @Override
    public void run() {
        for (int i = 20; i <30; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}
class Test{
    public static void main(String[] args) {
//        测试让两个子线程抢资源
        TestThread01 t1 = new TestThread01();
        testThread02 t2 = new testThread02();
        t1.setPriority(10);
        t1.setName("hyci");
        t1.start();
        t2.setPriority(1);
        t2.setName("hycq");
        t2.start();
    }
}

join方法

案例:白金卡,黄金卡,插队特权

当一个线程调用了join方法,这个线程就会先被执行,他执行完才会执行别的线程,

PS:必须先就绪,才可以执行join方法

代码示例:

package Thread.com.hyc.Thread.demo06;
public class TestThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <=10; i++) {
            System.out.println(this.getName()+i);
        }
    }
    public TestThread(String name) {
        super(name);
    }
}
class test {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i <=100; i++) {
            System.out.println(Thread.currentThread().getName()+i);
            if (i==6){
                TestThread tt = new TestThread("child--Thread");
                tt.start();
                tt.join();//钻石vip无条件插队
            }
        }
    }
}

sleep

可以让当前线程按照设置时间进入阻塞状态,时间够了,回到就绪状态

public class Test01 {
    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(4000);
        System.out.println("hhhhhhhhh");
    }
}

小案例,秒表:

class Test2{
    public static void main(String[] args) {
            while (true){
                Date d  = new Date();
                DateFormat df = new SimpleDateFormat("HH:mm:ss");
            System.out.println(df.format(d));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
        }

伴随线程

SetDaemon,

我想将子线程设置为主线程的伴随线程,主线程停止的时候,子线程也不要执行了。

案例:皇上---》驾崩---》妃子陪葬

PS:先设置伴随,让后再启动
代码案例

package Thread.com.hyc.Thread.demo08;
public class TestThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <=1000; i++) {
            System.out.println("子线程---"+i);
        }
    }
}
class Test{
    public static void main(String[] args) {
        TestThread tt = new TestThread();
        tt.setDaemon(true);
        tt.start();
//        主线程中还要输出1-10的数字
        for (int i = 1; i < 10; i++) {
            System.out.println("main"+i);
        }
    }
}

同步方法:

用静态方法会出现下面的问题:

1.会出现,还没有--就被抢走资源的情况,导致连续有同一张票被抢

2.会出现负数票

上面的代码,出现了重复,或者是错票的问题,就是线程安全引发的问题,

原因:多个线程,争抢资源的过程中,导致共享的资源出现问题,一个线程没有执行完毕,另一个线程就来抢资源了

解决方法:同步代码块,在我的程序上加上锁

这也有几个操作,

方法1:runnable接口

使用锁来完成一个次只有一个线程可以执行锁中的代码

package Thread.com.hyc.Thread.demo09;
public class BuyTicketThread implements Runnable {
    int ticketNum = 10;
    @Override
    public void run() {
        for (int i = 1; i <100; i++) {
            synchronized (this){//把具有安全隐患的代码锁住即可,如果锁多了就会效率降低
                if (ticketNum>0){
                    System.out.println("我在"+Thread.currentThread().getName()+"窗口抢到了第"+ticketNum--+"张票");
                }
            }
        }
    }
}
Thread

如果我们直接和之前的接口一样在synchronized (this)还是用this的话,还是有问题,我们要保证锁只有一把。

class BuyTicketThread1 extends Thread{
    int ticketNum = 10;
    @Override
    public void run() {
        for (int i = 1; i <100; i++) {
            synchronized (this){//把具有安全隐患的代码锁住即可,如果锁多了就会效率降低
                if (ticketNum>0){
                    System.out.println("我在"+Thread.currentThread().getName()+"窗口抢到了第"+ticketNum--+"张票");
                }
            }
        }
    }
}

如果我们创建了三个实例,那么this就不是同一把锁,所以我们不能用this,我this换成字符串abc,就解决了问题,为什么?因为常量池里就只有一个abc,所以大家用的都是这个,就不会出现不是同一把锁的问题,不过我们不会直接用abc,一般放线程类的字节码信息,

public void run() {
        for (int i = 1; i <100; i++) {
            synchronized (BuyTicketThread1.class){//把具有安全隐患的代码锁住即可,如果锁多了就会效率降低
            }
        }
    }

锁中括号内放的一定是引用类型,不可以是基本类型

尽量不要用String和包装类来做同步监视器

如果有,建议使用final修饰同步监视器

小结:

  1. 第一线程来到同步代码块,发现是open状态,需要关闭,之后第一线程去执行其中代码
  2. 第一线程执行过程中,发生线程切换也无所谓,但是切换之后线程一样调用不了代码,应为被第一个线程锁住了,其他的线程都无法调用,等待到资源重新回到第一线程,继续执行
  3. PS:同步代码块中能发生CPU资源线程切换嘛,是可以的
  4. 当多个代码块使用同一个同步监视器,那么锁住一个代码块的同时,也会锁住其他同一同步监视器的代码块,其他的线程无法直接使用方法
  5. 当多个代码块使用同一个同步监视器,那么锁住一个代码块的同时,也会锁住其他同一同步监视器的代码块,但是没有锁住其他的代码块,线程还可以去访问其他同步监视器的代码块

方法2:同步方法

实现接口和继承Thread类的两种使用同步方法的形式

public class BuyTicketThread implements Runnable {
    int ticketNum = 10;
    @Override
    public void run() {
        for (int i = 1; i <100; i++) {
            buyticket();//锁住的是this
        }
    }
    public  synchronized void buyticket(){
        if (ticketNum>0){
            System.out.println("我在"+Thread.currentThread().getName()+"窗口抢到了第"+ticketNum--+"张票");
        }
    }
}
class BuyTicketThread1 extends Thread{
    static  int ticketNum = 10;
    @Override
    public void run() {
        for (int i = 1; i <100; i++) {
            buyticket();//锁住的是当前的字节码文件BuyTicketThread1.class
        }
    }
    public static synchronized void buyticket(){
        if (ticketNum>0){
            System.out.println("我在"+Thread.currentThread().getName()+"窗口抢到了第"+ticketNum--+"张票");
        }
    }
}

小结:

多线程在争抢资源的时候,就需要实现线程同步,需要加锁,锁必须是同步的,一般使用引用类型 数据

目的:解决线程安全问题

关于同步方法:

  1. 不要将run定义为同步方法
  2. 非静态同步方法的同步监视器的this
  3. 静态同步方法的同步监视器是类名。class的字节码信息对象
  4. 同步代码块效率高于同步方法
  5. 因为,方法是将线程挡在了方法的外部,而代码块是将线程挡在了代码的外部,方法的内部
  6. 同步方法的锁住的是this,一旦方法锁住了一个方法,就锁住了这个类里所有的同步方法,代码块只是锁住了同步监视器的代码块,而没有锁住其他监视器的代码块

lock锁

和传统的syn相比,lock提供了多种锁方案,jdk1.5后新增的线程同步方式,

syn是java中的关键字,这个关键自的识别是靠jvm来识别完成的,是虚拟机级别,但是lock是API级别,提供相应的接口和对应的实现类,这个方式更加的灵活,表现出的性能也比之前更好

示例代码

public class BuyTicketThread implements Runnable {
    int ticketNum = 10;
//    拿来一把锁
    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        for (int i = 1; i <100; i++) {
            lock.lock();
            try {
                if (ticketNum>0){
                    System.out.println("我在"+Thread.currentThread().getName()+"窗口抢到了第"+ticketNum--+"张票");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }
    public  synchronized void buyticket(){
    }
}

lock和syn的区别:

  1. lock是显示锁(手动开启关闭,)syn是隐式的锁
  2. lock只有代码块锁,syn有代码块锁,方法锁
  3. 使用lock锁,jvm将花费较少的时间,来调度线程,性能更好,并且具有更好的扩展性,提供了更多的子类

优先使用顺序:

lock---》同步代码块 ---》同步方法

线程同步的有缺点

对比:

线程安全:效率低

线程不安全:效率高

形象举例:

线程安全 = 一个厕所,一个人一个人的上。效率低

线程不安全=一起上厕所,效率高

可能造成死锁:

不同线程分别占用了对方需要的同步资源,不放弃,都在等待对方放弃自己的资源,出现死锁,不会出现异常,不会出现提示,只有所有的线程都处于阻塞状态,无法继续

解决方法:少使用同步代码块嵌套

线程通信

应用场景:生产者,消费者

案例:仓库稚嫩而过放一件商品,生产者将生产出来的商品加入到仓库,消费者购买,

如果仓库没有产品,消费者等等待,生产者就绪

如果仓库中有放有产品,生产者等待,消费者就绪

1.商品类:品牌,名字

package Thread.com.hyc.Thread.demo10;
public class Product {
//    品牌
    private  String brand;
//    名字
    private String name;
    public String getBrand() {
        return brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

2.线程1:生产者

package Thread.com.hyc.Thread.demo10;
public class ProduerThread extends  Thread{//生产者线程
    //共享商品
    private  Product product;
    public ProduerThread( Product product){
        this.product = product;
    }
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            synchronized (product){
                if (i%2==0){
//                生产巧克力
                    product.setBrand("费列罗");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    product.setName("巧克力");
                }else {
//                啤酒
                    product.setBrand("哈尔冰");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    product.setName("啤酒");
                }
                System.out.println("生产者生产了"+product.getBrand()+"-----"+product.getName());
            }
        }
    }
}

3.线程2:消费者

package Thread.com.hyc.Thread.demo10;
public class CustomerThread extends Thread {
    private  Product product;
    @Override
    public void run() {
        for (int i = 1; i <=10 ; i++) {
            synchronized (product){
                System.out.println("消费者消费了"+product.getBrand()+" : "+product.getName());
            }
        }
    }
    public CustomerThread(Product product){
        this.product = product;
    }
}

功能分解1:问题:

1.生产者和消费者输出错乱

解决:提取公共的代码,为同步方法

public synchronized void setProduct(String brand,String name){
        this.setBrand(brand);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setName(name);
        System.out.println("生产者生产了"+this.getBrand()+"-----"+this.getName());
    }
    public  synchronized  void getProduct(){
        System.out.println("消费者消费了"+this.getBrand()+" : "+this.getName());
    }

功能分解2,问题:

1没有交替输出

我们用一个变量来控制同信

package Thread.com.hyc.Thread.demo11;
import javax.naming.Name;
public class Product {
//    品牌
    private  String brand;
//    名字
    private String name;
    private boolean flag =false;//默认没有商品,生产者先生产
    public String getBrand() {
        return brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public synchronized void setProduct(String brand,String name){
        if (flag == true){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.setBrand(brand);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setName(name);
        System.out.println("生产者生产了"+this.getBrand()+"-----"+this.getName());
        flag = true;
        notify();
    }
    public  synchronized  void getProduct(){
        if (flag == false){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("消费者消费了"+this.getBrand()+" : "+this.getName());
        flag = false;
        notify();
    }
}

我们用wait和notify两个方法,来完成交替,

必须放在同步代码块使用。

sleep和wait的区别:sleep进入阻塞状态,没有释放锁,wait进入阻塞状态但是同时释放了锁

概念图:

目录
相关文章
|
开发框架 分布式计算 Java
【面试题精讲】JavaSe和JavaEE的区别
【面试题精讲】JavaSe和JavaEE的区别
|
7月前
|
Java Linux 编译器
JavaSE基础1
JavaSE基础
73 4
|
7月前
|
Java
javaSE思维导图
这篇文章是关于Java SE基础的总结,包含了思维导图,以帮助读者更好地理解和记忆Java SE的知识点。
javaSE思维导图
|
7月前
|
存储 Java
JavaSE基础2
JavaSE基础
53 1
|
10月前
|
Java 编译器 数据安全/隐私保护
一文让你深入了解JavaSE的知识点(上)
一文让你深入了解JavaSE的知识点
|
10月前
|
安全 Java 程序员
一文让你深入了解JavaSE的知识点(下)
一文让你深入了解JavaSE的知识点(下)
|
10月前
|
存储 人工智能 算法
JavaSE 进阶-javase进阶(一)
JavaSE 进阶-javase进阶
75 0
|
10月前
|
安全 Java API
JavaSE 进阶-javase进阶(二)
JavaSE 进阶-javase进阶
57 0
|
10月前
|
SQL 缓存 安全
【JavaEE进阶】 #{}和${}
【JavaEE进阶】 #{}和${}
|
10月前
|
缓存 NoSQL Java
JavaSE面试题(一)
JavaSE面试题(一)
JavaSE面试题(一)