Java基础学习笔记

Java基础学习笔记

语言基础

面向对象

思想
对象之间的关系
  • 继承
  • 依赖
  • 关联
什么是继承?

继承是从已有类获得继承信息而创建新类的过程,提供继承信息的类称为父类(或基类),获取继承信息的类称为子类(或派生类),子类继承父类的功能,并可以增加它自己的新功能特性的功能。

判断是否为继承关系:

  • 子类 is 父类
  • class 子类 extends 父类{}
关键字

继承: extends
在这里插入图片描述
在UML类图设计中,继承用一条带空心三角形箭头的实线表示,从子类指向父类,或者子接口指向父接口

实现:interface

实现指的是class 类实现interface接口的功能,是类与接口间最常见的关系
在这里插入图片描述

在UML类图设计中,实现用一条带空心三角箭头的虚线表示,从类指向实现的接口

什么是依赖?

概念:依赖就是一个类A使用到了另一个类B,而这种关系是具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A类。即做某件事时才产生联系,这种关系就是依赖

判断是否为依赖关系

  • A类 use B类
  • 代码实现
//1.方法里面new
class Rerson{ 
    public void eat(){
        Spoon s = new Spoon(); 
    } 
}

//2.以参数的形式传入
class Rerson{ 
    public void eat(Spoon s){
    } 
}

在这里插入图片描述
在 UML类图设计中,依赖用一条带箭头的虚线箭头表示

什么是关联?

概念:关联体现的是两个类之间的语义级别的一种强依赖关系,一般是长期性的,并且双方的关系一般是平等的。关联可以是单向、双向的。

代码层面,为被关联类B以类的属性形式出现在关联类A中,也可能是关联类A引用了一个类型为被关联类B的全局变量

判断是否为关联关系

  • 一种强依赖,比依赖的关系更强,是一种稳定的关系
  • 如:主人和宠物的关系

*
在UML类图设计中,关联关系用由关联类A指向被关联类B的带箭头实线表示,在关联的两端可以标注关联双方的角色和多重性标志

关联关系的特例(整体和局部是否相同的生命周期)
聚合

概念:聚合是关联关系的一种特例,体现的是整体与局部的关系,即has-a的关系。

此时整体与局部是可分离的, 他们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享

判断是否为聚合关系

  • 整体和局部的关系,且整体和局部可分
  • 如:公司与员工的关系

在这里插入图片描述

在UML类图设计中, 聚合关系是以空心菱形加实线箭头表示

组合

概念:组合是关联关系的一种特例,体现的是contains-a的关系。这种关系比聚合更强,也称为强聚合。

它同样体现整体与局部间的关系,但此时整体与局部是不可分的,整体的生命周期结束也就意味着局部生命周期的结束

判断是否为组合关系

  • 整体和局部的关系,且整体和局部不可分
  • 如:人和人的大脑的关系

在UML类图设计,组合关系是以实心菱形加实线箭头表示

三大特征
封装

封装就是将数据和操作数据的方法绑定起来,只能通过已定义的接口对数据进行访问。

在类中编写的方法就是对实现细节的一种封装,编写的类就是对数据和数据操作的封装。

封装就是隐藏一切可隐藏的东西,只对外提供最简单的编程接口。

继承

继承是从已有类获取继承信息的过程,提供继承信息的类称为父类,获取继承信息的类称为子类。

子类在继承父类的方法的同时,可以增加自己的新功能特性。

多态

多态是指允许不同子类的对象对同一消息做出不同的响应。

多态性可细分为编译时的多态性运行时的多态性

方法重载实现的是编译时的多态性。方法重载是指在同一个类中,出现了重名的多个方法,但是他们的参数列表(参数个数、参数类型、参数顺序)不尽相同,在根据实际参数的个数、类型和顺序,在编译时确定执行重载方法的哪一个,这就是编译时的多态性。

而方法重写实现的是运行时的多态性,运行时多态是面向对象最精髓的东西,子类继承父类并重写父类中已有的方法或抽象方法,用父类型引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为,它在编译时调用的是同样父类中的方法,而在运行时才表现出其不同的行为,称为运行时多态性。

四个常用关键字
static

static使用场景:成员变量、成员方法、代码块、内部类

static变量也称为静态变量,静态变量被所有对象共享,内存中只存在一个副本,在类初始加载时才会初始化;而普通的非静态变量是对象所拥有的,在创建对象的时候初始化,可以存在多个副本,多个对象所拥有的非静态变量互不影响。

static方法也称为静态方法,由于静态方法不依赖于任何对象就可以直接访问,因此,对静态方法来说是没有this的,因为他不依附于任何对象,没有对象就没有this。所以静态方法中不能包含非静态成员变量和非静态方法。如果静态方法中调用非静态成员变量,在编译时,非静态成员变量并没有对象,该变量不存在,编译失败;而编译器也无法预知非静态方法中是否访问了非静态成员变量,所以也禁止在静态方法中调用非静态成员方法。

static代码块,用于类的初始化操作,在静态代码块中不能直接访问非静态成员。静态初始化块可以放置在类中的任何地方,类中可以有多个静态初始化块,在类初次被加载时,会按照静态初始化块的顺序来执行每个块,并且只会执行一次。因此,很多时候会将只需要执行一次的初始化操作放在static代码块中进行,提升程序性能。

普通类是不允许声明为静态的,只有内部类可以,被static修饰的内部类可以直接作为一个普通类来使用,而不需要先实例化一个外部类。

final

final主要用于修饰变量、方法、类。

final修饰变量时,如果是基本数据类型的变量,则其数据一旦赋初值后就不能被覆盖;如果是引用类型的变量,则初始化之后不能让其指向另一个对象。

final修饰方法时,表名该方法不能被重写。final方法所在类被继承时,被final修饰的方法不能被重写,但是可以重载。使用final方法的原因:1.将方法锁定,防止其他继承类修改它的含义。2.提高效率

final修饰类时,表名该类不能被继承,final类中所以的成员方法都会隐式地指定为final方法;静态内部类中可以定义静态成员和实例成员。外部类以外的其他类需要通过完整的类名访问静态内部类中的静态成员,如果要访问静态内部类中的实例成员,则需要通过静态内部类的实例;静态内部类可以直接访问外部类的静态成员,如果要访问外部类的实例成员,则需要通过外部类的实例去访问。

this

this可用于任何实例方法内指向当前对象,也可指向对其调用当前方法的对象,或者在需要当前类型对象引用时使用

this.属性名称:指的是访问类中的成员变量,用来区分成员变量和局部变量的重名问题。

this.方法名称:用来访问本类的成员方法

this():访问本类的构造方法,()中可以有参数,如果有参数,就是调用带参的构造函数,this()不能使用在普通方法中,只能写在构造方法中,且必须是构造方法中的第一条语句

super

用于子类不能继承父类的构造方法,因此,如果要调用父类的构造方法,可以使用super。super可以用来访问父类的构造方法、普通方法、属性

super.父类属性名:调用父类中的属性

super.父类方法名:调用父类中的方法

super():调用父类的无参构造方法,必须在子构造方法的方法体的第一行

super(参数):调用父类的有参构造方法

内部类
什么是内部类?

内部类是将一个类定义在另一个类的内部。内部的类称为内部类

public class Outer{
    class Inner{
     
    }
}
内部类的特点

1.内部类可以很好的实现隐藏,可以使用protected、private修饰符

2.内部类可以直接访问外部类的所有成员,包括私有的成员

3.外部类不能直接访问内部类的成员,必须首先建立内部类的对象才可以访问

4.内部类可以解决一些问题,比如间接地去实现多继承。避免修改接口而实现同一个类中两种同名方法的调用

内部类的分类
  • 成员内部类
  • 静态内部类
  • 匿名内部类
  • 局部内部类
    在这里插入图片描述

public class LambdaDemo4 {
    //静态内部类
    static class MyLove2 implements ILove {
        @Override
        public void love(int a) {
            System.out.println("我是静态内部类,I Love You-->" + a);
        }
    }

    public static void main(String[] args) {
        //局部内部类
        class MyLove3 implements ILove {
            @Override
            public void love(int a) {
                System.out.println("我是局部内部类,I Love You-->" + a);
            }
        }


        //外部类
        ILove love = new MyLove();
        love.love(1);

        //静态内部类
        love = new MyLove2();
        love.love(2);

        //局部内部类
        love = new MyLove3();
        love.love(3);

        //匿名内部类
        new ILove() {
            @Override
            public void love(int a) {
                System.out.println("我是匿名内部类,I Love You-->" + a);
            }
        }.love(4);

        //Lambda表达式
        ((ILove) a -> System.out.println("我是Lambda表达式,I Love You-->" + a)).love(5);

    }
}

interface ILove {
    void love(int a);
}


//外部类
class MyLove implements ILove {
    @Override
    public void love(int a) {
        System.out.println("我是外部类,I Love You-->" + a);
    }
}

成员内部类

特点

  • 成员内部类属于外部类的实例成员,成员内部类可以有public,private,default,protected权限修饰符。在成员内部类中访问外部类的成员方法和属性,要使用 “外部类.this.成员方法” 和 "外部类名.this.成员属性"的形式
  • 创建成员内部类的实例使用"外部类名.内部类名 实例名 = 外部类实例名.new 内部类构造方法(参数)"的形式

成员内部类的限制

  • 成员内部类不能与外部类重名
  • 不能在成员内部类中定义static属性、方法和类(static final形式的常量定义除外)。因为一个成员内部实例必然与一个外部类实例关联,static成员完全可以移到其外部类中去。

public class MemberInnerClass {
    public static void main(String[] args) {
        //创建外部类
        Outer1 outer = new Outer1();
        //创建了内部类的对象inner
        Outer1.Inner1 inner = outer.new Inner1();
        inner.innerShow();
    }
}

class Outer1{
    private String name = "张山";
    private int num1 = 10;
    public void outerShow(){
        System.out.println(name);
        System.out.println(num1);
    }
    public class Inner1{
        private String name = "李四";
        private int num2 = 22;
        public void innerShow(){
            System.out.println(Outer1.this.name);
            System.out.println(Outer1.this.num1);
            outerShow();
            System.out.println(name);
            System.out.println(num2);
        }

    }
}

静态内部类

特点

  • 使用static修饰的成员内部类叫静态内部类
  • 静态内部类跟外部类没有任何关系,只是在生成类名和类定义时有影响。静态内部类可以看作是外部类平级的类。使用方式和外部类平级的类完全相同
  • 创建静态内部类的实例使用 “外部类名.内部类名 实例名 = new 外部类名.内部类名(参数)

静态内部类的限制

  • 静态内部类不能与外部类重名
  • 静态内部类不能访问外部类的非静态的属性和方法。外部类不能访问内部类的非静态的属性和方法
public class StaticInnerClass {
    public static void main(String[] args) {
        Outer2 outer2=new Outer2();
        Outer2.Inner2 inner2 = new Outer2.Inner2();
        inner2.innerShow();
        outer2.outerShow();
    }
}


class Outer2{
    private static String name = "张山";
    private int num1 = 10;
    private static int num2 = 200;
    public void outerShow(){
        System.out.println(name);
        System.out.println(num1);
        System.out.println(Inner2.num3);
    }
    public static class Inner2{
        private String name = "李四";
        private int num2 = 22;
        private static int num3 = 33;

        public void innerShow(){
            //静态内部类不能访问外部类的非静态成员
//            outerShow();
            System.out.println(name);
            System.out.println(Outer2.num2);
            System.out.println(num2);
        }

    }
}


匿名内部类

特点

  • 匿名内部类是没有名称的内部类,没办法引用它们。必须在创建时,作为new语句的一部分来声明并创建他们的实例
  • 匿名内部类必须继承一个类(抽象、非抽象都可以)或者一个接口。如果父类(或父接口)是抽象类,则匿名内部类必须实现其所有抽象方法
  • 匿名内部类中可以定义代码块,用于实例的初始化,但是不能定义静态代码块


public class AnonymousInnerClass {
    public static void main(String[] args) {

        Person p=new Person();
        /*
        Animal dog=new Dog();
        p.feed(dog);
         */
        new Animal(){
            private String name="zzz";
            /*static {
                System.out.println("我是静态代码块,静态内部类中不能有静态代码块");
            }

             */
            @Override
            public void eat() {
                System.out.println("吃狗粮");
            }
            public void show(){
                System.out.println("hhh");
            }
        }.show();

        p.feed(new Animal(){
            @Override
            public void eat() {
                System.out.println("吃狗粮");
            }
        });
        p.feed(new Animal(){
            @Override
            public void eat() {
                System.out.println("吃老鼠");
            }
        });

    }
}
class Person{
    public void feed(Animal animal){
        animal.eat();
    }
}

abstract class Animal{
    public abstract void eat();
}

/*
class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println("吃狗粮");
    }
}
 */


匿名内部类语法

  • 这种形式的new语句声明一个新的匿名类,它对一个给定的类进行扩展,或者实现一个给定的接口,并同时创建该匿名类的一个新实例

  • new interface/superclass() {//类体}

局部内部类

特点

  • 定义在代码块、方法体内的类叫局部内部类
  • 局部内部类访问外部类的属性和方法,使用 “外部类名.this.属性名” 和 "外部类名.this.方法名(参数)"的形式
  • 对外部世界完全隐藏,只能在其作用域内生成对象

局部内部类的限制

  • 局部类不能加访问修饰符,因为他们不是类成员
  • 成员内部类不能与外部类重名
  • 局部内部类访问作用域的内部变量,该局部变量需要使用final修饰

public class LocalInnerClass {
    public static void main(String[] args) {
        Outer3 outer3 = new Outer3();
        outer3.showOuter();
    }
}

class Outer3{
    private String name = "张三";
    private int num1 = 10;
    private static int num2=220;
    public void showOuter(){
        final int num3=30;//局部变量需要使用final修饰,jdk1.8开始,默认添加上final修饰符
        //局部内部类不能加访问修饰符
        class Inner3{
            private int num4=40;
            public void showInner(){
                System.out.println(num4);
                System.out.println(Outer3.this.num1);
                System.out.println(Outer3.num2);
                System.out.println(num3);
            }
            public void show(){
                System.out.println("show");
            }
        }
        Inner3 inner=new Inner3();
        inner.showInner();
    }
}

lambda表达式
什么是函数式编程?

函数式编程是一种思想特点,忽略面向对象的复杂语法,强调做什么,而不是谁去做

函数式接口

  • 任何接口,如果只包含唯一一个抽象方法,那么他就是一个函数式接口
public interface Runnable{
	public abstract void run();
}
  • 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
什么是lambda表达式?
  • lambda表达式是JDK 8开始后的一种新语法形式
  • lambda的完整格式
    • ()对应方法的形参
    • ->固定格式
    • {}对应方法的方法体
() -> {
	
}
  • Lambda表达式可以用来简化匿名内部类的书写

  • Lambda表达式只能简化函数式接口的匿名内部类的写法

  • 有且仅有一个抽象方法的接口叫做函数式接口,接口上方可以加@FunctionalInterface注解,满足语法要求加上注解不会报错,不满足要求加上注解会报错

lambda表达式的省略写法

  • 参数类型可以省略不写
  • 如果只有一个参数,参数类型可以省略,同时()也可以省略
  • 如果lambda表达式的方法体只有一行,大括号,分号,return可以省略不写,需要同时省略

lambda表达式的使用前提

  • 必须是接口的匿名内部类,接口中只能有一个抽象方法

lambda表达式的作用

  • 简化函数式接口的匿名内部类的写法,去掉了没有意义的代码,留下核心的逻辑

  • Lambda是一个匿名函数,我们可以把Lambda表达式理解为一段可传递的代码,可以写出更简洁、更灵活的代码,作为一种更加紧密的代码风格,使Java语言表达能力得到了提升

public class LambdaDemo2 {
    public static void main(String[] args) {
        /**
         * 1.参数类型可以省略不写。
         * 2,如果只有一个参数,参数类型可以省略,同时()也可以省略。
         * 3.如果Lambda表达式的方法体只有一行,大括号,分号,return可以省略不写,需要同时省略。
         */

        Integer[] arr = {2,5,6,9,4,7,1,0};

//        //匿名内部类
//        Arrays.sort(arr, new Comparator<Integer>() {
//            @Override
//            public int compare(Integer o1, Integer o2) {
//                return o1 - o2;
//            }
//        });
//
//        //lambda完整格式
//        Arrays.sort(arr,(Integer o1, Integer o2) -> {
//            return o1 - o2;
//        });

        //lambda省略写法
        Arrays.sort(arr,((o1, o2) -> o1 - o2));

        System.out.println(Arrays.toString(arr));

    }
}
接口和抽象类
什么是抽象?
  • 抽象是把多个事物的共性内容提取出来,隐藏一切可隐藏的细节,只对外提供最基本的编程接口,本质就是把我们关注的内容提取出来
  • 如:猫、狗都属于动物,动物是抽象出来的概念
什么是抽象方法?
  • Java中可以定义没有方法体的方法,该方法由其子类来具体实现。没有方法体的方法我们称之为抽象方法

抽象方法特点

  • 只有方法头没有方法体的方法
  • 抽象方法用abstract来修饰
  • 抽象方法代表一种不确定的操作或行为
  • 抽象方法不能被调用
什么是抽象类?
  • 含有抽象方法的类我们称之为抽象类

抽象类的特点

  • 定义中含有抽象方法的类
  • 抽象类用abstract 来修饰
  • 抽象类代表一种抽象的对象类型
  • 抽象类不能实例化
  • 抽象类中可以有具体方法,可以没有抽象方法
public abstract class A {
    public void methodA(){
    
    }
    public abstract void methodB();
}
模板方法模式
  • 一个模板方法用一些抽象的操作定义一个算法,而子类将重定义这些操作以提供具体行为
  • 定义了一个操作中的算法框架,把一些步骤推迟到子类去实现。模板方法模式让子类不需要改变算法结构而重定义特定的算法步骤

public class TemplateDemo {
    public static void main(String[] args) {
        Teacher t1=new CTeacher();
        t1.work();
        Teacher t2=new DBTeacher();
        t2.work();
    }
}

abstract class Teacher{
    public void prepared(){
        System.out.println("到达教室");
        System.out.println("打开投影仪");
    }

    public void end(){
        System.out.println("关闭投影仪");
        System.out.println("关闭门窗");
    }

    //方法定义为抽象方法
    public abstract void teaching();

    //模板方法
    public void work(){
        //1.准备工作
        prepared();

        //2.进行授课
        teaching();

        //3.下课后的任务
        end();
    }
}

class CTeacher extends Teacher{
    //对抽象方法的实现
    public void teaching(){
        System.out.println("打开VM");
        System.out.println("书写C的指令");
        System.out.println("调试C的程序");

    }
}

class DBTeacher extends Teacher{
    //对抽象方法的实现
    public void teaching(){
        System.out.println("打开MySQL");
        System.out.println("书写SQL指令");
        System.out.println("运行SQL指令");

    }
}

接口概念和作用

定义:抽象类是从多个类中抽象出来的模板,如果将这种抽象进行的更彻底,则可以提取出一种更加特殊的“抽象类”——接口,interface

接口的特点
  • 接口中只能存放静态常量和抽象方法
  • Java接口是对功能的扩展
  • 通过实现接口,Java类可以实现多实现
  • 一个类可以同时继承一个父类并且实现多个接口
  • 接口与接口之间可以通过extends来产生继承关系
接口和抽象类的区别
  • 抽象类和具体实现类之间是一种继承关系,也就是说如果采用抽象类的方式,则父类和子类在概念上应该是相同的
  • 接口和实现类在概念上不要求相同,接口只是抽取相互之间没有关系的类的共同特征,而不去关注类之间的关系,它可以使没有层级关系的类具有相同的行为
  • 抽象类是对一组具有相同属性和行为的逻辑上有关系的事物的一种抽象,而接口是对一组具有相同属性和行为的逻辑上不相关的事物的一种抽象
  • 对于接口和抽象类的选择,反映出设计人员宽带问题的不同角度,抽象类用于一组相关的事物,表示的是“is-a”的关系;而接口用于一组不相关的事物,表示的是“like-a”的关系
面向接口编程
  • 开发系统时,主体框架使用接口,接口构成系统的骨架,这样就可以通过更换接口的实现类来更换系统的实现
简单工厂模式

定义:专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类或接口

提供一个类,由它负责根据一定的条件创建某一具体类的实例


public class FactoryDemo {
    public static void main(String[] args) {
        IFruit fruit = Factory.getFruit("橘子");
        if (fruit!=null){
            System.out.println(fruit.get());
        }else {
            System.out.println("抱歉,工厂暂时未能提供您要的水果");
        }
    }
}

class Factory{
    public static IFruit getFruit(String name){
        if ("苹果".equals(name)){
            return new Apple();
        } else if ("香蕉".equals(name)) {
            return new Banana();
        }else {
            return null;
        }
    }
}

interface IFruit{
    public String get();
}

class Apple implements IFruit{
    public String get(){
        return "苹果get!";
    }
}

class Banana implements IFruit{
    public String get(){
        return "香蕉get!";
    }
}
策略模式
定义
  • 策略模式是对算法的包装,把使用算法的责任和算法本身分隔开,委派给不同的对象管理。策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类型的子类型
  • 针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使他们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化

public class StrategyDemo {
    public static void main(String[] args) {
        int[] array = {1,5,6,26,88,22,90,11,62,4};
        ISort bubbleSort = new BubleSort();
        ISort selectSort = new SelectSort();
        Context context = new Context(selectSort);
        context.sort(array);
        context.printArray(array);
    }
}

class Context{
    private ISort sort = null;
    public Context(ISort sort){
        this.sort = sort;
    }
    public void sort(int[] array){
        //交给具体接收到的策略类来帮忙排序
        sort.sort(array);
    }
    public void printArray(int[] array){
        for (int i=0;i<array.length;i++){
            System.out.println(array[i]+" ");
        }
    }

}

interface ISort{
    public void sort(int[] array);
}

//冒泡算法
class BubleSort implements ISort{
    public void sort(int[] array){
        System.out.println("冒泡算法");
        for (int i=0;i<array.length-1;i++){
            for (int j=0;j<array.length-i-1;j++){
                if (array[j]>array[j+1]){
                    int temp = array[j];
                    array[j]=array[j+1];
                    array[j+1]=temp;
                }
            }
        }
    }
}


//选择排序算法
class SelectSort implements ISort{
    public void sort(int[] array){
        System.out.println("选择算法");
        int min=0;
        for (int i=0;i<array.length;i++){
            min=i;
            for (int j=i+1;j<array.length;j++){
                if (array[min]>array[j]){
                    min=j;
                }
            }
            if(i!=min){
                int temp = array[i];
                array[i]=array[min];
                array[min]=temp;
            }
        }
    }
}
集合

框架体系
在这里插入图片描述

  • Collection接口存储一组不唯一、无序的对象
  • List接口存储一组不唯一,有序(索引顺序)的对象
  • Set接口存储一组唯一、无序的对象
  • Map接口存储一组键值对象,提供Key到value的映射;key-唯一、无序; value-不唯一、无序
List -数据可重复、有序
ArrayList 线性表
  • 在内存中分配连续的空间,实现长度可变的数组

  • 优点:遍历元素和随机访问元素的效率比较高

  • 缺点:添加和删除需大量移动元素,效率低,按内容查找效率低

LinkedList 链表
  • 采用双向链表存储方式

  • 缺点:遍历和随机访问效率低下

  • 优点:插入、删除元素效率比较高(但是前提也是必须先低效率查询,如果插入发生在头尾可以减少查询次数)

Set -数据唯一、无序
Map -key 唯一 无序 value 不唯一 无序
线程与进程

程序:程序是指令和数据的有序集合,是一个静态的概念

进程Process

  • 进程是程序的执行过程,是系统资源分配的单位
  • 通常一个进程可以包含若干个线程,程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,GC线程
  • 在进程中,开辟多个线程,线程的运行由调度器安排调度,调度器是和操作系统紧密相关的,先后顺序是不能人为的干预

线程Thread

  • 线程就是独立的执行路径, 是CPU调度和执行的单位
  • 线程会带来额外的开销,如cpu调度时间,并发控制开销
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
线程的实现-多线程三种创建方式
  1. 继承Thread类

步骤

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程

不推荐使用:避免OOP单继承局限性,子类只能继承一个父类,

  1. 实现Runnable接口

步骤

  • 定义MyRunnable类实现Runnable接口
  • 实现run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程

推荐使用:灵活方便,可以实现接口,避免单继承局限性,方便一个对象被多个线程同时使用

  1. 实现Callable接口

步骤

  • 实现Callable接口,需要返回值类型
  • 重写call方法,需要抛出异常
  • 创建目标对象
  • 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
  • 提交执行:Future result1 = ser.submit(t1);
  • 获取结果:boolean r1 = result1.get();
  • 关闭服务:ser.shutdownNow()

优势

  • 可以定义返回值
  • 可以抛出异常
静态代理模式
  • 特点

    • 真实对象和代理对象都需要实现同一个接口

    • 代理对象必须代理真实角色

  • 优势

    • 代理对象可以做很多真实对象做不了的事情

    • 真实对象专注做自己的事情

线程状态

在这里插入图片描述

  • 创建状态

    • New

      • 尚未启动的线程处于此状态

      • Thread t = new Thread()

        • 线程对象一旦创建就进入到了新生状态
  • 就绪状态

    • 当调用start()方法,线程立即进入就绪状态,但不意味着立即调度执行,等待CPU调度
  • 运行状态

    • Runnable

      • 在Java虚拟机中执行的线程处于此状态

      • 获得CPU资源,进入运行状态,线程才真正执行线程体的代码块

  • 阻塞状态

    • BLOCKED

      • 被阻塞等待监视器锁定的线程处于此状态
    • WAITING

      • 正在等待另一个线程执行特定动作的线程处于此状态
    • TIMED_WAITING

      • 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
    • 当调用sleep,wait或同步锁定时,线程进入阻塞状态,就是代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待cpu调度执行

  • 死亡状态

    • TERMINATED

      • 已退出的线程处于此状态

      • 线程中断或者结束,一旦进入死亡状态,就不能再次启动

线程停止
  • 建议线程正常停止——>利用次数,不建议死循环

  • 建议使用标志位——>设置一个标志位

  • 不要使用stop 或者 destroy 等过时的或者JDK不建议使用的方法

线程休眠
  • Thread.sleep(1000); //线程休眠,指定当前线程阻塞的毫秒数

  • sleep存在异常 InterruptException

  • sleep时间达到后线程进入就绪状态

  • sleep可以模拟网络延时,倒计时等

  • 每一个对象都有一个锁,sleep不会释放锁

线程礼让
  • 礼让线程,让当前正在执行的线程暂停,但不阻塞

  • 将线程从运行状态转为就绪状态

  • 让cpu重新调整,礼让不一定成功,看CPU心情

  • Thread.yield(); //线程礼让

线程强制执行
  • thread.join(); //相当于插队
线程优先级
  • 优先级高的不一定先执行,只是增加他先被CPU调度的概率
守护线程
  • 线程分为守护线程和用户线程

  • 虚拟机必须确保用户线程执行完毕

  • 虚拟机不用等待守护线程执行完毕

    • 如后台记录操作日志,监控内存,垃圾回收机制
线程同步
  • 多个线程操作同一个资源
线程同步的形成条件:队列+锁
  • 线程同步是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用
  • 为解决访问冲突,保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁

存在问题

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起
  • 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
并发

同一个对象被多个线程同时操作

同步方法、同步块

同步方法

  • synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行

  • 缺点

    • 将大的方法引申为synchronized将会影响效率

同步块

  1. synchronized (Obj){}
  2. 方法里面需要修改的内容才需要锁,锁的太多,浪费资源,所以引入代码块
  3. Obj–同步监视器
    • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器

    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class

同步监视器的执行过程

1.第一个线程访问,锁定同步监视器,执行其中代码

2.第二个线程访问,发现同步监视器被锁定,无法访问

3.第一个线程访问完毕,解锁同步监视器

4.第二个线程访问,发现同步监视器没有锁,任何锁定并访问

线程协作/生产者消费者模式

这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件

  1. 生产者,在没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费
  2. 消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费

在生产者消费者问题中,仅有synchronized是不够的, synchronized可阻止并发更新同一个共享资源,实现同步,但是synchronized不能依赖实现不同线程之间的消息传递。

解决方法

  1. 并发协作模式“生产者/消费者模式”——>管程法:
  • 生产者:负责生产数据的模块(可能是方法,对象,线程,进程
  • 消费者:负责处理数据的模块(可能是方法,对象,线程,进程
  • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区
  • 生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
  1. 并发协作模式“生产者/消费者模式”——>信号灯法
  • 通过标志位来判断
线程通信问题

均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常 IllegalMonitorStateException 非法监视器状态异常

  • wait()

    • 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
  • wait(long timeout)

    • 指定等待的毫秒数
  • notify()

    • 唤醒一个处于等待状态的线程
  • notifyAll()

    • 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度
原子类

原子类是具有原子性的类,对于一组操作,要么全部执行成功,要么全部执行失败,不能只有其中某个部分执行成功。

作用

  • 保证并发情况下的线程安全
什么是死锁?

多个线程各自占用一些公共资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方资源释放,都停止执行的情况

死锁的四个条件

1.互斥条件:一个资源每次只能被一个进程使用

2.请求与保持条件: 一个进程因请求资源而阻塞时,对已获得的资源保持不放

3.不可剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺

4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

Lock锁

锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象

ReentrantLock 可重入锁
  • ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁
synchronized 与 Lock 的对比
  • Lock是显示锁(手动开启和关闭锁) synchronized是隐式锁,出了作用域自动释放

  • Lock只有代码块锁 synchronized有代码块锁和方法锁

  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的拓展性,提供更多的子类

  • 优先使用顺序

    • Lock > 同步代码块 > 同步方法
线程池

背景

  • 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大

思路

  • 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现重复利用。

优势

  • 提高响应速度(减少了创建新线程的时间

  • 降低资源消耗(重复利用线程池中的线程,不需要每次都创建

  • 便于线程管理

常用属性
  • corePoolSize

    • 核心池的大小
  • maximumPoolSize

    • 最大线程数
  • keepAliveTime

    • 线程没有任务时最多保持多长时间后会终止
线程池相关API
  • ExecutorService

    • 真正的线程池接口

    • 常用方法

      • void execute(Runnable command): 执行任务/命令,没有返回值,一般用来执行Runnable

      • Future submit(Callable task):执行任务,有返回值,一般又用来执行Callable

      • void shutdown():关闭链接池

  • Executors

    • 工具类、线程池的工厂类,用于创建并返回不同类型的线程池
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值