D

46 阅读40分钟

第一章.String

1.String介绍

 1.概述:String是一个类,代表的是字符串类型
 2.特点:
   a.Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现
     凡是带双引号的,都是String类的对象
   b.字符串是常量,它们的值在创建之后不能更改
     String s1 = "hello"
     s1+="world"
   c.因为 String 对象是不可变的,所以可以共享
     String s1 = "abc"
     String s2 = "abc"

image-20220228091716722

image-20220228092243117

2.String的实现原理

 1.String的实现原理:
   底层是一个被final修饰的数组
 2.jdk8:
   String底层是char数组
   jdk9以及以后:
   String底层是byte数组
 ​
   char类型占内存2个字节
   byte类型占内存1个字节
       
 3.我们定义的字符串,都会自动装载到String底层的数组中
   比如:
     String s = "abc"
     char[] chars = {'a','b','c'}
 private final char value[];

数组的前面修饰符为final,为最终的数据,数组一旦建立,数组的地址就被锁死(相当于是一个常量)

3.String的创建

 1.构造:
   String() -> 利用空参构造创建String对象
   String(char[] value)-> 根据char数组创建String对象
   String(String original) ->根据字符串创建String对象
   String(byte[] bytes) ->通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
                          默认平台:当前操作系统 ->  GBK
                              
 2.简化:
   String 变量名 = ""
 public class Test01 {
     public static void main(String[] args)  {
         //String() -> 利用空参构造创建String对象
         String s1 = new String();
         System.out.println(s1);
 ​
         //String(char[] value)-> 根据char数组创建String对象
         char[] chars = {'a','b','c'};
         String s2 = new String(chars);
         System.out.println(s2);
         //String(String original) ->根据字符串创建String对象
         String s3 = new String("abc");
         System.out.println(s3);
 ​
         /*
           中文对应的字节都是负数
 ​
           String(byte[] bytes) ->通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
                          默认平台:当前操作系统 ->  GBK
 ​
                          GBK:  一个中文,占2个字节
 ​
                          UTF-8: 一个中文,占3个字节
 ​
                          idea启动时,是按照UTF-8编码去运行->在运行时 idea会添加一个启动参数:-Dfile.encoding=UTF-8
          */
         byte[] bytes = {-28, -67, -96, -27, -91, -67};
         String s4 = new String(bytes);
         System.out.println(s4);//你好
 ​
 ​
         byte[] bytes1 = {97,98};
         String s5 = new String(bytes1);
         System.out.println(s5);//ab
         
     }
 }
 ​

image-20220228094249893

 扩展构造:
   String(char[] value, int offset, int count) -> 将char数组的一部分转成String对象
          value:要转的char数组
          offset:从数组的哪个索引开始转
          count:转多少个字符
              
   String(byte[] bytes, int offset, int length) -> 将byte数组的一部分转成String对象
          bytes:要转的byte数组
          offset:从数组的哪个索引开始转
          length:转多少个字节
              
   String(byte[] bytes, String charsetName) -> 按照指定的字符集将byte数组转成字符串
                               charsetName:传递的是字符编码,不区分大小写
public class Test02 {
    public static void main(String[] args) throws UnsupportedEncodingException {
       /* String(char[] value, int offset, int count) -> 将char数组的一部分转成String对象
               value:要转的char数组
               offset:从数组的哪个索引开始转
               count:转多少个字符
        */
        char[] chars = {'a','b','c','d'};
        String s1 = new String(chars,1,2);
        System.out.println(s1);
        /*String(byte[] bytes, int offset, int length) -> 将byte数组的一部分转成String对象
        bytes:要转的byte数组
        offset:从数组的哪个索引开始转
        length:转多少个字节*/

        byte[] bytes = {97,98,99,100,101};
        String s2 = new String(bytes,0,4);
        System.out.println(s2);

        /*
            String(byte[] bytes, String charsetName) -> 按照指定的字符集将byte数组转成字符串
                              charsetName:传递的是字符编码,不区分大小写
         */
        byte[] bytes1 = {-28, -67, -96, -27, -91, -67};
        String s3 = new String(bytes1,"gbk");
        System.out.println(s3);
    }
}

new String要主要导包,我们使用的是lang包下的String

image-20220228093338937

4.String 面试题

public class Test03 {
    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = "abc";
        String s3 = new String("abc");

        System.out.println(s1==s2);//true
        System.out.println(s1==s3);//false
        System.out.println(s2==s3);//false
    }
}

image-20220228104111316

问题:String s = new String("abc")-> 在这个过程中产生了几个对象?
    
答:  1个或者2

image-20220228105229037

5.字符串常见问题

public class Test04 {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "world";
        String s3 = "helloworld";
        String s4 = "hello"+"world";
        String s5 = s1+"world";
        String s6 = s1+s2;

        System.out.println(s3==s4);//true
        System.out.println(s3==s5);//false
        System.out.println(s3==s6);//false
        System.out.println(s5==s6);//false
    }
}

1.字符串拼接,如果等号右边都是字符串字面值,不会创建新对象

2.字符串拼接,如果等号右边有变量参与拼接,会创建新对象

image-20220228105849387

第二章.String的方法

1.判断方法

boolean equals(Object anObject)  -> 比较字符串内容
boolean equalsIgnoreCase(String anotherString)  -> 比较字符串内容,忽略大小写
 public class Test01_If {
     public static void main(String[] args) {
 ​
         String s1 = "abc";
         String s2 = "abc";
         //boolean equals(Object anObject)  -> 比较字符串内容
         System.out.println(s1.equals(s2));
         //boolean equalsIgnoreCase(String anotherString)  -> 比较字符串内容,忽略大小写
         String s3 = "ABC";
         System.out.println(s1.equalsIgnoreCase(s3));
     }
 }
 ​

2.练习1

 已知用户名和密码,请用程序实现模拟用户登录。总共给三次机会,登录之后,给出相应的提示
 ​
 步骤:
 1.定义两个字符串,分别表示username,password(之前注册过的用户名和密码)
 2.创建Scanner对象,录入用户名和密码
 3.判断,如果用户名和密码都和已存在的一样,登录成功
   否则,登录失败
 public class Test02 {
     public static void main(String[] args) {
         //1.定义两个字符串,分别表示username,password(之前注册过的用户名和密码)
         String username = "root";
         String password = "1234";
         //2.创建Scanner对象,录入用户名和密码
         Scanner sc = new Scanner(System.in);
 ​
         for (int i = 0; i < 3; i++) {
             System.out.println("请您输入用户名:");
             String name = sc.next();
             System.out.println("请您输入密码:");
             String pwd = sc.next();
        /* 3.判断,如果用户名和密码都和已存在的一样,登录成功
         否则,登录失败*/
 ​
             if (username.equals(name) && password.equals(pwd)){
                 System.out.println("登录成功");
                 break;
             }else{
                 if (i==2){
                     System.out.println("账户冻结");
                 }else{
                     System.out.println("登录失败");
                 }
             }
         }
 ​
     }
 }

3.获取功能

public int length():返回 此字符串的长度
public String concat(String str):将指定的字符串拼接到老串的末尾,产生一个新串,老串不动
char charAt(int index)  :返回指定索引处的charint indexOf(String str)  :获取的是指定字符串在老串中第一次出现的索引位置
String substring(int beginIndex):返回一个子字符串,从beginIndex开始截取字符串到字符串末尾,老串不动
String substring(int beginIndex, int endIndex) :返回一个子字符串,从beginIndex到endIndex截取字符串,  含beginIndex,不含endIndex 
public class Test03_Get {
    public static void main(String[] args) {
        String s = "abcdefg";
        //public int length():返回 此字符串的长度
        System.out.println("s.length() = " + s.length());
        //public String concat(String str):将指定的字符串拼接到老串的末尾,产生一个新串,老串不动
        String newStr1 = s.concat("哈哈哈哈哈");
        System.out.println("newStr1 = " + newStr1);

        //char charAt(int index)  :返回指定索引处的char值
        char element1 = s.charAt(2);
        System.out.println("element1 = " + element1);
        //int indexOf(String str)  :获取的是指定字符串在老串中第一次出现的索引位置
        int index = s.indexOf("a");
        System.out.println("index = " + index);
        //String substring(int beginIndex):返回一个子字符串,从beginIndex开始截取字符串到字符串末尾,老串不动
        String newStr2 = s.substring(2);
        System.out.println("newStr2 = " + newStr2);
       /* String substring(int beginIndex, int endIndex) :
        返回一个子字符串,从beginIndex到endIndex截取字符串,  含beginIndex,不含endIndex*/

        String newStr3 = s.substring(2, 4);
        System.out.println("newStr3 = " + newStr3);
        
    }
}

4.练习2

遍历字符串
public class Test04 {
    public static void main(String[] args) {
        String s = "abcdefg";
        for (int i = 0; i < s.length(); i++) {
            System.out.println(s.charAt(i));
        }
    }
}

5.转换功能

char[] toCharArray()  ->  将字符串转成char数组
byte[] getBytes()  -> 使用默认字符集,将字符串转成字节数组
String replace(CharSequence target, CharSequence replacement)  ->将与target匹配的字符串使用replacement字符串替换

扩展:
 byte[] getBytes(String charsetName)->按照指定的编码表将字符串转成byte数组
                        charsetName:指定的编码集->不区分大小写
public class Test05_ZhuanHuan {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String s1 = "abcdefg";
        //char[] toCharArray()  ->  将字符串转成char数组
        char[] chars = s1.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            System.out.println(chars[i]);
        }
        //byte[] getBytes()  -> 使用默认字符集,将字符串转成字节数组
        byte[] bytes = s1.getBytes();
        for (int i = 0; i < bytes.length; i++) {
            System.out.println(bytes[i]);
        }
        /*
          String replace(CharSequence target, CharSequence replacement)
           ->将与target匹配的字符串使用replacement字符串替换
         */
        String newStr = s1.replace("a", "z");
        System.out.println("newStr = " + newStr);
        System.out.println("==========================");


        /*
           byte[] getBytes(String charsetName)->按照指定的编码表将字符串转成byte数组
                        charsetName:指定的编码集->不区分大小写
                        
                        在GBK中:一个中文占2个字节
                        在UTF-8中:一个中文占3个字节
         */
        String s2 = "";
        byte[] bytes1 = s2.getBytes("gbk");
        System.out.println(Arrays.toString(bytes1));
    }
}

7.练习4

键盘录入一个字符串,统计该字符串中大写字母字符,小写字母字符,数字字符出现的次数(不考虑其他字符)

步骤:
  1.创建Scanner对象,键盘录入一个字符串
  2.定义三个变量,分别用来统计大写字母,小写字母,数字的个数
    int big = 0;
    int small = 0;
    int number = 0;
  3.遍历字符串,将每一个字符获取出来
  4.判断如果在A-Z范围内,证明是大写字母,big++
    A-Z-> 65-90
    
    假如:  B -> 66   判断  66是否在65-90之间,如果在证明就是大写字母
        
  5.判断如果在a-z范围内,证明是小写字母,small++
    a-z -> 97-122
    假如: b -> 98   判断98是否在97-122之间,如果在,证明是小写字母
        
  6.判断如果在0-9范围内,证明是数字,number++
    0-9  -> 48-57
    假如: 1->49  判断49是否在48-57之间,如果在,证明是数字
public class Test06 {
    public static void main(String[] args) {
        //1.创建Scanner对象,键盘录入一个字符串
        Scanner sc = new Scanner(System.in);
        String data = sc.next();
        /*2.定义三个变量,分别用来统计大写字母,小写字母,数字的个数
        int big = 0;
        int small = 0;
        int number = 0;*/
        int big = 0;
        int small = 0;
        int number = 0;

        //3.遍历字符串,将每一个字符获取出来
        char[] chars = data.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            char element = chars[i];
       /* 4.判断如果在A-Z范围内,证明是大写字母,big++
        A-Z-> 65-90

        假如:  B -> 66   判断  66是否在65-90之间,如果在证明就是大写字母*/
            if (element >= 'A' && element <= 'Z') {
                big++;
            }

        /*5.判断如果在a-z范围内,证明是小写字母,small++
            a-z -> 97-122
        假如: b -> 98   判断98是否在97-122之间,如果在,证明是小写字母*/

            if (element >= 'a' && element <= 'z') {
                small++;
            }
        /*6.判断如果在0-9范围内,证明是数字,number++
        0-9  -> 48-57
        假如: 1->49  判断49是否在48-57之间,如果在,证明是数字*/
            if (element>='0' && element<='9'){
                number++;
            }
        }

        System.out.println("大写字母:"+big+"个");
        System.out.println("小写字母:"+small+"个");
        System.out.println("数字:"+number+"个");
    }
}

8.分割功能

String[] split(String regex) -> 按照指定字符对字符串进行切割
public class Test07_Split {
    public static void main(String[] args) {
        String s = "abc,java,bigdata";
        String[] split = s.split(",");
        System.out.println(Arrays.toString(split));

        System.out.println("=======================");

        String s1 = "java.txt";
        String[] split1 = s1.split("\.");
        System.out.println(Arrays.toString(split1));
    }
}

第三章.扩展方法

boolean contains(CharSequence s)  -> 判断老串中是否包含指定的字符串
boolean endsWith(String suffix)  -> 判断老串是否以指定字符串结尾
boolean startsWith(String prefix)  -> 判断老串是否以指定字符串开头
String toLowerCase()  -> 将字符串转型小写
String toUpperCase()  -> 将字符串转成大写
String trim()  -> 去掉字符串两端空格
public class Test08_Method {
    public static void main(String[] args) {
        String s = "abcdefg";
        //boolean contains(CharSequence s)  -> 判断老串中是否包含指定的字符串
        System.out.println(s.contains("abc"));
        //boolean endsWith(String suffix)  -> 判断老串是否以指定字符串结尾
        System.out.println(s.endsWith("f"));
        //boolean startsWith(String prefix)  -> 判断老串是否以指定字符串开头
        System.out.println(s.startsWith("ab"));
        //String toLowerCase()  -> 将字符串转型小写
        String s1 = "ABCDEFG";
        System.out.println(s1.toLowerCase());
        //String toUpperCase()  -> 将字符串转成大写
        System.out.println(s.toUpperCase());
        System.out.println("============================");
        //String trim()  -> 去掉字符串两端空格
        String s2 = " abc  haha hehe ";
        System.out.println(s2);
        String trim = s2.trim();
        System.out.println(trim);

        System.out.println("============================");
        String newStr = s2.replace(" ", "");
        System.out.println(newStr);
    }
}

第四章.StringBuilder

1.StringBuilder的介绍

1.概述:java自带的类,一个可变的字符序列
2.作用:主要用于字符串拼接
3.问题:String可以直接用+作字符串拼接,那么为什么还要用StringBuilder去拼接呢?
  原因:
     a.String拼接,每拼接一次,会产生一个新的串儿,如果要是拼接次数过多,会很占用内存资源,效率会变低
     b.StringBuilder拼接,创建出来,会有一个缓冲区(char数组,没有被final修饰),字符串拼接之后,会直接保存到这个缓冲区中
       此缓冲区始终就这一个,不会过多的占用内存资源
4.StringBuilder的特点:
  a.可变的字符序列->没有被final修饰的char数组
  b.创建之后,会产生一个缓冲区(char数组),始终就这一个缓冲区,除非我们重新new了
  c.缓冲区默认长度为16,拼接之后,直接在缓冲区中存储
  d.如果拼接字符串,超出了默认为16的容量范围,会自动扩容
  e.怎么扩容?
    Arrays.copyOf-> 数组扩容(先扩容,再将源数组数据复制到新数组中)
  f.扩容多少倍呢?  2倍+2

image-20220228153030902

image-20220228153946174

2.StringBuilder的使用

1.构造方法:
  StringBuilder()
  StringBuilder(String str)
public class Test01 {
    public static void main(String[] args) {
        StringBuilder sb1 = new StringBuilder();
        System.out.println(sb1);
        StringBuilder sb2 = new StringBuilder("abc");
        System.out.println(sb2);
    }
}
常用方法:
  StringBuilder append(任意类型的数据)->拼接字符串,返回的是StrinBuilder对象自己
  StringBuilder reverse()->字符串翻转
  String toString()-> 将StringBuilder转成String
public class Test02 {
    public static void main(String[] args) {
        method01_Append();
        method02_Reverse();
        method03_Reverse();

    }

    /*
      翻转练习
      判断字符串是否是回文内容
       上海自来水来自海上
     */
    private static void method03_Reverse() {
        String s = "蝶恋花香花恋蝶1";
        StringBuilder sb = new StringBuilder(s);
        sb.reverse();
        //将sb转成String
        String s1 = sb.toString();

        if (s.equals(s1)){
            System.out.println("回文");
        }else{
            System.out.println("不是回文");
        }
    }

    public static void method02_Reverse() {
        StringBuilder sb = new StringBuilder("abcdefg");
        sb.reverse();
        System.out.println(sb);
    }

    public static void method01_Append() {
        StringBuilder sb1 = new StringBuilder();
        /*
          newSb和sb1其实是同一个对象
         */
        //StringBuilder newSb = sb1.append("张无忌");

        //链式调用
        sb1.append("张无忌").append("赵敏").append("涛哥").append("柳岩");

        System.out.println(sb1);
    }
}

3.练习

int[] arr = {1,2,3,4} 输出结果为:[1,2,3,4]
public class Test03 {
    public static void main(String[] args) {
        //int[] arr = {1,2,3,4} 输出结果为:[1,2,3,4]

        //1.定义数组
        int[] arr = {1,2,3,4};

        //2.创建StringBuilder对象
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        //3.遍历数组
        for (int i = 0; i < arr.length; i++) {
            if (i==arr.length-1){
                sb.append(arr[i]).append("]");
            }else{
                sb.append(arr[i]).append(",");
            }
        }

        //4.将StringBuilder变成String类型
        String s = sb.toString();
        System.out.println(s);
    }
}

4.String 和StringBuilder以及StringBuffer区别

1.相同点:
  三个都可以用于字符串拼接
2.不同点:
  a.String,每拼接一次,都会产生一个新串儿,占用内存空间
  b.StringBuilder和StringBuffer始终只有一个缓冲区,不过多占用内存空间   
  c.StringBuilder 速度快,线程不安全
    StringBuffer  速度慢,线程安全
从拼接字符串效率上来说:
 StringBuilder>StringBuffer>String
public class Test04 {
public static void main(String[] args) {
  long start = System.currentTimeMillis();
  String s = "";
  for (int i = 0; i < 100; i++) {
      s+="haha";
  }

  /*StringBuilder sb = new StringBuilder();
  for (int i = 0; i < 100000; i++) {
      sb.append("haha");
  }*/

  long end = System.currentTimeMillis();

  System.out.println(end-start);
}
}

第五章.Math类

1.Math类介绍

1.概述:java自带的类,数学工具类
2.作用:做数学运算的
3.特点:
  a.Math的构造是私有的->不能new对象
  b.方法都是静态的
4.使用:
  类名直接调用

2.Math类方法

static int abs(int a) -> 求参数的绝对值
static double ceil(double a) -> 向上取整
static double floor(double a) ->向下取整
static long round(double a)  -> 四舍五入
static int max(int a, int b) ->求两个数之间的较大值 
static int min(int a, int b) ->求两个数之间的较小值
public class Test01 {
    public static void main(String[] args) {
        //static int abs(int a) -> 求参数的绝对值
        System.out.println(Math.abs(-1));
        //static double ceil(double a) -> 向上取整
        System.out.println(Math.ceil(2.3));
        System.out.println(Math.ceil(-2.3));
        //static double floor(double a) ->向下取整
        System.out.println(Math.floor(2.5));
        System.out.println(Math.floor(-2.9));
        //static long round(double a)  -> 四舍五入
        System.out.println(Math.round(1.5));
        System.out.println(Math.round(-1.8));
        //static int max(int a, int b) ->求两个数之间的较大值
        System.out.println(Math.max(10,20));
        //static int min(int a, int b) ->求两个数之间的较小值
        System.out.println(Math.min(10,20));
    }
}

round()方法的实现原理:先给参数+0.5,取小于或者等于这个数的最大整数

第六章.BigInteger

1.BigInteger介绍

1.为什么要学习BigInteger?
  将来我们操作的整数会很大,有可能比long型接收的数还要大,超过long的整数,可以认为是一个对象
2.作用:
  处理超大整数

2.BigInteger使用

1.构造:
  BigInteger(String val) 
2.方法:
  BigInteger add(BigInteger val)-> 加   (this + val)
  BigInteger subtract(BigInteger val)->减  (this - val)
  BigInteger multiply(BigInteger val)  ->乘 (this * val)
  BigInteger divide(BigInteger val) ->除 (this / val)
public class Test01 {
    public static void main(String[] args) {
        /*1.构造:
        BigInteger(String val)*/
        BigInteger b1 = new BigInteger("1212121212121212121212121212");
        BigInteger b2 = new BigInteger("1212121212121212121212121212");

        //BigInteger add(BigInteger val)-> 加   (this + val)
        BigInteger add = b1.add(b2);
        System.out.println("add = " + add);
        //BigInteger subtract(BigInteger val)->减  (this - val)
        BigInteger subtract = b1.subtract(b2);
        System.out.println("subtract = " + subtract);
        //BigInteger multiply(BigInteger val)  ->乘 (this * val)
        BigInteger multiply = b1.multiply(b2);
        System.out.println("multiply = " + multiply);
        //BigInteger divide(BigInteger val) ->除 (this / val)
        BigInteger divide = b1.divide(b2);
        System.out.println("divide = " + divide);
    }
}

第七章.BigDecimal类

1.BigDecimal介绍

1.为甚要学BigDecimal?
  之间用doublefloat两个类型的数据直接参与运算时,会出现精度损失的问题
  所以用BigDecimal做小数的运算,不会出现精度损失问题

2.BigDecimal使用

1.构造:
  BigDecimal(String str)  ->  str必须是数字类型
2.方法:
  BigDecimal add(BigDecimal val)-> 加   
  BigDecimal subtract(BigDecimal val)->减  
  BigDecimal multiply(BigDecimal val)  ->乘 
  BigDecimal divide(BigDecimal val) ->->除不尽,会报错
      
3.注意:
  要想小数是精确的,创建BigDecimal时,参数要用字符串形式
public class Test01 {
    public static void main(String[] args) {
        //创建两个BigDecimal对象
        BigDecimal b1 = new BigDecimal("3.55");
        BigDecimal b2 = new BigDecimal("2.12");

        //BigDecimal add(BigDecimal val)-> 加
        BigDecimal add = b1.add(b2);
        System.out.println("add = " + add);
        //BigDecimal subtract(BigDecimal val)->减
        BigDecimal subtract = b1.subtract(b2);
        System.out.println("subtract = " + subtract);
        //BigDecimal multiply(BigDecimal val)  ->乘
        BigDecimal multiply = b1.multiply(b2);
        System.out.println("multiply = " + multiply);
        /*
           BigDecimal divide(BigDecimal val) ->除
           BigDecimal如果除不尽,会报错
         */
        BigDecimal divide = b1.divide(b2);
        System.out.println("divide = " + divide);
    }
}
BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) 
                  divisor:要除的那个数
                  scale:保留几位小数
                  roundingMode:舍入方式
                               static int ROUND_UP :向上加1
                               static int ROUND_DOWN :直接舍去
                               static int ROUND_HALF_UP:四舍五入
public class Test02 {
    public static void main(String[] args) {
        //创建两个BigDecimal对象
        BigDecimal b1 = new BigDecimal("3.55");
        BigDecimal b2 = new BigDecimal("2.12");
        /*
          BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
                  divisor:要除的那个数
                  scale:保留几位小数
                  roundingMode:舍入方式
                               static int ROUND_UP :向上加1
                               static int ROUND_DOWN :直接舍去
                               static int ROUND_HALF_UP:四舍五入
         */

        BigDecimal divide = b1.divide(b2, 2, BigDecimal.ROUND_UP);
        System.out.println(divide);

    }
}

第八章.Date日期类

1.Date类的介绍

1.概述:表示特定的瞬间,精确到毫秒
2.毫秒:时间单位
  1000毫秒 = 13.基本地理知识:
  a.时间原点:197011000秒
  b.北京时区:东八区->一个时区相差一个小时
    东八区要比时间原点所在时区,晚8个小时
  c.北京经纬度:
    东经116 北纬40
  d.季风气候:温带大陆性季风气候-> 四季分明
  e.0度经线->本初子午线
  f.赤道->分南北半球

2.Date类的使用

1.构造:
  Date()->创建Date对象,获取当前系统时间
  Date(long time)->设置时间,从时间原点开始算后面推指定的时间,传递的是毫秒值
public class Test01 {
    public static void main(String[] args) {
        //Date()->创建Date对象,获取当前系统时间
        /*Date date = new Date();
        System.out.println(date);*/
        //Date(long time)->设置时间,从时间原点开始算后面推指定的时间

        Date date = new Date(1000L);
        System.out.println(date);
    }
}

3.Date类的常用方法

long getTime()  -> 获取时间的毫秒值
void setTime(long time)  -> 设置时间,从时间原点开始算,传递毫秒值
package com.atguigu.d_date;

import java.util.Date;

public class Test02 {
    public static void main(String[] args) {
        //method01();
        method02();
    }

    private static void method02() {
        Date date = new Date(1000L);
        long time = date.getTime();

        System.out.println("time = " + time);
    }

    public static void method01() {
        Date date = new Date();
        System.out.println(date);

        //long getTime()  -> 获取时间的毫秒值,返回的是对应的毫秒值
        long time = date.getTime();
        System.out.println(time);
        //void setTime(long time)  -> 设置时间,从时间原点开始算,传递毫秒值
        date.setTime(1646104558853L);

        System.out.println(date);
    }
}

第九章.Calendar日历类

1.Calendar介绍

1.概述:日历类,如果涉及到操作具体的时间字段了,Calendar中的方法代替Date使用,是一个抽象类
2.获取:本类中的方法
  static Calendar getInstance()
3.
 国外月份:
  0  1  2  3  4  5  6  7  8  9  10  11
 国内月份:
  1  2  3  4  5  6  7  8  9  10  11  12

4.常用方法:
  int get(int field) ->返回给定日历字段的值
  void set(int field, int value)  :将给定的日历字段设置为指定的值
  void add(int field, int amount) :根据日历的规则,为给定的日历字段添加或者减去指定的时间量
  Date getTime():将Calendar转成Date对象
日历字段:
  在Calendar中日历字段都是静态的
  所以我们调用方法传递日历字段时:类名调用,指定要操作的字段

1620277709044

public class Test01 {
    public static void main(String[] args) {
        //get();
        //set();
        //add();
        getTime();
    }

    /*
      Date getTime():将Calendar转成Date对象
     */
    private static void getTime() {
        Calendar calendar = Calendar.getInstance();
        Date date = calendar.getTime();
        System.out.println(date);
    }

    /*
       void add(int field, int amount) :
                根据日历的规则,为给定的日历字段添加或者减去指定的时间量
     */
    private static void add() {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.YEAR,-1);
        int year = calendar.get(Calendar.YEAR);
        System.out.println("year = " + year);
    }


    /*
      void set(int field, int value)  :将给定的日历字段设置为指定的值
     */
    private static void set() {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR,2000);
        int year = calendar.get(Calendar.YEAR);
        System.out.println("year = " + year);
    }

    /*
      int get(int field) ->返回给定日历字段的值
     */
    public static void get() {
        Calendar calendar = Calendar.getInstance();
        int year = calendar.get(Calendar.YEAR);
        System.out.println(year);

    }
}
快速将一段代码抽到一个方法中:
1.选中要抽取的代码
2.按:ctrl+alt+m

扩展方法:

void set(int year, int month, int date)  -> 设置年月日

设置的是:2022,2,1
需求:求出2月份的最后一天(2月份一共有多少天)


public class Test02 {
public static void main(String[] args) {
  //获取Calendar对象
  Calendar calendar = Calendar.getInstance();
  /*
    设置年月日:2022,2,1 其实对应的就是我们国内的3月1日

    让3月1日减去1天,就是2月的最后一天
   */

  calendar.set(2000,2,1);

  //减去一天
  calendar.add(Calendar.DATE,-1);

  //输出Date字段
  int day = calendar.get(Calendar.DATE);

  System.out.println(day);
}
}

第十章.SimpleDateFormat日期格式化类

1.SimpleDateFormat介绍

1.概述:日期格式化类 SimpleDateFormat extends DateFormat
2.作用:
  a.将Date对象按照我们指定的日期格式,格式化成String日期表示形式
  b.将符合指定格式的日期字符串转成Date对象
3.如何去指定日期格式:
  yyyy-MM-dd HH:mm:ss
  分隔符可以改变,但是里面的字母不能随意写
      
4.构造:
  SimpleDateFormat(String pattern) 
                   pattern->代表的是指定的日期格式
                   yyyy-MM-dd HH:mm:ss
                   分隔符可以改变,但是里面的字母不能随意写
时间字母表示说明
y
M
d
H
m
s

2.SimpleDateFormat常用方法

String format(Date date)->格式化->将Date对象按照指定的格式格式化成String  
Date parse(String source)->解析->将符合日期格式的字符串转成Date对象      
public class Test01 {
    public static void main(String[] args) throws ParseException {
        format();
        parse();
    }

    /*
     Date parse(String source)->解析->将符合日期格式的字符串转成Date对象
     */
    public static void parse() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time = "2000-01-01 00:00:00";
        Date date = sdf.parse(time);
        System.out.println(date);
    }

    public static void format() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
        //String format(Date date)->格式化->将Date对象按照指定的格式格式化成String
        String format = sdf.format(new Date());
        System.out.println("format = " + format);
    }
}

第十一章.JDK8新日期类

1. LocalDate 本地日期

1.1.获取LocalDate对象

1.概述:
  是一个不可变的日期时间对象,表示日期,通常被视为年月日
  这个类是不可变的和线程安全的
2.创建:
  static LocalDate now() -> 获取当前系统时间的年-月-日
  static LocalDate of(int year, int month, int dayOfMonth) -> 设置年-月-日     
public class Test01_LocalDate {
    public static void main(String[] args) {
        //static LocalDate now() -> 获取当前系统时间的年-月-日
        LocalDate localDate = LocalDate.now();
        System.out.println(localDate);
        //static LocalDate of(int year, int month, int dayOfMonth) -> 设置年-月-日

        LocalDate localDate1 = LocalDate.of(2000, 01, 01);
        System.out.println(localDate1);
    }
}
LocalDateTime()->表示年月日,时分秒
创建方式和LocalDate一样

1.2.获取日期字段的方法 : 名字是get开头

int getYear()->获取年份
int getMonthValue()->获取月份
int getDayOfMonth()->获取月中的第几天
public class Test03_LocalDate {
    public static void main(String[] args) {
        LocalDate localDate = LocalDate.now();
        //int getYear()->获取年份
        System.out.println(localDate.getYear());
        //int getMonthValue()->获取月份
        System.out.println(localDate.getMonthValue());
        //int getDayOfMonth()->获取月中的第几天
        System.out.println(localDate.getDayOfMonth());
    }
}

1.3.设置日期字段的方法 : 名字是with开头

LocalDate withYear(int year):设置年份
LocalDate withMonth(int month):设置月份
LocalDate withDayOfMonth(int day):设置月中的天数
public class Test04_LocalDate {
    public static void main(String[] args) {
        LocalDate localDate = LocalDate.now();
        //LocalDate withYear(int year):设置年份
       /* LocalDate localDate1 = localDate.withYear(2000);
        System.out.println(localDate1);
        //LocalDate withMonth(int month):设置月份
        LocalDate localDate2 = localDate1.withMonth(4);
        System.out.println(localDate2);
        //LocalDate withDayOfMonth(int day):设置月中的天数
        LocalDate localDate3 = localDate2.withDayOfMonth(12);
        System.out.println(localDate3);*/

        LocalDate localDate1 = localDate.withYear(2000).withMonth(2).withDayOfMonth(14);
        System.out.println(localDate1);
    }
}

1.4.日期字段偏移

设置日期字段的偏移量,方法名plus开头,向后偏移
设置日期字段的偏移量,方法名minus开头,向前偏移
public class Test05_LocalDate {
    public static void main(String[] args) {
        //plus();
        minus();
    }

    /*
      设置日期字段的偏移量,方法名minus开头,向前偏移
     */
    private static void minus() {
        LocalDate localDate = LocalDate.now();
        LocalDate localDate1 = localDate.minusYears(1);
        System.out.println(localDate1.getYear());
    }


    /*
      设置日期字段的偏移量,方法名plus开头,向后偏移
     */
    private static void plus() {
        LocalDate localDate = LocalDate.now();
        LocalDate localDate1 = localDate.plusYears(1);
        System.out.println(localDate1.getYear());
    }
}

2.Period和Duration类

2.1 Period 计算日期之间的偏差

方法:
  static Period between(LocalDate d1,LocalDate d2):计算两个日期之间的差值
  
  getYears()->获取相差的年
  getMonths()->获取相差的月
  getDays()->获取相差的天
public class Test06_Period {
    public static void main(String[] args) {
        //创建两个LocalDate对象,设置年月日
        LocalDate localDate1 = LocalDate.of(2022, 12, 12);
        LocalDate localDate2 = LocalDate.of(2021, 11, 11);

        Period period = Period.between(localDate2, localDate1);

        //getYears()->获取相差的年
        System.out.println(period.getYears());
        //getMonths()->获取相差的月
        System.out.println(period.getMonths());
        //getDays()->获取相差的天
        System.out.println(period.getDays());
    }
}

2.2 Duration计算时间之间的偏差

1.static Duration between(Temporal startInclusive, Temporal endExclusive)  -> 计算时间差
2.Temporal接口:
  LocalDate,LocalDateTime
    
3.注意:利用Duration计算时间偏差,是算的比较精准的,主要是时分秒,所以我们不应该调用between传递LocalDate对象
      因为LocalDate对象不操作时分秒,需要传递LocalDateTime,因为LocalDateTime可以操作时分秒
    
5.利用Duration获取相差的时分秒  -> to开头
  toDays():获取相差天数
  toHours():获取相差小时
  toMinutes():获取相差分钟
  toMillis():获取相差秒(毫秒)
public class Test07_Duration {
    public static void main(String[] args) {
        LocalDateTime localDateTime1 = LocalDateTime.of(2022, 12, 12, 12, 12, 12);
        LocalDateTime localDateTime2 = LocalDateTime.of(2011, 11, 11, 11, 11, 11);

        Duration duration = Duration.between(localDateTime2, localDateTime1);

        //toDays():获取相差天数
        System.out.println(duration.toDays());
        //toHours():获取相差小时
        System.out.println(duration.toHours());
        //toMinutes():获取相差分钟
        System.out.println(duration.toMinutes());
        //toMillis():获取相差秒(毫秒)
        System.out.println(duration.toMillis());
    }
}

总结:

如果要是计算年月日的时间偏差,用Period

如果要是计算时分秒的时间偏差,用Duration

3.DateTimeFormatter日期格式化类

1.概述:日期格式化对象
2.获取:
  static DateTimeFormatter ofPattern(String pattern) 
      pattern:自己定义的日期格式
          
3.方法:
  a.TemporalAccessor parse(CharSequence text)->将符合格式的字符串转成TemporalAccessor日期对象
    TemporalAccessor接口:LocalDate , LocalDateTime都是此接口的实现类
    
    LocalDateTime中的方法:
    static LocalDateTime from(TemporalAccessor ta)->将TemporalAccessor转成LocalDateTime
  
  b.format方法
    String format(TemporalAccessor ta)->将日期按照格式转成String
public class Test08_DateTimeFormatter {
    public static void main(String[] args) {
       // parse();
        format();
    }

    public static void format(){
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime localDateTime = LocalDateTime.now();
        String format = dtf.format(localDateTime);
        System.out.println(format);
    }

    public static void parse() {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String time = "2020-01-01 00:00:00";
        TemporalAccessor temporalAccessor = dtf.parse(time);
        /*
          将TemporalAccessor转换成LocalDateTime对象
         */
        LocalDateTime localDateTime = LocalDateTime.from(temporalAccessor);
        System.out.println(localDateTime);
    }
}

第十二章.System类

1.概述:java自带的类
2.特点:
  a.构造私有,不能new对象
  b.方法为静态的
3.使用:
  类名直接调用
方法说明
static void gc()获取垃圾回收器
static long currentTimeMillis()获取当前系统时间毫秒值
static void exit(int status)退出正在运行的jvm
static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)src:源数组 srcPos:从源数组的哪个索引开始复制 dest:目标数组 destPos:从目标数组的哪个索引开始粘贴 length:复制多少个
public class Test01 {
    public static void main(String[] args) {
       // static long currentTimeMillis()
        long start = System.currentTimeMillis();
        System.out.println(start);

        //static void exit(int status)

        for (int i = 0; i < 10; i++) {
            if (i==5){
                System.exit(0);
            }
            System.out.println("helloworld"+i);
        }
    }
}
public class Test02 {
    public static void main(String[] args) {
        /*
         static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
                src:源数组
                srcPos:从源数组的哪个索引开始复制
                dest:目标数组
                destPos:从目标数组的哪个索引开始粘贴
                length:复制多少个
         */

        int[] arr = {1,2,3,4,5,6,7,8,9};

        int[] arr1 = new int[9];

        System.arraycopy(arr,0,arr1,0,8);

        System.out.println(Arrays.toString(arr1));
    }
}

第十三章.Arrays数组工具类

1.概述:数组工具类
2.特点:
  a.Arrays构造私有
  b.方法为静态的
3.使用:
  类名直接调用  
方法名说明
static String toString()按照指定格式打印数组
static void sort(数组)排序->升序
static int binarySearch(int[] arr,int data)二分查找
static int[] copyOf(int[] original, int newLength)数组扩容
public class Test01 {
    public static void main(String[] args) {
        //static String toString()按照指定格式打印数组
        int[] arr = {1, 2, 3, 4, 5};
        System.out.println(Arrays.toString(arr));

        //static void sort(数组)排序->升序
        int[] arr1 = {5,4,3,2,1};
        Arrays.sort(arr1);
        System.out.println(Arrays.toString(arr1));

        /*
          static int binarySearch(int[] arr,int data)
          二分查找
         */

        int[] arr2 = {11,22,33,44,55,66,77};
        System.out.println(Arrays.binarySearch(arr2,10));


        /*
          static int[] copyOf(int[] original, int newLength)->数组扩容
          original:老数组
          newLength:新数组的长度

          返回的是一个新数组
         */
        int[] arr3 = {1,2,3,4,5};
        int[] arr4 = Arrays.copyOf(arr3, 10);

        /*
         一般的数组扩容
         */
        arr3 = arr4;
        System.out.println(Arrays.toString(arr3));

    }
}

image-20220302092635369

第十四章.包装类

1.基本数据类型对应的引用数据类型(包装类)

1.概述:基本类型对应的引用类型(基本类型都有对应的一个类(包装类))
2.拆箱和装箱
  a.装箱:将基本类型转成对应的包装类
  b.拆箱:将包装类转成对应的基本类型
      
  基本类型和包装类之间是可以自动转换的(自动拆箱,自动装箱)
基本类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

##2.Integer的介绍以及使用

1.概述:int的包装类
2.装箱:
  a.构造:
    Integer(int value) 
    Integer(String s) -> s必须是数字格式
  b.方法:
    static Integer valueOf(int i) 
    static Integer valueOf(String s) -> s必须是数字格式 
public class Test01 {
    public static void main(String[] args) {
        /*
            a.构造:
             Integer(int value)
             Integer(String s) -> s必须是数字格式
         */
        Integer i1 = new Integer(1);
        System.out.println("i1 = " + i1);
        Integer i2 = new Integer("11");
        System.out.println("i2 = " + i2);

        /*
              static Integer valueOf(int i)
              static Integer valueOf(String s) -> s必须是数字格式
         */
        Integer i3 = Integer.valueOf(1);
        System.out.println("i3 = " + i3);
        Integer i4 = Integer.valueOf("11");
        System.out.println("i4 = " + i4);
    }
}
3.拆箱:
  int intValue()->将Integer转成int  
public class Test02 {
    public static void main(String[] args) {
        /*
          3.拆箱:
            int intValue()->将Integer转成int
         */
        Integer integer = new Integer(10);
        int i = integer.intValue();
        System.out.println(i+1);


        /*
          自动装箱
         */
        Integer i1 = 10;
        System.out.println(i1+1);

        /*
          自动拆箱
         */
        int i2 = i1;
        System.out.println(i2+1);
    }
}

3.基本类型和String之间的转换

3.1 基本类型往String转

1.方式1:
  +  -> 任意类型遇到字符串都会变成字符串
2.方式2:
  String中的静态方法
  static String valueOf(int i) 
 public class Test03_IntToString {
     public static void main(String[] args) {
         /*
           1.方式1:
             +  -> 任意类型遇到字符串都会变成字符串
           2.方式2:
             String中的静态方法
             static String valueOf(int i)
          */
 ​
         int i = 10;
         String s1 = i+"";
         System.out.println(s1+1111);
 ​
 ​
         String s2 = String.valueOf(10);
         System.out.println(s2+111111);
     }
 }

3.2 String转成基本数据类型

 每个包装类中都有一个parsexxx的方法,可以将字符串转成基本类型
 注意:要转的字符串必须是数字形式
位置方法说明
ByteparseByte(String s)将字符串转成byte
ShortparseShort(String s)将字符串转成short
IntegerparseInt(String s)将字符串转成int
FloatparseFloat(String s)将字符串转成float
DoubleparseDouble(Stirng s)将字符串转成double
LongparseLong(String s)将字符串转成long
BooleanparseBoolean(String s)将字符串转成boolean
 public class Test04_StringToInt {
     public static void main(String[] args) {
         int i = Integer.parseInt("111");
         System.out.println(i+1);
     }
 }
public class Test05 {
public static void main(String[] args) {
  Integer i1 = 100;
  Integer i2 = 100;
  System.out.println(i1==i2);//true


  Integer i3 = 127;
  Integer i4 = 127;
  System.out.println(i3==i4);//true

 /*
   超过-128到127范围,返回的是new Integer对象
  */
  Integer i5 = 128;
  Integer i6 = 128;
  System.out.println(i5==i6);//false
}
}

image-20220302101625199

第十五章.多线程基本了解

1.多线程_线程和进程

 进程:进入到内存中运行的应用程序

image-20220302102707594

 线程:是进程中的一个执行单元
 线程作用:负责当前进程中程序的运行,一个进程中至少有一个线程.一个进程中是可以有多个线程的,这样的应用程序就称之为多线程程序

image-20220302103829253

并发:同一个时刻多个线程同时操作了同一个数据

并行:同一个时刻多个线程同时执行不同的程序

2.CPU调度

 1.分时调度:指的是让所有的线程轮流获取CPU使用权,并且平均分配每个线程占用的cpu的时间片
 2.抢占式调度:java程序实现的多线程就是抢占式调度
            多个线程之间抢占CPU使用权,谁的优先级高,先抢到CPU使用权几率就大

3.主线程介绍

 public class Test01_Main {
     public static void main(String[] args) {
         for (int i = 0; i < 5; i++) {
             System.out.println("哈哈哈哈哈");
         }
         System.out.println(Math.abs(-10));
     }
 }
 ​

image-20220302104929563

第十六章.创建线程的方式(重点)

1.第一种方式_extends Thread

 1.实现多线程步骤:
   a.定义一个类,继承Thread
   b.重写Thread中的run方法(run方法专门用于设置线程任务的)
   c.创建自定义线程类对象
   d.调用Thread中的start方法
     start()方法-> 开启线程,jvm自动执行run方法
 public class MyThread extends Thread {
     @Override
     public void run() {
         for (int i = 0; i < 5; i++) {
             System.out.println("myThread线程执行了......");
         }
     }
 }
 public class Test01_Main {
     public static void main(String[] args) {
         //创建线程对象
         MyThread myThread = new MyThread();
         //调用Thread类中的start方法,开启线程,jvm自动执行run方法
         myThread.start();
         
         /*
           如果单纯的调用run方法
           此线程并没有开启,只是简单调用方法而已
          */
         //myThread.run();
 ​
         for (int i = 0; i < 5; i++) {
             System.out.println("Main线程执行了......");
         }
     }
 }
 ​

2.多线程在内存中的运行原理

image-20220302112120965

 注意:同一个线程对象,不能连续调用多次start方法

3.Thread类中的方法

方法说明
String getName()获取线程名字
void setName()设置线程名字
static Thread currentThread()获取的是当前正在执行的线程对象 此方法在哪个线程中使用,获取的就是哪个线程对象
static void sleep(long time)线程睡眠,设置的是睡眠时间,遇到sleep线程不走了,直到超时了,线程自动醒来,继续执行
 public class Test01_Main {
     public static void main(String[] args) throws InterruptedException {
         //创建线程对象
         MyThread myThread = new MyThread();
 ​
         //设置线程名字
         myThread.setName("赵四");
 ​
         //调用Thread类中的start方法,开启线程,jvm自动执行run方法
         myThread.start();
 ​
         /*
           如果单纯的调用run方法
           此线程并没有开启,只是简单调用方法而已
          */
         //myThread.run();
 ​
         for (int i = 0; i < 5; i++) {
             Thread.sleep(1000L);
             System.out.println(Thread.currentThread().getName()+"线程执行了......");
         }
     }
 }
 ​
 public class MyThread extends Thread {
     @Override
     public void run() {
         for (int i = 0; i < 5; i++) {
 ​
             try {
                 Thread.sleep(1000L);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
 ​
             System.out.println(Thread.currentThread().getName()+"线程执行了......");
         }
     }
 }

问题:为甚在run方法中不能throws异常

原因:Thead(父类)中的run没有throws异常,所以子类重写之后就不能throws

4.第二种方式_实现Runnable接口

 1.定义类,实现Runnable接口
 2.重写Runnable接口中的run方法,设置线程任务
 3.创建Thread对象,将自定义线程类封装到Thread对象中
 4.调用Thread中的start方法,开启线程
 public class Test01 {
     public static void main(String[] args) {
         //创建实现类对象
         MyRunnable myRunnable = new MyRunnable();
         /*
            创建Thread对象
 ​
            构造:
              Thread(Runnable target, String name)
              name:线程名字
          */
         Thread thread = new Thread(myRunnable,"super屎蛋儿");
         thread.start();
 ​
         for (int i = 0; i < 10; i++) {
             System.out.println(Thread.currentThread().getName()+"执行了");
         }
     }
 }
 public class MyRunnable implements Runnable{
     @Override
     public void run() {
         for (int i = 0; i < 10; i++) {
             System.out.println(Thread.currentThread().getName()+"执行了");
         }
     }
 }

5.两种实现多线程的方式区别

 1.继承:由于继承是单继承,所以是有局限性
 2.接口:解决继承的局限性

6.第三种方式_匿名内部类创建多线程

 1.匿名内部类回顾:
   new 接口/抽象类(){
      重写方法
   }.重写的方法();
 ​
   接口/抽象类 对象名 = new 接口/抽象类(){
      重写方法
   }
   对象名.重写的方法名();
 ​
   匿名内部类其实指向的是子类或者实现类对象
       
 2.匿名内部类实现多线程
   new Thread(new Runnable(){
      run
   }).start();
 public class Test02 {
     public static void main(String[] args) {
         new Thread(new Runnable() {
             @Override
             public void run() {
                 for (int i = 0; i < 10; i++) {
                     System.out.println(Thread.currentThread().getName()+"执行了");
                 }
             }
         }).start();
 ​
 ​
         new Thread(new Runnable() {
             @Override
             public void run() {
                 for (int i = 0; i < 10; i++) {
                     System.out.println(Thread.currentThread().getName()+"执行了");
                 }
             }
         }).start();
     }
 }

image-20220302143126409

小结:

1.继承Thread方式

a.定义一个类,继承Thread

b.重写run方法,设置线程任务

c.创建对象,调用start方法,开启线程,jvm自动执行run

2.实现Runnable接口

a.定义一个实现类,实现Runnable接口

b.重写run方法

c.创建实现类对象,放到Thread对象中,调用start方法

3.匿名内部类方式实现多线程

new Thread(new Runnable(){

重写run方法

}).start();

第十七章.线程安全

1.线程安全问题-->线程不安全的代码

 当多个线程访问同一个资源时,很容易出现线程安全问题
 public class MyTicket implements Runnable{
     //定义100张票
     int ticket = 100;
 ​
     @Override
     public void run() {
         while(true){
             if (ticket>0){
                 System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
                 ticket--;
             }
         }
     }
 }
 ​
 public class Test01 {
     public static void main(String[] args) {
         MyTicket myTicket = new MyTicket();
 ​
         Thread t1 = new Thread(myTicket, "谢广坤");
         Thread t2 = new Thread(myTicket, "赵四");
         Thread t3 = new Thread(myTicket, "刘能");
 ​
         //开启线程
         t1.start();
         t2.start();
         t3.start();
     }
 }

以上代码有问题->线程安全问题,多个线程访问了同一张票,线程不安全了

原因:cpu在多个线程之间做切换

2.解决线程安全问题的第一种方式(使用同步代码块)

 1.关键字:synchronized
 2.格式:
   synchronized(任意对象){
     可能出现线程不安全的代码
   }
 ​
 3.当一个线程进入到synchronized中,抢到了锁,并将锁锁上,其他线程需要在代码块外面等待,当一个线程出了synchronized之后,释放锁,将锁打开,其他线程才可以抢锁,进入到synchronized中执行
     
 4.注意:要想使用synchronized实现线程同步,线程安全,多个线程之间使用的锁对象必须是同一把锁(同一个锁对象)
 public class Test01 {
     public static void main(String[] args) {
         MyTicket myTicket = new MyTicket();
 ​
         Thread t1 = new Thread(myTicket, "谢广坤");
         Thread t2 = new Thread(myTicket, "赵四");
         Thread t3 = new Thread(myTicket, "刘能");
 ​
         //开启线程
         t1.start();
         t2.start();
         t3.start();
     }
 }
 public class MyTicket implements Runnable {
     //定义100张票
     int ticket = 100;
 ​
     //随便创建一个对象
     Object obj = new Object();
 ​
     @Override
     public void run() {
         while (true) {
             try {
                 Thread.sleep(100L);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             synchronized (obj) {
                 if (ticket > 0) {
                     System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
                     ticket--;
                 }
             }
 ​
         }
     }
 }
 ​

3.解决线程安全问题的第二种方式:同步方法

3.1.普通同步方法

 1.格式:
   修饰符 synchronized 返回值类型 方法名(参数){
     方法体
     return 结果
   }
 ​
 2.普通同步方法默认锁对象:this
 public class Test01 {
     public static void main(String[] args) {
         MyTicket myTicket = new MyTicket();
 ​
         Thread t1 = new Thread(myTicket, "广坤");
         Thread t2 = new Thread(myTicket, "赵四");
         Thread t3 = new Thread(myTicket, "刘能");
 ​
         //开启线程
         t1.start();
         t2.start();
         t3.start();
     }
 }
 public class MyTicket implements Runnable {
     //定义100张票
     int ticket = 100;
 ​
     @Override
     public void run() {
         while (true) {
             try {
                 Thread.sleep(100L);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
 ​
             method();
         }
     }
 ​
   /*  public synchronized void method(){
         if (ticket > 0) {
             System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
             ticket--;
         }
     }*/
 ​
     public void method() {
         synchronized (this) {
             if (ticket > 0) {
                 System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
                 ticket--;
             }
         }
     }
 }

3.2.静态同步方法

 1.格式:
   修饰符 static synchronized 返回值类型 方法名(参数){
     方法体
     return 结果
   }
 2.静态同步方法默认锁:当前类.class
 public class Test01 {
     public static void main(String[] args) {
         MyTicket myTicket = new MyTicket();
 ​
         Thread t1 = new Thread(myTicket, "广坤");
         Thread t2 = new Thread(myTicket, "赵四");
         Thread t3 = new Thread(myTicket, "刘能");
 ​
         //开启线程
         t1.start();
         t2.start();
         t3.start();
     }
 }
 ​
 public class MyTicket implements Runnable {
     //定义100张票
     static int ticket = 100;
 ​
     @Override
     public void run() {
         while (true) {
             try {
                 Thread.sleep(100L);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
 ​
             method();
         }
     }
 ​
    /* public static synchronized void method(){
         if (ticket > 0) {
             System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
             ticket--;
         }
     }*/
 ​
     public static void method() {
         synchronized (MyTicket.class) {
             if (ticket > 0) {
                 System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
                 ticket--;
             }
         }
     }
 }
 ​

第十八章.死锁(了解)

1.死锁介绍(锁嵌套就有可能产生死锁)

 指的是两个或者两个以上的线程在执行的过程中,由于竞争同步锁而产生的一种阻塞现象;如果没有外力的作用,他们将无法继续执行下去,这种情况就称之为死锁.

1628599841613

 根据上图所示:线程T1正在持有R1锁,但是T1线程必须再拿到R2锁,才能继续执行
 而线程T2正在持有R2锁,但是 T2线程需要再拿到R1锁,才能继续执行
 此时两个线程处于互相等待的状态,就是死锁,在程序中的死锁将出现在同步代码块的嵌套中

2.死锁的分析

image-20220302161907708

3.代码实现

 public class LockA {
     static LockA lockA = new LockA();
 }
 public class LockB {
     static LockB lockB = new LockB();
 }
 public class MyThread implements Runnable{
     private boolean flag;
 ​
     public MyThread(boolean flag) {
         this.flag = flag;
     }
 ​
     @Override
     public void run() {
 ​
         if (flag){
             synchronized (LockA.lockA){
                 System.out.println("if...lockA");
                 synchronized (LockB.lockB){
                     System.out.println("if...lockB");
                 }
             }
         }else{
             synchronized (LockB.lockB){
                 System.out.println("else...lockB");
                 synchronized (LockA.lockA){
                     System.out.println("else...lockA");
                 }
             }
         }
     }
 }
 ​
 public class Test01 {
     public static void main(String[] args) {
         MyThread myThread1 = new MyThread(true);
         MyThread myThread2 = new MyThread(false);
 ​
         Thread thread1 = new Thread(myThread1);
         Thread thread2 = new Thread(myThread2);
 ​
         thread1.start();
         thread2.start();
     }
 }

第十九章.线程状态

1.线程状态介绍

   当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State这个枚举中给出了六种线程状态:
   这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析。
线程状态导致状态发生条件
NEW(新建)线程刚被创建,但是并未启动。还没调用start方法。
Runnable(可运行)线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
Blocked(锁阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
Timed Waiting(计时等待)同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。或者调用过时方法stop()

2.线程状态图

image-20220304093351646

第二十章.等待唤醒

1.等待唤醒案例分析(线程之间的通信)

image-20220304101531751

2.等待唤醒案例实现

 //包子铺
 public class BaoZiPu {
     //定义count,代表包子个数
     private int count;
     //定义flag,代表是否有包子
     private boolean flag;
 ​
     public BaoZiPu() {
     }
 ​
     public BaoZiPu(int count, boolean flag) {
         this.count = count;
         this.flag = flag;
     }
 ​
 ​
     /*
       改造get方法 ,此方法给消费线程用
       作为消费包子方法
      */
     public void getCount() {
         System.out.println("消费了......第"+count+"个包子");
     }
 ​
     /*
       改造set方法,此方法给生产线程用
       作为生产包子方法
      */
     public void setCount() {
         count++;
         System.out.println("生产了..第"+count+"个包子");
     }
 ​
     public boolean isFlag() {
         return flag;
     }
 ​
     public void setFlag(boolean flag) {
         this.flag = flag;
     }
 }
 ​
 public class Product implements Runnable {
     //创建包子铺对象
     private BaoZiPu baoZiPu;
 ​
     public Product(BaoZiPu baoZiPu) {
         this.baoZiPu = baoZiPu;
     }
 ​
     @Override
     public void run() {
         while (true) {
 ​
             try {
                 Thread.sleep(100L);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             synchronized (baoZiPu) {
                 if (baoZiPu.isFlag() == true) {
                     try {
                         baoZiPu.wait();
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
 ​
                 //生产包子
                 baoZiPu.setCount();
                 //改变flag状态,为true,证明有包子了
                 baoZiPu.setFlag(true);
                 //唤醒消费线程
                 baoZiPu.notify();
             }
 ​
         }
     }
 }
 ​
 //消费者
 public class Consumer implements Runnable {
     //创建包子铺对象
     private BaoZiPu baoZiPu;
 ​
     public Consumer(BaoZiPu baoZiPu) {
         this.baoZiPu = baoZiPu;
     }
 ​
 ​
     @Override
     public void run() {
         while (true) {
             try {
                 Thread.sleep(100L);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             synchronized (baoZiPu) {
                 if (baoZiPu.isFlag() == false) {
                     try {
                         baoZiPu.wait();
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
 ​
                 //消费包子
                 baoZiPu.getCount();
                 //改变flag状态,为false,证明没有包子了
                 baoZiPu.setFlag(false);
                 //唤醒生产线程
                 baoZiPu.notify();
             }
 ​
         }
     }
 }
 ​
 public class Test01 {
     public static void main(String[] args) {
         BaoZiPu baoZiPu = new BaoZiPu();
 ​
         Product product = new Product(baoZiPu);
         Consumer consumer = new Consumer(baoZiPu);
         new Thread(product).start();
         new Thread(consumer).start();
     }
 }
 ​

image-20220304103323349

image-20220304105020240

3.用同步方法改造等待唤醒案例

 //包子铺
 public class BaoZiPu {
     //定义count,代表包子个数
     private int count;
     //定义flag,代表是否有包子
     private boolean flag;
 ​
     public BaoZiPu() {
     }
 ​
     public BaoZiPu(int count, boolean flag) {
         this.count = count;
         this.flag = flag;
     }
 ​
 ​
     /*
       改造get方法 ,此方法给消费线程用
       作为消费包子方法
      */
     public synchronized void getCount() {
 ​
         if (this.flag == false) {
             try {
                 this.wait();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
 ​
         //消费包子
         System.out.println("消费了......第" + count + "个包子");
         //改变flag状态,为false,证明没有包子了
         this.flag = false;
         //唤醒生产线程
         this.notify();
     }
 ​
     /*
       改造set方法,此方法给生产线程用
       作为生产包子方法
      */
     public synchronized void setCount() {
         if (this.flag == true) {
             try {
                 this.wait();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
 ​
         //生产包子
         count++;
         System.out.println("生产了..第" + count + "个包子");
         //改变flag状态,为true,证明有包子了
         this.flag = true;
         //唤醒消费线程
         this.notify();
 ​
     }
 ​
     public boolean isFlag() {
         return flag;
     }
 ​
     public void setFlag(boolean flag) {
         this.flag = flag;
     }
 }
 ​
 //生产者
 public class Product implements Runnable {
     //创建包子铺对象
     private BaoZiPu baoZiPu;
 ​
     public Product(BaoZiPu baoZiPu) {
         this.baoZiPu = baoZiPu;
     }
 ​
     @Override
     public void run() {
         while (true) {
 ​
             try {
                 Thread.sleep(100L);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
 ​
             baoZiPu.setCount();
 ​
         }
     }
 }
 ​
 //消费者
 public class Consumer implements Runnable {
     //创建包子铺对象
     private BaoZiPu baoZiPu;
 ​
     public Consumer(BaoZiPu baoZiPu) {
         this.baoZiPu = baoZiPu;
     }
 ​
 ​
     @Override
     public void run() {
         while (true) {
             try {
                 Thread.sleep(100L);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
 ​
             baoZiPu.getCount();
         }
     }
 }
 public class Test01 {
     public static void main(String[] args) {
         BaoZiPu baoZiPu = new BaoZiPu();
 ​
         Product product = new Product(baoZiPu);
         Consumer consumer = new Consumer(baoZiPu);
         new Thread(product).start();
         new Thread(consumer).start();
     }
 }
 ​

第二十一章.多等待多唤醒

1.解决多生产多消费问题(if改为while,将notify改为notifyAll)

 //包子铺
 public class BaoZiPu {
     //定义count,代表包子个数
     private int count;
     //定义flag,代表是否有包子
     private boolean flag;
 ​
     public BaoZiPu() {
     }
 ​
     public BaoZiPu(int count, boolean flag) {
         this.count = count;
         this.flag = flag;
     }
 ​
 ​
     /*
       改造get方法 ,此方法给消费线程用
       作为消费包子方法
      */
     public synchronized void getCount() {
 ​
         while (this.flag == false) {
             try {
                 this.wait();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
 ​
         //消费包子
         System.out.println("消费了......第" + count + "个包子");
         //改变flag状态,为false,证明没有包子了
         this.flag = false;
 ​
         this.notifyAll();
     }
 ​
     /*
       改造set方法,此方法给生产线程用
       作为生产包子方法
      */
     public synchronized void setCount() {
         while (this.flag == true) {
             try {
                 this.wait();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
 ​
         //生产包子
         count++;
         System.out.println("生产了..第" + count + "个包子");
         //改变flag状态,为true,证明有包子了
         this.flag = true;
 ​
         this.notifyAll();
 ​
     }
 ​
     public boolean isFlag() {
         return flag;
     }
 ​
     public void setFlag(boolean flag) {
         this.flag = flag;
     }
 }
 ​
 //生产者
 public class Product implements Runnable {
     //创建包子铺对象
     private BaoZiPu baoZiPu;
 ​
     public Product(BaoZiPu baoZiPu) {
         this.baoZiPu = baoZiPu;
     }
 ​
     @Override
     public void run() {
         while (true) {
 ​
             try {
                 Thread.sleep(100L);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
 ​
             baoZiPu.setCount();
 ​
         }
     }
 }
 ​
 //消费者
 public class Consumer implements Runnable {
     //创建包子铺对象
     private BaoZiPu baoZiPu;
 ​
     public Consumer(BaoZiPu baoZiPu) {
         this.baoZiPu = baoZiPu;
     }
 ​
 ​
     @Override
     public void run() {
         while (true) {
             try {
                 Thread.sleep(100L);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
 ​
             baoZiPu.getCount();
         }
     }
 }
 ​
 public class Test01 {
     public static void main(String[] args) {
         BaoZiPu baoZiPu = new BaoZiPu();
 ​
         Product product = new Product(baoZiPu);
         Consumer consumer = new Consumer(baoZiPu);
         new Thread(product).start();
         new Thread(product).start();
 ​
         new Thread(consumer).start();
         new Thread(consumer).start();
     }
 }

第二十二章.Lock锁

1.Lock对象的介绍和基本使用

 1.概述:Lock是一个接口
 2.实现类:
   ReentrantLock
 3.方法:
   void lock()  获取锁
   void unlock()  释放锁    
 public class MyTicket implements Runnable {
     //定义100张票
     int ticket = 100;
 ​
     //创建Lock锁对象
     Lock lock = new ReentrantLock();
 ​
     @Override
     public void run() {
         while (true) {
             try {
                 //获取锁
                 lock.lock();
                 if (ticket > 0) {
                     System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
                     ticket--;
                 }
                 Thread.sleep(100L);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }finally {
                 //释放锁
                 lock.unlock();
             }
             
         }
         
     }
 }
 ​
 public class Test01 {
     public static void main(String[] args) {
         MyTicket myTicket = new MyTicket();
 ​
         Thread t1 = new Thread(myTicket, "谢广坤");
         Thread t2 = new Thread(myTicket, "赵四");
         Thread t3 = new Thread(myTicket, "刘能");
 ​
         //开启线程
         t1.start();
         t2.start();
         t3.start();
     }
 }

2.Lock和synchronized区别

2.1.Lock

 1.底层实现原理: 乐观锁
 2.乐观锁执行原理:  CAS机制  Compare And Swap  (比较并交换)        

image-20220304143012312

2.2.synchronized

 属于悲观锁,当一个线程拿到锁对象之后,其他线程拿不到,执行不了,当一个线程做操作的时候,其他线程操作不了

2.3.Lock和synchronized区别(悲观锁和乐观锁的区别)

 a. Lock属于乐观锁,使用多个线程操作的是同一个变量
    synchronized属于悲观锁,使用多个线程操作一段代码
 b.
    乐观锁:线程A在操作变量时,允许线程B操作,只是会先判断,如果有问题,就放弃本次操作.判断如果没有问题,就会正常操作
    悲观锁:当线程A正在操作的时候,不允许线程B执行,要等A出来之后B才有可能进入执行
 ​
 c.相对来说,悲观锁效率比较低,乐观锁效率比较高

当多线程操作同一个数据时,会出现以下问题:

1.可见性

 i=9,变量i的初始值为9,每一个线程的操作都是减1。两个线程AB同时访问变量,B先执行i-1,在将结果i=8同步到内存中,A线程也执行i-1,这时i=9的状态就被执行两次,出现线程安全问题。
 ​
 线程安全问题产生的原因:一个线程对共享数据的修改不能立即为其他线程所见。
 ​
 解决:给共享的变量加上关键字:volatile

2.有序性

 多行代码的编写顺序和编译顺序。
 有些时候,编译器在编译代码时,为了提高效率,会对代码“重排”:
 ​
 .java文件
 int a = 0;      //第一行
 int b = 20;     //第二行
 int c = a / b;  //第三行
 ​
 在执行第三行之前,由于第一行和第二行的先后顺序无所谓,所以编译器可能会对“第一行”和“第二行”进行代码重排:
 .class
 int b = 20;
 int a = 0;
 int c = a / b;
 ​
 但在多线程环境下,这种重排可能是我们不希望发生的,因为:重排,可能会影响另一个线程的结果,所以我们不需要代码进行重排
 ​
 解决:给共享的变量加上关键字:volatile

3.原子性

 指的是一个操作不可中断,即在多线程并发的环境下,一个操作一旦开始,就会在同一个CPU时间片内执行完毕
 ​
 volatile解决不了原子性问题,所以为了多线程操作同一个数据出现的原子性问题,我们可以使用原子类
 ​
 Atomicxxx类->xxx代表具体数据类型  ->  原子类的实现原理就是乐观锁
 public class Test02 {
 public static void main(String[] args) {
   //原子类
   AtomicInteger i = new AtomicInteger();//int i = 0
   /*int addAndGet(int delta)以原子方式将给定值与当前值相加。*/
   int i1 = i.addAndGet(2);
   System.out.println(i1);
 }
 }

第二十三章.Condition(阻塞队列)

 1.为什么要学Condition
   wait和notify,都是本地方法,如果在高并发的情况下频繁操作wait和notify,比较消耗系统资源
 2.Condition:接口
 3.获取:需要用到Lock对象中的方法
       Condition newCondition()
 4.方法:
   void await()  等待
   void signal() 唤醒
 5.注意:
   Condition必须配合Lock对象使用
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 ​
 //包子铺
 public class BaoZiPu {
     //定义count,代表包子个数
     private int count;
     //定义flag,代表是否有包子
     private boolean flag;
 ​
     Lock lock = new ReentrantLock();
 ​
     //创建一个生产者的队列,装生产者等待的线程
     Condition productCondition = lock.newCondition();
     //创建一个消费者的队列,装消费者等待的线程
     Condition consumerCondition = lock.newCondition();
 ​
 ​
 ​
     public BaoZiPu() {
     }
 ​
     public BaoZiPu(int count, boolean flag) {
         this.count = count;
         this.flag = flag;
     }
 ​
 ​
     /*
       改造get方法 ,此方法给消费线程用
       作为消费包子方法
      */
     public void getCount() {
         //获取锁
         lock.lock();
         while (this.flag == false) {
             try {
                 consumerCondition.await();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
 ​
         //消费包子
         System.out.println("消费了......第" + count + "个包子");
         //改变flag状态,为false,证明没有包子了
         this.flag = false;
 ​
         productCondition.signal();
 ​
         //释放锁
         lock.unlock();
     }
 ​
     /*
       改造set方法,此方法给生产线程用
       作为生产包子方法
      */
     public  void setCount() {
 ​
         //获取锁
         lock.lock();
         while (this.flag == true) {
             try {
                 productCondition.await();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
 ​
         //生产包子
         count++;
         System.out.println("生产了..第" + count + "个包子");
         //改变flag状态,为true,证明有包子了
         this.flag = true;
 ​
         consumerCondition.signal();
 ​
         //释放锁
         lock.unlock();
     }
 ​
     public boolean isFlag() {
         return flag;
     }
 ​
     public void setFlag(boolean flag) {
         this.flag = flag;
     }
 }
 ​
 //消费者
 public class Consumer implements Runnable {
     //创建包子铺对象
     private BaoZiPu baoZiPu;
 ​
     public Consumer(BaoZiPu baoZiPu) {
         this.baoZiPu = baoZiPu;
     }
 ​
 ​
     @Override
     public void run() {
         while (true) {
             try {
                 Thread.sleep(100L);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
 ​
             baoZiPu.getCount();
         }
     }
 }
 //生产者
 public class Product implements Runnable {
     //创建包子铺对象
     private BaoZiPu baoZiPu;
 ​
     public Product(BaoZiPu baoZiPu) {
         this.baoZiPu = baoZiPu;
     }
 ​
     @Override
     public void run() {
         while (true) {
 ​
             try {
                 Thread.sleep(100L);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
 ​
             baoZiPu.setCount();
 ​
         }
     }
 }
 public class Test01 {
     public static void main(String[] args) {
         BaoZiPu baoZiPu = new BaoZiPu();
 ​
         Product product = new Product(baoZiPu);
         Consumer consumer = new Consumer(baoZiPu);
         new Thread(product).start();
         new Thread(product).start();
 ​
         new Thread(consumer).start();
         new Thread(consumer).start();
     }
 }

第二十四章.线程池

image-20220304154338528

 1.为什么要学线程池:
   之前如果要是有线程任务了,我们就要开线程,用完还要销毁线程,所以如果频繁的开启线程,销毁线程很耗费内存资源
   所以我们想要创建线程,此线程可以循环利用
   将创建出来的线程放到一个容器中,有线程任务了,从容器中获取线程去执行;用完还回去
 1.线程池:Executors
 2.创建线程池,指定最多能创建多少个线程->Executors中的方法
   static ExecutorService newFixedThreadPool(int nThreads) 
                                             nThreads:代表的是线程池中最多能创建多少条线程对象
 3.ExecutorService:用来管理线程池中的线程的
 ​
 4.ExecutorService中的方法:
   Future<?> submit(Runnable task) -> 提交线程任务 
       
 5.Future->接口 
   a.作用:用来接收线程任务(run方法)的返回值的
   b.注意:run方法没有返回值,所以我们可以不用Future去接收    
       
 6.ExecutorService中的方法:
   void shutdown()-> 关闭线程池
 public class Test01 {
     public static void main(String[] args) {
         //创建线程池,指定线程池中最多能创建多少条线程
         ExecutorService es = Executors.newFixedThreadPool(2);
         //提交线程任务
         es.submit(new MyRunnable());
         es.submit(new MyRunnable());
         es.submit(new MyRunnable());
 ​
         es.shutdown();
     }
 }
 public class MyRunnable implements Runnable{
     @Override
     public void run() {
         System.out.println(Thread.currentThread().getName()+"...执行了");
     }
 }

第二十五章.Callable接口

 1.ExecutorService中的方法:
   Future submit(Callable<T> task)  
       
 2.Callable<V>接口:类似于Runnable
   V call()->设置线程任务的,类似于Runnable中的run方法
     
 3.call方法和run方法区别:
   a.相同点:
     call方法和run方法都是设置线程任务的
   b.不同点:
     run方法:没有返回值,而且不能在run方法上throws异常
     call方法:有返回值的,而且能在call方法上throws异常
         
 4.call方法的返回值是什么类型呢?
   Callable中<V>是啥类型,call返回值就是什么类型
         
   <v>尖括号中的类型只能是引用数据类型
      <int>->不行
      <Integer>->行
 ​
 5.Future:
   a.Future是一个接口
   b.作用:用来接收run方法或者call方法的返回值的
         run方法没有返回值,所以无需用Future接收
         call方法有返回值,所以需要用Future接收
       
   c.Future中的方法:
     V get()->获取Future接收到的返回值结果
 public class Test {
     public static void main(String[] args) throws ExecutionException, InterruptedException {
         //创建线程池
         ExecutorService es = Executors.newFixedThreadPool(2);
         Future<String> future = es.submit(new MyCallable());
         System.out.println(future.get());
     }
 }
 public class MyCallable implements Callable<String> {
 ​
     @Override
     public String call() throws Exception {
         return "柳岩和涛哥的....故事";
     }
 }
 ​

1.Callable练习

 需求:创建两个线程任务,一个线程任务完成1-100的和,一个线程任务返回一个字符串
 public class Test01 {
     public static void main(String[] args) throws ExecutionException, InterruptedException {
         ExecutorService es = Executors.newFixedThreadPool(2);
         Future<Integer> f1 = es.submit(new MySum());
         System.out.println(f1.get());
 ​
         Future<String> f2 = es.submit(new MyString());
         System.out.println(f2.get());
     }
 }
 //求和
 public class MySum implements Callable<Integer> {
     @Override
     public Integer call() throws Exception {
         int sum = 0;
         for (int i = 1; i <= 100; i++) {
             sum+=i;
         }
         return sum;
     }
 }
 public class MyString implements Callable<String> {
     @Override
     public String call() throws Exception {
         return "刘备说:飞飞,不要这样,哥哥给你哭一个";
     }
 }
 ​

第二十六章.定时器_Timer

 1.概述:Timer,定时器
 2.作用:让一个线程任务,每隔一段时间就执行一次,此时间使我们自己指定的
 3.使用:
   a.创建Timer对象
   b.调用方法
     void schedule(TimerTask task, Date firstTime, long period)  
                   TimerTask:抽象类,实现了Runnable接口,设置线程任务
                   firstTime:从哪个时间开始算起
                   period:每隔多长时间线程任务执行一次,传递毫秒值
 public class Test01 {
     public static void main(String[] args) {
         Timer timer = new Timer();
         timer.schedule(new TimerTask() {
             @Override
             public void run() {
                 System.out.println("柳岩说:涛哥,起床了");
             }
         },new Date(),2000L);
     }
 }