# 定义类

  • 格式:

    public class 类名{
        // 静态代码块
        // 构造代码块
        // 成员变量
        // 构造方法
        // 成员方法(set\get)
        // 内部类
    }
    
  • 案例

    /**
     * Created by PengZhiLin on 2021/8/19 9:18
     */
    public class Person {
        // 静态代码块
        static {
            System.out.println("静态代码块是随着类的加载而执行,并且只执行一次");
        }
        
        // 构造代码块
        {
            System.out.println("构造代码块是每次调用构造方法之前都会执行一次");
        }
        
        // 成员变量
        private String name;
        private int age;
        
        // 构造方法
        public Person(){
    
        }
    
        public Person(String name,int age){
            this.name = name;
            this.age = age;
        }
        
        // 成员方法(set\get)
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
        
        public void show(){
            System.out.println(name+","+age);
        }
        
        // 内部类
        public class NClass{
            
        }
    }
    
    

对象的创建和使用

  • 格式:

    • 创建对象: 类名 对象名 = new 类名(实参);
    • 对象使用:
      • 成员变量: 对象名.成员变量名
      • 非静态成员方法:
        • 无返回值的方法: 对象名.成员方法名(实参);
        • 有返回值的方法:
          • 方式一: 对象名.成员方法名(实参);
          • 方式二: 数据类型 变量名 = 对象名.成员方法名(实参) ;
          • 方式三: 另一个方法名(对象名.成员方法名(实参))
    • 静态成员:
      • 静态成员变量: 类名.成员变量名
      • 静态成员方法:
        • 无返回值的方法: 类名.成员方法名(实参);
        • 有返回值的方法:
          • 方式一: 类名.成员方法名(实参);
          • 方式二: 数据类型 变量名 = 类名.成员方法名(实参) ;
          • 方式三: 另一个方法名(类名.成员方法名(实参))
  • 案例

    
    /**
     * Created by PengZhiLin on 2021/8/19 9:17
     */
    public class Test {
        public static void main(String[] args) {
            // 1.创建Person对象
            Person p1 = new Person();
            Person p2 = new Person("张三",18);
    
            // 2.使用对象
            p1.setName("李四");
            p1.setAge(19);
            System.out.println(p1.getName()+","+p1.getAge());
    
            int age = p1.getAge();
            
            // 1.对象名或者类名 点
            // 2.根据提示选择要调用的方法--->查看是否有返回值以及参数个数和类型
            // 3.有返回值,并且要得到返回值,就直接在后面点var+回车,如果不需要得到返回值,就直接分号结束
            // 4.有参数,就传实际参数,没有参数,就不传
    
        }
    }
    
    

继承

  • 格式:

    public class 子类名 extends 父类名{}
    
  • 继承后的成员访问特点:

    • 子类可以继承父类的所有成员变量和成员方法

    • 优先在子类中查找,子类如果有,就直接使用,如果没有就去父类中找....

  • 方法的重写:

    • 概述: 子类中出现和父类一模一样的方法(方法名,返回值类型,形参列表)
    • 注意:
    • 重写的方法可以使用@Override注解标识
      • 子类中重写方法的访问权限不能低于父类中方法的访问权限
      • 子类中出现和父类一模一样的静态方法不属于方法重写

多态

多态的几种表现形式

  • 多态的条件:

    • 必须要有继承或者实现
    • 必须要有父类的引用指向子类的对象,或者接口的引用指向实现类的对象
    • 方法的重写
  • 多态的表现形式:

    • 普通父类多态

      public class Person{}
      public class Student extends Person{}
      public class Test{
          public static void main(String[] args){
              Person p = new Student();
          }
      }
      
    • 抽象父类多态

      public abstract class Person{}
      public class Student extends Person{}
      public class Test{
          public static void main(String[] args){
              Person p = new Student();
          }
      }
      
    • 父接口多态

      public interface IA{}
      public class Imp implements IA{}
      

    public class Test{
    public static void main(String[] args){
    IA a = new Imp();
    }
    }

    
    
    
    

多态时访问成员的特点

  • 访问特点:

    • 成员变量: 编译看左边,运行看左边
    • 成员方法:
      • 静态成员方法: 编译看左边,运行看左边
      • 非静态成员方法: 编译看左边,运行看右边

多态的应用场景:

  • 变量多态

    public class Animal {}
    public class Dog extends Animal{}
    public class Cat extends Animal{}
    public class Test{
        public static void main(String[] args){
            Animal anl = new Dog();
            anl = new Cat();
        }
    }
    
  • 形参多态

    public class Animal {}
    public class Dog extends Animal{}
    public class Cat extends Animal{}
    public class Test{
        public static void main(String[] args){
            Dog dog = new Dog();
            method(dog);
            
            method(new Cat());
        }
        
        public static void method(Animal anl){
           // 里面访问的规则: 遵守多态时成员访问特点进行访问成员
        }
    }
    
  • 返回值多态

    public class Animal {}
    public class Dog extends Animal{}
    public class Cat extends Animal{}
    public class Test{
        public static void main(String[] args){
            Dog dog = new Dog();
            method(dog);
            
            method(new Cat());
        }
        
        public static Animal method(Animal anl){
            // return new Dog();
            // return new Cat();
            return new Animal();
        }
    }
    

引用类型转换

向上转型
  • 概述: 子类类型自动转换为父类类型
  • eg: Animal anl = new Dog();
向下转型
  • 概述: 父类类型变量强制转换为子类类型--->父类类型的变量指向的对象一定是子类类型的对象
  • eg: Dog d = (Dog)anl;
instanceof关键字
  • 格式:

    if(对象名 instanceof 数据类型){}
    如果该对象是属于后面的数据类型,就返回true
    如果该对象不属于后面的数据类型,就返回false
    
  • 案例:

    /**
     * Created by PengZhiLin on 2021/8/19 10:54
     */
    abstract class Animal {
        public abstract void eat();
    }
    class Dog extends Animal{
        @Override
        public void eat() {
            System.out.println("狗吃骨头...");
        }
    
        public void lookHome(){
            System.out.println("狗在看家...");
        }
    }
    class Cat extends Animal{
        @Override
        public void eat() {
            System.out.println("猫吃鱼...");
        }
    
        public void catchMouse(){
            System.out.println("猫在抓老鼠...");
        }
    }
    public class Test {
        public static void main(String[] args) {
            method(new Dog());
            method(new Cat());
        }
    
        public static void method(Animal anl){
            anl.eat();
            // 转型
            if (anl instanceof Dog){
                Dog dog = (Dog)anl;
                dog.lookHome();
            }
    
            if (anl instanceof Cat){
                Cat cat = (Cat)anl;
                cat.catchMouse();
            }
        }
    }
    
    

接口

定义格式

  • 格式:

    public interface 接口名{
        //常量(jdk7及其之前)--使用public static final修饰,这三个关键字可以省略
        //抽象方法(jdk7及其之前)--使用public abstract修饰,这2个关键字可以省略
        //默认方法(jdk8及其之后)--使用public default修饰,public关键字可以省略
        //静态方法(jdk8及其之后)--使用public static修饰,public关键字可以省略
        //私有方法(jdk9及其之后)--使用private修饰,不可以省略
    }
    
  • 案例:

    /**
     * Created by PengZhiLin on 2021/8/19 11:17
     */
    public interface IA {
        //常量(jdk7及其之前)--使用public static final修饰,这三个关键字可以省略
        public static final int NUM = 10;
        
        //抽象方法(jdk7及其之前)--使用public abstract修饰,这2个关键字可以省略
        public abstract void method1();
        
        //默认方法(jdk8及其之后)--使用public default修饰,public关键字可以省略
        public default void method2(){
            System.out.println("method2 默认方法...");
        }
        //静态方法(jdk8及其之后)--使用public static修饰,public关键字可以省略
        public static void method3(){
            System.out.println("method3 静态方法...");
        }
        
        //私有方法(jdk9及其之后)--使用private修饰,不可以省略
    }
    
    

实现接口

  • 单实现:

    public class 实现类 implements 接口名{
        
    }
    
  
  
  
- 多实现:

  ```java
  public class 实现类 implements 接口名1,接口名2,..{
      
  }
  • 先继承后使用

    public class 实现类 extends 父类 implements 接口名{
        
    }
    
  • 如果要实现的接口中有抽象方法,实现类必须全部重写,否则实现类也得是个抽象类

接口中成员的访问特点

  • 特点

    常量--->一般供接口直接访问,实现类也可以
    抽象方法-->只供实现类重写的
    默认方法-->只供实现类重写或者直接继承使用
    静态方法-->只供接口直接访问
    私有方法-->只能在接口内部访问
    
  • 案例

    /**
     * Created by PengZhiLin on 2021/8/19 11:21
     */
    public class Imp implements IA {
        @Override
        public void method1() {
            System.out.println("重写method1抽象方法...");
        }
    }
    
    
    /**
     * Created by PengZhiLin on 2021/8/19 11:17
     */
    public class Test {
        public static void main(String[] args) {
            Imp imp = new Imp();
            System.out.println(IA.NUM);// 访问常量
            imp.method1();// 访问抽象方法
            imp.method2();// 访问默认方法
            IA.method3();//  访问静态方法
        }
    }
    
    

接口和接口之间的关系

  • 单继承

    public interface 子接口名 extends 父接口名{}
    
  • 多继承

    public interface 子接口名 extends 父接口名1,接口名2,...{}
    
  • 多层继承

    public interface 父接口名 extends 爷接口名1{}
    public interface 子接口名 extends 父接口名{}
    

集合

  • 集合继承关系和特点:

    单列集合:以单个单个元素进行存储数据
        List集合(接口): 元素可重复,元素有索引
            ArrayList类: 底层数据结构是数组,查询快,增删慢
    		LinkedList类:底层数据结构是链表,查询慢,增删快
                
    	Set集合(接口):  元素不可重复,元素无索引
            HashSet类:底层数据结构是哈希表结构,可以保证元素唯一
    		LinkedHashSet类:底层数据结构是链表+哈希表,由哈希表保证元素唯一,由链表保证元素存取顺序一致
    		TreeSet类:底层数据结构是红黑树,可以对元素进行排序
        
    双列集合:以键值对的形式进行存储数据
        Map集合(接口): 键唯一,值可以重复,如果键重复了,值会被覆盖,根据键找值
        	HashMap类:底层数据结构是哈希表结构,可以保证键唯一
    		LinkedHashMap类:底层数据结构是链表+哈希表,由哈希表保证键唯一,由链表保证键值对存取顺序一致
    		TreeMap类:底层数据结构是红黑树,可以对键进行排序	
    
  • 集合的api:

    • Collection接口的api
    • List接口的api
    • LinkedList类的api
    • Map接口的api
    • Collections工具类的api
  • HashSet集合保证元素唯一的原理:

    1.存储元素的时候会调用元素的hashCode方法,计算哈希值
    2.判断哈希值对应的位置是否有数据
    4.如果没有数据,就直接存储进去
    5.如果有数据,说明产生了哈希冲突
    6.然后调用该元素的equals方法需要和该位置上所有元素进行一一比较:
       如果该位置上所有元素与该元素不相等,就存储
       如果该位置上所有元素有任何一个元素与该元素相等,就不存储
    
    • 注意: 如果元素是自定义类型,保证元素唯一,需要重写equals and hashCode方法

IO流

  • 分类:

    • 字节流:

      • 字节输入流: InputStream--->read(), read(byte[] bys)

        • 普通字节输入流FileInputStream : 读一个字节,读一个字节数组
        • 字节缓冲输入流BufferedInputStream: 读一个字节,读一个字节数组
        • 反序列化流ObjectInputStream : 读对象(readObject())
      • 字节输出流: OutputStream---> write(int b), write(byte[] bys,int off,int len)

        • 普通字节输出流FileOutputStream: 写一个字节,写一个字节数组

        • 字节缓冲输出流BufferedOutputStream: 写一个字节,写一个字节数组

        • 序列化流ObjectOutputStream : 写对象(writeObject())

        • 打印流PrintStream: println(), print()

    • 字符流:

      • 字符输入流: Reader----> read(), read(char[] chs)

        • 普通字符输入流FileReader: 读一个字符,读一个字符数组

        • 字符缓冲输入流BufferedReader: 读一行(readLine())

        • 转换输入流InputStreamReader: 读一个字符,读一个字符数组-->指定编码读,字节流转换为字符流

      • 字符输出流: Writer------> write(int c), write(char[] chs,int off,int len)

        • 普通字符输出流FileWriter: 写一个字符,写一个字符数组
        • 字符缓冲输出流BufferedWriter: 根据系统写换行newLine()
        • 转换输出流OutputStreamWriter: 写一个字符,写一个字符数组-->指定编码写,字节流转换为字符流
  • IO流使用步骤:

    读写一个字节:
    	1.创建输入流对象,关联数据源文件路径
        2.创建输出流对象,关联目的地文件路径
        3.定义一个int类型的变量,用存储读取到的字节数据
        4.循环读取数据
        5.在循环中,写出数据
        6.关闭流,释放资源
            
     读写一个字节数组:
    	1.创建输入流对象,关联数据源文件路径
        2.创建输出流对象,关联目的地文件路径
        3.定义一个byte类型的数组,用来存储读取到的字节数据
        3.定义一个int类型的变量,用存储读取到的字节个数
        4.循环读取数据
        5.在循环中,写出数据
        6.关闭流,释放资源   
            
    

属性集

  • 相关api:

    public Properties() :创建一个空的属性列表。
    public void load(InputStream inStream): 从字节输入流中读取键值对。
    public void load(Reader reader) 从字符输入流中读取键值对。
    
    public Set<String> stringPropertyNames() :所有键的名称的集合。
    public String getProperty(String key) :使用此属性列表中指定的键搜索属性值。  
    
  • 案例:

    // 配置文件: 一定要放在src下面
    driverClass = com.mysql.jdbc.Driver
    url = jdbc:mysql://localhost:3306/web18_1
    username = root
    password = root
        
    

/**

  • Created by PengZhiLin on 2021/8/19 12:11
    */
    public class JDBCUtils {

    public static String driverClass;
    public static String url;
    public static String username;
    public static String password;

    static {
    try {
    // 1.创建Properties对象
    Properties pro = new Properties();

         // 2.加载配置文件
         //pro.load(new FileInputStream("day16\\src\\db.properties"));
         InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("db.properties");
         pro.load(is);
         
         // 3.取值
         driverClass = pro.getProperty("driverClass");
         url = pro.getProperty("url");
         username = pro.getProperty("username");
         password = pro.getProperty("password");
    
     } catch (Exception e) {
         e.printStackTrace();
     }
    

    }
    }




# 反射

## 反射之操作成员方法

#### Method类概述

```java
Method类概述
  * 每一个成员方法都是一个Method类的对象。

通过反射获取类的成员方法

Class类中与Method相关的方法
* Method getDeclaredMethod(String name,Class... args);----->推荐
    * 根据方法名和参数类型获得对应的构造方法对象,包括public、protected、(默认)、private的
      参数1:要获取的方法的方法名
      参数2:要获取的方法的形参类型的Class对象
* Method[] getDeclaredMethods();----->推荐
    * 获得类中的所有成员方法对象,返回数组,只获得本类的,包括public、protected、(默认)、private的

通过反射执行成员方法

Method对象常用方法
*  Object invoke(Object obj, Object... args)
    * 参数1:调用该方法的对象
    * 参数2:调用该法时传递的实际参数
      返回值:该方法执行完毕后的返回值
          
*  void setAccessible(true)
    设置"暴力访问"——是否取消权限检查,true取消权限检查,false表示不取消

示例代码

public class Person {

    public void show1() {
        System.out.println("无参数无返回值show1");
    }

    public void show2(int num, String str) {
        System.out.println("有参数无返回值show2,参数num:" + num + ",参数str:" + str);
    }

    public int show3() {
        System.out.println("无参数有返回值show3");
        return 3;
    }

    private String show4(String str) {
        System.out.println("有参数有返回值show4,参数str:" + str);
        return "itheima";
    }

}

/**
 * Created by PengZhiLin on 2021/8/19 12:18
 */
public class Test {
    public static void main(String[] args) throws Exception{
        // 1.获取字节码对象
        Class<Person> c = Person.class;

        // 2.通过反射创建Person对象
        Constructor<Person> cons = c.getDeclaredConstructor();
        Person p = cons.newInstance();

        // 3.通过反射获取成员方法
        Method show1M = c.getDeclaredMethod("show1");
        Method show2M = c.getDeclaredMethod("show2",int.class,String.class);
        Method show3M = c.getDeclaredMethod("show3");
        Method show4M = c.getDeclaredMethod("show4",String.class);
        show1M.invoke(p);
        show2M.invoke(p,18,"itheima");
        Object res1 = show3M.invoke(p);
        System.out.println("res1"+res1);
        show4M.setAccessible(true);
        Object res2 = show4M.invoke(p, "itcast");
        System.out.println("res2:"+res2);


    }
}

jdk8新特性

Lambda,Stream流,方法引用

  • Lambda表达式

    格式: (参数)->{代码块}
    前提: 接口必须是函数式接口
    套路:
    	1.判断是否可以使用Lambda表达式
        2.如果可以使用,就写上()->{}
    	3.填充小括号中的内容--->函数式接口中的抽象方法的形参一致
        4.填充大括号中的内容--->实现函数式接口抽象方法的方法体
    省略:
    	1.小括号中参数类型可以省略
        2.如果小括号中只有一个参数,那么小括号也可以一起省略
        3.如果大括号中只有一条语句,那么大括号,分号和return可以省略(一起省略)
    
  • Stream流

    • 使用步骤: 获取流--->操作流---->收集结果

    • Stream流api:

      • forEach

      • count

      • collect

      • filter

      • limit

      • skip

      • map

      • concat

    • 案例:

      /**
       * Created by PengZhiLin on 2021/8/19 14:33
       */
      public class Test1 {
          public static void main(String[] args) {
              // 1.获取流
              Stream<String> stream1 = Stream.of("张三丰", "张翠山", "金毛狮王", "张无忌");
              Stream<String> stream2 = Stream.of("110", "120", "119", "114");
      
              // 2.操作流--->过滤出姓张的元素,并取前2个,打印输出
              //stream1.filter(name->name.startsWith("张")).limit(2).forEach(name-> System.out.println(name));
      
              // 3.操作流--->转换Integer类型,并跳过前2个,打印输出
              //stream2.map(str->Integer.parseInt(str)).skip(2).forEach(i-> System.out.println(i));
      
              // 4.操作流--->合并2个流,并收集到集合中
              //List<String> list = Stream.concat(stream1, stream2).collect(Collectors.toList());
              //System.out.println(list);
      
              Set<String> set = Stream.concat(stream1, stream2).collect(Collectors.toSet());
              System.out.println(set);
      
          }
      }
      
      
  • 方法引用

    使用场景: 如果一个Lambda表达式大括号中的代码就是调用另一个方法或者与另一个方法代码相同,就直接把该方法引用过滤,替换Lambda表达式
    
    套路:
    	1.判断是否可以使用方法引用
        2.确定引入的方法的类型(构造方法,静态方法,非静态方法)
        3.根据引入的格式引入方法
            构造方法: 类名::new
            静态方法: 类名::方法名
            有参数成员方法: 对象名::方法名
            无参数成员方法: 类名:: 方法名
    
    /**
     * Created by PengZhiLin on 2021/8/19 14:42
     */
    class Person{
        String name;
    
        public Person(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    public class Test2 {
        public static void main(String[] args) {
            // 1.获取流
            Stream<String> stream1 = Stream.of("张三丰", "张翠山", "金毛狮王", "张无忌");
            Stream<String> stream2 = Stream.of("110", "120", "119", "114");
    
            // 2.操作流--->过滤出姓张的元素,并取前2个,打印输出
            //stream1.filter(name->name.startsWith("张")).limit(2).forEach(name-> System.out.println(name));
            //stream1.filter(name->name.startsWith("张")).limit(2).forEach(System.out::println);
    
            // 3.操作流--->转换Integer类型,并跳过前2个,打印输出
            //stream2.map(str->Integer.parseInt(str)).skip(2).forEach(i-> System.out.println(i));
            //stream2.map(Integer::parseInt).skip(2).forEach( System.out::println);
    
            // 4.操作流--->把姓名转换为Person对象,打印输出
            //stream1.map(Person::new).forEach(System.out::println);
    
            // 5.操作流--->把姓名转换为姓名对应的字符长度,打印输出
            stream1.map(String::length).forEach(System.out::println);
    
    
        }
    }
    
    

线程安全

线程创建和启动

  • 线程创建:

    • 通过继承Thread方式

      • 创建线程子类继承Thread类

      • 在线程子类中重写run方法,把线程需要执行的任务放在run方法里面

      • 创建线程子类对象

      • 调用start方法启动线程执行任务

    • 通过实现Runnable方式

      • 创建实现类实现Runnable接口

      • 在实现类中重写run方法,把线程需要执行的任务放在run方法里面

      • 创建Thread线程对象,并传入实现类对象

      • 使用线程对象调用start方法启动线程,执行任务

  • 启动:

    • 调用线程的start()方法

    • 线程的调度:抢占式调度

    • 每一条线程都会有独立的栈空间,来执行任务.

可见性问题演示

  • 概述: 一个线程没有看见另一个线程对共享变量的修改

  • 例如下面的程序,先启动一个线程,在线程中将一个变量的值更改,而主线程却一直无法获得此变量的新值。

    1. 线程类:
    public class MyThread extends Thread {
    
        static boolean flag = false;// 主和子线程共享变量
    
        @Override
        public void run() {
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            // 把flag的值改为true
            flag = true;
            System.out.println("修改后flag的值为:"+flag);
    
        }
    }
    
    1. 测试类:
    public class Test {
        public static void main(String[] args) {
            /*
                多线程的安全性问题-可见性:
                    一个线程没有看见另一个线程对共享变量的修改
             */
            // 创建子线程并启动
            MyThread mt = new MyThread();
            mt.start();
    
            // 主线程
            while (true){
                if (MyThread.flag == true){
                    System.out.println("死循环结束");
                    break;
                }
            }
            /*
                分析后期望的结果:主线程一直死循环,当子线程把共享变量flag的值改为true,主线程就结束死循环
                实际结果: 主线程一直死循环,当子线程把共享变量flag的值改为true,主线程依然还是死循环
                原因: 子线程对共享变量flag值的改变,对主线程不可见
                解决办法: 使用volatile关键字,当变量被修饰为volatile时,会迫使线程每次使用此变量,都会去主内存获取,保证其可见性
             */
        }
    }
    
    
  • 原因:

  • JMM内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。

  • 简而言之: 就是所有共享变量都是存在主内存中的,线程在执行的时候,有单独的工作内存,会把共享变量拷贝一份到线程的单独工作内存中,并且对变量所有的操作,都是在单独的工作内存中完成的,不会直接读写主内存中的变量值

    image-20210304171418228

可见性问题解决

  • 解决办法: 使用volatile关键字,当变量被修饰为volatile时,会迫使线程每次使用此变量,都会去主内存获取,保证其可见性

  • 代码:

    public class MyThread extends Thread {
    
        volatile static boolean flag = false;// 主和子线程共享变量
    
        @Override
        public void run() {
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            // 把flag的值改为true
            flag = true;
            System.out.println("修改后flag的值为:"+flag);
    
        }
    }
    

有序性问题演示和解决

有序性问题演示

  • 有些时候“编译器”在编译代码时,会对代码进行“重排”,例如:

    ​ int a = 10; //1

    ​ int b = 20; //2

    ​ int c = a + b; //3

  • 单线程: 第一行和第二行可能会被“重排”:可能先编译第二行,再编译第一行,总之在执行第三行之前,会将1,2编译完毕。1和2先编译谁,不影响第三行的结果。

  • 但在“多线程”情况下,代码重排,可能会对另一个线程访问的结果产生影响:

    image-20210304172335471

    多线程环境下,我们通常不希望对一些代码进行重排的!!

有序性问题解决

  • 使用volatile修饰共享变量,禁止编译器重排

原子性问题演示

  • 概述:所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体

  • 请看以下示例:

    • 一条子线程和一条主线程都对共享变量a进行++操作,每条线程对a++操作100000次

    1.制作线程类

    public class MyThread extends Thread{
        static int a = 0;
    
        @Override
        public void run() {
            // 子线程对a进行自增10万次
            for (int i = 0; i < 100000; i++) {
                a++;
            }
            System.out.println("子线程执行完毕");
        }
    }
    
    
public class Test {
      public static void main(String[] args) throws Exception{
          // 创建并启动子线程
          new MyThread().start();
  
          // 主线程对a进行自增10万次
          for (int i = 0; i < 100000; i++) {
              MyThread.a++;
          }
  
          // 为了保证子线程和主线程都执行完毕
          Thread.sleep(3000);
  
          // 打印最终共享变量a的值(子线程,主线程对a的操作都执行完毕了)
          System.out.println("最终:"+MyThread.a);
          /*
              分析后期望的结果: 20万
              实际的结果: 小于或者等于20万
              原因: 主线程和子线程同时对a进行自增,产生了覆盖的效果
           */
      }
  }

原因:两个线程对共享变量的操作产生覆盖的效果

原子性问题解决

  • 解决办法: 加锁,或者使用原子类

  • 代码:

    public class MyThread extends Thread{
        //static int a = 0;
        static AtomicInteger a = new AtomicInteger(0);
    
        @Override
        public void run() {
            // 子线程对a进行自增10万次
            for (int i = 0; i < 100000; i++) {
                /*synchronized ("suo"){
                    a++;
                }*/
                   
                a.getAndIncrement();
            }
            System.out.println("子线程执行完毕");
        }
    }
    
    
    public class Test {
        public static void main(String[] args) throws Exception {
            // 创建并启动子线程
            new MyThread().start();
    
            // 主线程对a进行自增10万次
            for (int i = 0; i < 100000; i++) {
                /*synchronized ("suo"){
                    MyThread.a++;
                }*/
                MyThread.a.getAndIncrement();
            }
    
            // 为了保证子线程和主线程都执行完毕
            Thread.sleep(3000);
    
            // 打印最终共享变量a的值(子线程,主线程对a的操作都执行完毕了)
            System.out.println("最终:" + MyThread.a.get());
            /*
                解决办法: 加锁,或者原子类
             */
        }
    }
    
    

AtomicInteger类的工作原理-CAS机制

image-20201216124240281

同步代码块

  • 格式:

    synchronized(锁对象){
        
    }
    
  • 锁对象

    • 语法: 可以是任意类对象
    • 注意: 如果多条线程想要实现同步,那么这多条线程的锁对象必须一致(相同)
  • 格式: 方法的返回值类型前面加上synchronized,其余都不变

  • 锁对象:

    • 非静态成员方法: 锁对象就是this

    • 静态成员方法: 该方法所在类的字节码对象,类名.class

Lock锁

  • public void lock(); 加锁

  • public void unlock(); 释放锁

  • Lock锁的等待唤醒机制:

    package com.itheima.demo7_lock锁扩展;
    
    /**
     * Created by PengZhiLin on 2021/8/9 9:55
     */
    public class MyThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                Test.lock.lock();
                    // 条件(flag=true): 子线程进入无限等待
                    if (Test.flag == true) {
                        try {
                            Test.condition.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 条件(flag=false): 子线程被唤醒,执行任务代码
                    if (Test.flag == false) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("子线程:第" + i + "次i循环");
                        // 唤醒主线程
                        Test.condition.signal();
                        // 修改旗帜变量的值
                        Test.flag = true;
                    }
                Test.lock.unlock();
            }
        }
    }
    
    
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * Created by PengZhiLin on 2021/8/9 9:55
     */
    public class Test {
    
        // 共享Lock对象
        static Lock lock = new ReentrantLock();
        // 共享锁对象
        static Condition condition = lock.newCondition();
    
        // 共享条件变量
        static boolean flag = false;
    
        public static void main(String[] args) {
    
            // 创建并启动子线程
            new MyThread().start();
    
            // 主线程任务
            for (int j = 0; j < 100; j++) {
                lock.lock();
                    // 条件(flag=false): 主线程进入无限等待
                    if (flag == false) {
                        try {
                            condition.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 条件(flag=true): 主线程被唤醒,执行任务代码
                    if (flag == true) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("主线程:第" + j + "次j循环");
                        // 唤醒子线程
                        condition.signal();
                        // 修改旗帜变量的值
                        flag = false;
                    }
               lock.unlock();
            }
        }
    }
    
    
posted on 2022-04-24 23:46  ofanimon  阅读(22)  评论(0编辑  收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css