java 内部类构造函数执行顺序_【Java学习】

线程范围内的数据共享

该运用在web中比较常见,例如银行转账系统,可能会有多个用户同时进行转账,这个时候使用的数据只能是在当前线程中被共享,相对于线程外面必须是独立的,否则就会出现不可预料的错误

如何实现在线程范围内的数据共享?

思路:通过map集合的方式实现,让map集合保存当前线程(Thread.currentThread())和线程使用的数据,把线程保存为key,数据保存为map的value,要取出当前线程的数据就可以通过map的get方法来获取当前key的值,这样就能实现该值只属于某个线程

使用ThreadLocal类实现线程范围内的数据共享

该类提供了线程局部 (thread-local)

变量,相当于是一个map集合,与map集合不同的是它不需要设置key(当前线程),只是需要值,自动和当前线程绑定,通过get方法获取当前线程上的值

如果当线程消失后,该线程局部变量会被垃圾回收(除非存在对这些值的其他引用)

停止线程

Stop方法已经过时.

如何停止线程?

只有一种,run方法结束,开启多线程运行,运行代码通常是循环结构.只要控制住循环,就可以让run方法结束,也就是线程的结束.

特殊情况:当线程处于冻结状态,就不会读取到标记,那么线程就不会结束

当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除,强制让线程恢复到运行状态,这样就可以操作标记让线程结束

Thread类中提供了该方法interrupt();à将处于冻结状态的线程强制的恢复到运行状态,是在清除线程的冻结状态不是结束状态

数组

概念-> 同一种类型数据的集合,其实数组就是一个容器。

好处->  可以自动给数组中的元素从0开始编号,方便操作这些元素

数组引用只能接收null和数组对象地址

一般定义方式:int [] arr = new int[9];

赋值:arr[4]=90;

int arr [] = new int[9];

int arr [] = new int []{1,5,5,5,};注意这里给了详细的数字就不能在给长度避免发生值和个数不匹配

int [] arr = {1,11,11,1};

注意:下标越界在编译的时候是检查不错错误的,因为在编译阶段检查的是语法错误,只有在运行的时候才会在堆中分配内存,这个时候才会检测出错误!

数组元素在没有赋值的情况下预设是0(数值数组).

Int[] arr; arr={1,2};//这种赋值方法是错误的,在定义数组的时候才支持arr={1}这种方式赋值(int[]

arr={1,5};//OK)

Int[] arr = new int[5];

arr={1,2};//error,可以使用数组下标进行赋值(arr[0]=1;//OK)

数组的操作

1.获取数组的元素

int [] arr = new int[3];

通常会用到遍历。(用for即可)

除了可以手动控制for中循环条件中数组长度还可以通过用---数组名.length(获取数组长度)属性来获取数组的长度

For(int i = 0;i

2.获取最值:

(1).获取最值需要对数组元素进行比较,每一次比较都会有一个较大的值,因为该值不确定需要通过一个变量进行临时存储。

(2).让数组中的每一个元素都和这个变量中的值进行比较。

(3).当所有的元素都比较完成,那么该变量中存储的就是数组中的最值(最大或最小)

步骤:

1.定义变量,初始化数组中的任意一个元素即可。

2.通过循环语句对数组进行遍历

3.在变量过程中定义判断条件,如果变量得到的元素比变量中的元素大或小,就赋值给该变数。

关于临储的值是否能为0,可以,这种方式实际上就是在初始化数组中的任意角标(参见截图中的:数组_最值1和最值2 )

3.排序:

(1).选择排序(截图名:选择排序)

选择排序原理:

从下标0开始,依次和所有元素比较,得出最值,通过临时变量来交换两数据!(下标0和1比,0和2比…….)

注意:外循环控制遍历次数,内循环用来比较和交换,最后一个下标可以不用在做比较,为了代码的效率一般在外循环的循环条件减1!

(2).冒泡排序(截图名:冒泡排序原理图)

原理:最相邻的两个元素进行比较如果符合条件就交换数据、

第一圈:最值出现了最后位

相比之下冒泡排序比较快

无论什么排序都需要对元素进行位置的置换

(3).普通查找(截图名:普通查找):

要知道该数是否在数组中存在,首先需要给这个函数(假设)发送我们需要查找的数组和需要查找的数,当在元素中找到该数时候就返回该数在数组中的下标,没有找到应该返回一个-1因为下标最低的是0,返回负数表示不存在,如果该数在数组中重复出现只能返回第一次找到的下标!

4.折半查找:

优点:可以提高查找效率

前提条件:数组必须是有序的

5.对数组的其他操作参见截图和程序(位置:G:\javacode\进制操作)

二维数组(参见截图:G:\数据\c语言和Java\Java笔记\截图)

定义方式:int [][] y = new int[5][];//可以不写第二个[]中的值,但是第一个必须要写

int y [][] = new int[][];

int

[] y[] = new int [][];

注意:int [] x,y[];这里不是表示定义了int [] x;int

y[];而是int []

x; int []

y[];一个一维数组和一个二维数组

注解:中括号跟着类型走表示后面的都适用,跟名字走就只适用它本身

概念:

二维数组就是数组中的数组,int [][] y = new int

[2][3];表示一个二维数组中有2个一维数组,每个一维数组里面有3个元素,更直白的理解可以用对二维数组中一种赋值来表示:int

[][] y

={{},{},{}};里面的小{}就是一个一维数组;注意的几个事项:y[0];表示的是第一个一维数组的地址(如果输出),默认为null;

函数(Function)

定义:

就是在类中独立的具有一些功能的模块

如何定义一个函数?

1.既然函数是一个独立的功能,那么该功能的运算结果是什么先明确,也就是返回值

2.在明确在定义该功能的过程中是否需要未知的内容参与运算(是自己独立就能够完成还是需要调用者给我们提供呢?),也就是参数(形参)

其实这两个就是明确了函数的返回值类型和形参列表(就是参数的类型和个数)

注意:

在函数定义的时候,只要完成相对应的功能就OK,不要完成其他的功能!(做好自己分内之事,功能要单一)

函数的重载(overload)

概念:同一个类中,多个方法功能相仿但是需要处理的数据不同(形参不同),这个时候命名成了问题,java为我们提供了重载机制,即在同一个类中,允许存在一个以上的同名函数,只要它们的参数类型和个数不同即可。

注意:重载和返回值类型没有关系

什么时候用重载:

当定义的功能相仿,但参与运算的数据(形参)或者参数位置内容不同,那么这个时候就可以用同一个函数名称,方便阅读,但是参数列表不同!

函数的重写(override)

重写的三种情况:

1. 当子类和父类出现相同名称的变量时候:

如果子父类中出现非私有的同名成员变量时,子类要访问本类中的变数,用this(不加默认也是当前物件);子类要访问父类中的同名变数时用super

Super的使用和this的使用几乎一致

This代表的是本类对象的引用

Super代表的是父类对象的引用

2. 子父类中的函数

当子类出现和父类一模一样的函数时,子类对象调用该函数,会运行子类函数的内容,父类的函数被覆盖掉(也在非静态方法区只是没有运行)

这种情况就是函数的另一个特性:重写或覆盖

当子类继承了父类,沿袭了父类的功能到子类中,但是子类虽然具备该功能,但是功能的内容和父类不一致,这时,没有必要定义新功能,而是使用函数的覆盖特性,来重写父类的功能,保留父类的定义

注意:

1. 子类覆盖父类,必须保证子类权限大于等于父类权限,才可以覆盖,否则错误

2. 静态只能覆盖静态(一般不用)

3. 记住:重载只看同名函数的参数列表

重写子父类必须要一模一样

3.子父类中的构造函数

在对子类对象进行初始化时,父类的构造函数也会运行。那是因为子类的构造函数默认第一行有一条隐式的super();在调用父类的构造函数;而且子类的所有构造函数都默认的在第一行添加super();

为什么子类一定要访问父类中的构造函数?

因为父类中的成员子类可以直接获取,所以子类对象在建立时,所以子类需要在查看父类构造函数是如何对数据初始化的,所以就先访问一下父类的构造函数;如果要访问父类指定的构造函数,可以通过修改super();来实现

注意:super语句必须定义在子类构造函数的第一行

子类的实例化过程

子类的所有的构造函数,默认都会访问父类中的空参数的构造函数,因为子类每一个构造函数内的第一行都有一句隐式super();

当父类中没有空参数的构造函数时,子类必须手动通过super语句来指定访问父类的构造函数

当然子类的构造函数第一行也可以手动执行this语句来访问本类中的其他构造函数,子类中至少有一个构造函数的第一条语句是访问父类的构造函数

Final关键词

定义:使用该关键字修饰的类、方法等 都是最终的,不能在进行修改

1. 可以修饰类,函数,变量

2. 被final修饰的类不可以被继承

3. 被final修饰的方法不能被复写

4. 被final修饰的变量是一个常量只能被赋值一次,即可以修饰成员变量,也可以修饰局部变量;当在描述事物时,一些数据的出现值是固定的,那么这时为了增强阅读性,都给这些值起个名字,方便阅读,而这些值不需要改变,加final修饰。

作为常量:常量的书写规范所有字母都大写,如果由多个单词组成。单词间用_连结。

5. 内部类定义在类中的局部位置时,只能访问该局部被final修饰的局部变量

抽象类(方法)

当多个类中出现相同功能,但是主体不同,这是可以进行向上抽取,这时,只抽取功能定义,而不抽取功能主体。

抽象:即笼统的,不实际的,简单说就是看不懂的

抽象类的特点:

1. 抽象方法一定在抽象类中,抽象类中不一定有抽象方法

2. 抽象方法和抽象类都必须被Abstract关键词修饰

3. 抽象类不可以new创建对象,因为调用抽象方法没有意义

4. 抽象类中的抽象方法要被使用,必须由子类复写其所有的抽象方法后,建立子类对象调用,如果子类只是覆盖了部分抽象方法,那么该子类还是一个抽象类

5. 抽象方法没有方法体,这样可以强制子类写符合本类的方法主体

抽象类和一般类没有太大不同,该如何描述事物就如何事物,只不过该事物中出现了看不懂的东西,这些不确定的部分,也是该事物的功能,需要明确出现,但是无法定义主体,通过抽象方法来表示。

抽象类比一般类多了抽象方法

抽象类不可以实例化

特殊:抽象类中可以不定义抽象方法,这样做仅仅是为了不让该类建立对象(也可以访问抽象类的静态普通方法)

接口(interface)

初期理解,可以认为是一个特殊的抽象类;当抽象类中的方法都是抽象的,那么该类可以通过接口的形式来表示。

Class 用于定义类

Interface 用于定义接口

Implements 实现接口关键词

接口定义时,格式特点:

1. 接口常见的定义:常量,抽象方法

常量:public static final

方法:public abstract

注意: 接口中的成员都是public

接口是不可以创建对象的,因为有抽象方法

需要被子类实现,子类对接口中的抽象方法全都覆盖后,子类才可以实例化,否则子类是一个抽象类

接口可以被类多实现,也是对多继承不支持的转换形式,java支持多实现(因为接口中的方法是没有方法体的,子类愿意怎么实现就怎么实现,假设两个接口中有两个方法一样,那么子类在实现的时候只需要实现他们中的一个就OK,两个interface中的方法都被复写了)

普通类可以同时继承普通类和多个接口,但是extends必须在implements之前

基本体系在类中实现,扩展功能可以在接口中实现

内部类

定义:将一个类定义在另一个类的里面,对里面的那个类就称为内部类(内置类,嵌套类)

访问特点:

内部类可以访问外部类中的成员,包括私有成员

而外部类想访问内部类中的成员必须要建立内部类的对象

之所以可以访问外部类中的成员,是因为内部类中持有一个外部类的引用,格式:外部类名.this

访问格式:

1.当内部类定义在外部类的成员位置上,而且非私有,可以在外部其他类中,直接建立内部类对象

格式:

外部类名.内部类名 变量名 = new 外部类().new内部类()

Outer.Inner in test = new Outer().new Inner();

2.当内部类在成员位置上,就可以被成员修饰符所修饰,如private将内部类在外部类中进行封装

Static:内部类具备static的特性

当内部类被static修饰后,只能访问外部类中的static成员,出现了访问局限

在外部其他类中,如何直接访问静态内部类呢

New.外部类名().内部类名().内部类方法名();//就是建立一个内部类的对象

在外部其他类中,如何直接访问static内部类的静态成员呢

外部类名.内部类名.成员名();

注意:当内部类中定义了静态成员,该内部类必须是静态的

当外部类中的静态方法访问内部类时,内部类也必须是静态的

内部类何时使用:

当描述事物时,事物的内部还有事物,该事物用内部类来描述。因为内部事物在使用外部事物的内容。

局部内部类:

内部类定义在局部时候:

1.不可以被成员修饰符修饰

2.还可以直接访问外部类中的成员,因为还持有外部类中的引用。但是只能直接访问它所在的局部中被final修饰的局部变量

匿名内部类

好处:简化书写

弊端:在多态的情况下不能访问子类对象特有成员,也不能强转;在父类方法过多的情况下,不使用匿名内部类,那样会使得代码阅读性非常差

1. 匿名内部类其实就是内部类的简写格式

2. 定义匿名内部类的前提条件:

内部类必须是继承一个类或者实现接口

3. 匿名内部类的格式:

new 父类或者接口(){复写父类或接口或自定义方法};

4. 其实匿名内部类就是一个匿名子类对象,而且这个对象有点胖,也可以理解为带内容的对象

5. 匿名内部类不可以访问外部非final修饰的变量

适用范围:

如果一个类的语句比较少,逻辑简单,而且不经常变动,这个时候可以使用匿名内部类

以下情况不建议使用:

如果一个类包含了很重要的逻辑,将来要经常修改,则该类就不应该当作匿名内部类来使用,否则会导致代码混乱

包(package)

Package必须放在程序的代码的第一行,分号结束

包存在的时候,类名就是包名.类名

包也是一种封装形式(类,函数等都是)

一个包中的类要被访问,必须要有足够大的权限,所访问的类要被public修饰

类公有后,被访问的成员也要公有

权限: public protected default private

同一个类中 ok ok ok ok

同一个包中 ok ok ok

子类 ok ok

不同包中 ok

总结:

不同包之间进行访问,被访问的包和包中的类和成员要被public修饰

不同包中的子类还可以访问父类中被protected修饰的权限修饰的成员

包与包之间可以使用的权限只有两种,public protected

为了简化类名的书写,使用一个关键词:import(导入包中类)

Import导入的是包中的类,通配符*表示当前目录下的所有类

但是建议不要写*,这样比较占用内存空间,要用那个类就导入那个类

导入不同包中相同类名的时候,必须加包名加以区分,否则报错

建议定义包名不要重复

Jar包

定义:就是java文档的压缩包

好处:

方便项目的携带

方便使用,只要在classpath设置jar包目录即可

数据库驱动,SSH框架等都是jar包体现的

创建jar包

• jar -cvf mypack.jar packa packb

查看jar包

• jar -tvf mypack.jar [>定向文件]

解压缩

• jar -xvf mypack.jar

自定义jar包的清单文件(做一个双击可执行的jar包)

• jar

–cvfm mypack.jar mf.txt packa packb

步骤:

1. 将java源文件编译成class字节码文件(通过指定包可以在编译的时候把class文件放到一个文件夹中)

2. 在cmd中执行命令 jar – cvfm jarname.jar mymanifest

pack

需要自己手动指定

Main-Class:(空格隔开) 包名.主类 (必须有回车换行)

把这个信息保存在一个文本文件中 在mymanifest中输入该文件的名字即可

异常

什么是异常:程序在运行时出现不正常的情况,导致程序不能正常工作

异常的由来:问题也是现实生活中的具体事物,也可以通过java的类的形式进行描述,并封装成对象

其实就是java对不正常情况进行描述后的对象体现

对于问题的划分分为两种:

一种是严重的问题,一种非严重的问题

对于严重的,java通过Error类进行描述

对于error一般不编写针对性的代码对其处理

对于非严重的,java通过Exception类进行描述

对于exception可以使用针对性的处理方式进行处理

无论error或者exception都具有一些共性内容

比如:不正常情况的信息,引发原因等

异常继承关系:

Throwable

------Error 严重错误

------Exception 非严重错误

-----------RuntimeException 运行时错误,不会在编译时期发现

异常处理步骤:

Try

{

需要检测的代码

}

Catch(异常类 变数)

{

处理方法

}

Finally

{

一定会执行语句

}

对捕获到的异常对象进行常见方法操作:

getMessage();//异常信息

toString();//异常名称:异常信息

printStackTrace();//异常名称,异常信息,异常出现的位置

//其实jvm默认的异常处理机制,就是在调用printStackTrace方法,打印异常的堆栈的跟踪信息

异常在子父类覆盖中的体现

1. 子类在覆盖父类时,如果父类的方法抛出异常,那么子类的覆盖方法只能抛出父类的异常或者该异常的子类

2. 如果父类方法抛出多个异常,那么子类在覆盖该方法时只能抛出父类异常的子集

3. 如果父类或者接口的方法中没有异常抛出,那么子类在覆盖方法时也不可以抛出异常,如果子类方法发生了异常,就必须要进行try处理,绝对不能抛出

对多异常的处理

1. 声明异常时,建议声明更为具体的异常,这样处理可以更具体

2. 对方声明几个异常,就应该有几个catch

3. 如果多个catch块中异常存在继承关系,父类异常catch放在子类后面

4. 建立在进行catch处理时,catch中一定要定义具体处理方式,不要简单定义一句

e.printStackTrace();也不要简单的就书写一句输出语句等

自定义异常

出现背景:因为项目中会出现特有的问题,而这些问题并未被java所描述并封装对象,所以对于这些特有的问题可以安装java对问题的封装思想,将特有的问题,进行自定义的异常封装

当函数内部出现了throw抛出异常对象,那么就必须要给对应的处理动作,要么在函数内try

catch处理,要么就throws抛出给调用函数处理--------一般情况下,函数内部出现异常,函数上需要声明

自定义异常信息

因为父类中已经把异常信息的操作都完成了

所以子类只是在构造时,用super调用父类的构造方法把子类的信息传到父类就OK

自定义异常注意

必须是自定义类继承Exception

继承Exception原因:

异常体系有一个特点:因为异常类和异常对象都被抛出

他们都具有可抛性,这个可抛性是Throwable这个体系中独有特点

只有这个体系中的类和对象才可以被throws和throw操作

如果该异常的发生,无法在继续进行运算,就让自定义异常继承RuntimeException

异常其他细节

Throws和throw区别

1.Throws使用在函数上

2.Throw使用在函数类

3.Throws后面跟异常类,可以跟多个,用逗号隔开

4.Throw后面跟对象

RuntimeException

如果该函数内容抛出该异常,函数上可以不用声明,编译一样通过(可处理可不处理的异常)

如果在函数上声明了该异常,调用者可以不用进行处理,编译也可以通过

-------之所以不用在函数声明,是因为不需要调用者处理,当该异常发生,希望程序停止。因为在运行时,出现了无法继续运算的情况,希望停止程序后,对代码修正

对于异常分为两种:

1. 编译时被检测的异常

2. 编译时不被检测的异常(运行时异常,runtimeexception以及它的子类)

Finally

定义:无论该异常是否执行都会执行finally代码块里面的内容主要用来释放资源

注意:

Catch是用于处理异常,如果没有catch就代表异常没有被处理,如果该类是检测时异常就必须声明

在异常处理的过程中:编译时异常(checked Exception)在向上抛的时候应该将其转换为运行时异常(unchecked

Exception),编译时异常抛给调用者没有太多的意义,会给上层程序造成麻烦,但是这里发生的异常应该上层调用者知道又不给上层程序带来麻烦,所以将其转换为运行时异常,上层程序可处理可不处理(注意:在向上抛运行时异常时,应该将本异常信息(catch到的异常)封装进行里面,异常链不能断)

示例:

Try{…}catch(Exception e){throw new RuntimeException(e);}

多线程

进程:是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元

线程:就是进程中的一个独立的控制单元,线程在控制进程的执行

一个进程中至少有一个线程

进程是一个静态的概念,进程执行指的是进程中的主线程开始执行,而非进程本身执行

Java VM

启动的时候就会有一个进程java.exe,该进程中至少有一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程

扩展:其实更细节说明虚拟机,JVM启动不止一个线程,还有负责垃圾回收机制的线程

封装(Encapsulation)

定义:是指隐藏对象的属性和实现细节,仅对外提供公有访问方式

好处:将变化隔离、便于使用、提高重用性、提高安全性

原则:将不需要对外提供的内容都隐藏起来

把属性都隐藏,提供公用方法对其访问

权限:

Public(公有)

Private(私有)

默认(什么都不写)

Protected(保护,在包中使用较多)

一个.java档里面,不能出现两个或者以上的public类或者接口

不能修饰方法中的变量,只能修饰成员(成员变量,成员方法)

继承(Inheritance)

(1).提高了代码的复用性

(2).让类与类之间产生了关系,有了这个关系,才有了多态的特效

注意:千万不要为了获取其他类的功能简化代码而继承,必须是类与类有所属关系才可继承。所属关系 is

a

(3).java语言中:java只支持单继承,不支持多继承(接口支持)

多继承容易带来安全隐患:当多个父类中定义了相同功能,但功能内容不同时,子类对象不确定运行那个

Java保留了这种机制,并用另一种体现形式来表现,叫多实现也就是接口

Java支持多层继承,也就是一个继承体系

如何使用一个继承体系中的功能呢?

要想使用体系,先查阅体系父类的描述,因为父类中定义的是该体系中共性功能。通过了解共性功能,就可以知道该体系的基本功能,那么这个体系基本可以使用了;那么在具体调用时,要创建最子类对象,为什么?一是因为有可能父类不能创建对象(如抽象类),二是创建子类对象可以使用更多功能,包括基本的也包括特有的。

简单一句话:查阅父类功能,创建子类对象使用功能。

聚集: has a

聚合:球队中的球员

组合:人和人的手

多态

定义:可以理解为事物存在的多种体现形态

1.

多态的体现

父类的引用指向了自己子类的对象

父类的引用也可以接收自己的子类对象

2.

多态的前提

必须是与类之间有关系,要么继承,要么实现

通常还有一个前提,存在覆盖

3.

多态的好处

多态的出现大大的提高程序的扩展性

4.

多态的应用

通过一个函数调用这个继承体系中的某一个方法

5.

多态在代码中的特点(多态使用的注意事项)

成员函数的特点:

在编译时期:参阅引用型变量所属的类中是否有调用的方法,如果有,编译通过,否则失败

在运行时期:参阅对象所属的类中是否有调用方法。

简单结论:成员函数在多态调用时,编译看左边,运行看右边

在多态中成员变量的特点:

无论编译和运行,都参阅左边(引用类型变数所属的类)

在多态中,静态成员函数的特点:

无论编译和运行都参考左边

6.

多态的弊端

提高了扩展性,但是只能使用父类的引用访问父类的成员(不能直接通过父类引用访问子类特有成员)

理论概念

在实际开发中其实就是

找对象----建立对象----使用对象----维护对象之间关系

先有对象还是先有类:答案不确定,对于计算机而言是先有类再有对象,而实在生活中我们是先看到对象才会去描述这个对象(描述就是提取对象中的共性内容,对具体的抽象)

什么是类:类是一个静态属性和动态可执行操作的结合(对现实生活中的事物的描述)

对象:就是这类事物,实实在在存在的个体(类的实例化)

映像到java中,描述就是class定义的类,具体对象就是对应java在堆内存中用new建立的实体。

设计模式

解决某一类问题最行之有效的方式

Java中有23种设计模式

单例设计模式:

让一个类在内存只有一个对象

保证对象的唯一性:

1. 为了避免其他程序过多的建立该类对象,所以必须禁止其他程序建立该类对象(构造方法私有化)

2. 但是其他程序有可以访问到该类对象,只好在本类中,自定义一个物件(private static A aa = new A();)

3. 为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式(提供一个公有方法返回我们aa对象的地址)

两种方式:

1. 饿汉式:就是先初始化对象,一般开发中常用

2. 懒汉式(面试常考):对象是方法被调用时,才初始化,也叫对象的延时加载(特点),只有调用提供给用户的方法时候才在内存中new对象

---------------在实际开发中区别不大,因为要创建这个对象肯定要用,不用就不用创建,一般使用饿汉式,因为比较直观安全(这里要涉及到CPU的知识,CPU在某一个时刻只能处理一个程序,多线程同步的问题)!懒汉式在多线程中存在安全隐患,可以使用同步函数来解决,但是会影响程序效率,解决这个问题可以通过双重判断来实现。

自定义线程

通过对API的查找,java已经提供了对线程这类事物的描述,Thread类。

第一种方式

一个类继承Thread并重写run方法(目的就是将自定义代码存储在run方法中,让线程运行),调用start方法,start有两个作用:启用线程 调用run方法

发现每一次运行结果都不同:因为多个线程都获取CPU的执行权,CPU执行到谁,谁就运行,明确一点,在某一个时刻,只能有一个程序在运行,多核除外;CPU在做着快速的切换,以达到看到去是同时运行的效果,我们可以形象把多线程的运行行为看作在互相抢夺CPU执行权,这就是多线程的一个特效:随机性,谁抢到谁执行,至于执行多长CPU说了算!

为什么要覆盖run方法呢?

Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码,该存储的功能就是run方法;也就是说Thread类中的run方法用于存储线程要运行的代码。

局部变量在每一个线程区域当中都有独立的一份

线程类中常见的几个方法:

Static Thread currentThread();获取当前线程物件

getName();获取线程名称

设置线程名称:SetName或者构造函数

创建线程的第二种方式,实现runnable接口

步骤:

1. 定义类实现runnable接口

2. 覆盖runnable接口中的run方法

将线程要运行的代码存放在该run方法中

3. 通过Thread类创建线程对象

4. 将runnable接口的子类对象作为实际参数传递给Thread类的构造函数

Why?因为,自定义的run方法所属对象是runnable接口的子类对象,所以要让线程去指定对象的run方法,就必须明确该run方法所属对象

5. 调用Thread类的start方法开启线程并调用runnable接口的子类run方法

实现方式和继承方式有什么区别呢?

实现方式好处:避免了单继承的局限性,在定义线程时,建议使用实现方式

两种方式的区别:

继承Thread:线程代码存放在Thread子类run方法中

实现runnable:线程代码存放在接口的子类的run方法中

线程的同步:(同步有两种表现形式:同步代码块和同步函数)

java对于多线程的安全问题提供了专业的解决方式就是->同步代码块

synchronized(对象)//对象就是锁

{

需要被同步的代码

}

对象如同锁,持有锁的线程可以在同步中执行,没有持有锁的线程即使获取了CPU的执行权也进不去,因为没有锁

形象的说明就像:火车上的卫生间

Synchronized(类对象名aa)的含义:判断aa是否已经被其他线程霸占,如果发现已经被其他线程霸占,则当前线程陷入等待中,如果发现aa没有被其他线程霸占,则当前线程霸占住aa对象,并执行同步代码块,在当前线程执行同步代码块时,其他线程将无法在执行同步代码块中的代码,当前对象执行完同步代码块后,会自动释放aa对象的霸占,此时其他线程会互相竞争对aa的霸占,最终CPU会选择其中的某一个线程执行

最终导致的结果是:一个线程正在操作某一个资源的时候,将不允许其他线程操作操作该资源,即一次只允许一个线程该资源

同步的是实现(synchronized(监视器)):

Synchronized语句会有一个监视器,也叫锁,锁有两种状态(锁旗标),分别是零和一,默认为1,当有线程进入锁的时候,锁的标志位自动变为0,这个时候其他线程则不能进入;当该线程执行完同步中的代码时,监视器又会自动将锁旗标改为1,等待下一个线程的进入!

同步的前提:当使用了synchronized后没有达到效果,就应该看看条件是否满足

1.必须要有两个或者两个以上的线程

2.必须是多个线程使用同一个锁

必须保证同步中只有一个线程在运行,这个也是使用同步的目的

非静态的同步函数使用的是this锁,静态的同步函数使用的是*.class锁

好处:解决了多线程的安全问题

弊端:多个线程都需要判断锁,较为消耗资源

如何找问题

1.明确那些代码是多线程运行代码

2.明确共享资料

3.明确多线程运行代码中那些语句是操作共享数据的

同步函数

同步函数用的是那一个锁呢?

函数需要被对象调用,函数都一个所属对象的引用this,所以同步函数使用的锁是this

如果同步函数被静态修饰后,使用的锁是什么?

通过验证,发现不再是this,静态中没有this

静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象

类名.class 该对象的类型是Class

静态的同步方法,使用的锁是该方法的所在类中的字节码文件对象。

死锁:

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

导致死锁的根源在于不适当地运用“synchronized”关键词来管理线程对特定对象的访问。“synchronized”关键词的作用是,确保在某个时刻只有一个线程被允许执行特定的代码块,因此,被允许执行的线程首先必须拥有对变量或对象的排他性的访问权。当线程访问对象时,线程会给物件加锁,而这个锁导致其它也想访问同一物件的线程被阻塞,直至第一个线程释放它加在对象上的锁。

线程之间的通信

定义:其实就是多个线程在操作同一个资源,但是操作不同(InputOutputDemo.java)

线程等待机制

Wait; 等待

Notify(); 唤醒线程池中的第一个

notifyAll(); 唤醒线程池全部线程

都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用同步中,因为只有同步才具有锁。

为什么这些操作线程的方法要定义在object类中呢?

因为这些方法在操作同步中线程时,都必须要表示它们所操作线程持有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒,不可以对不同锁中的线程进行唤醒。

也就是说,等待和唤醒必须是同一个锁

而锁可以是任意对象,所以可以被任意对象调用的方法定义在object类中

对于多个生产者和消费者为什么要定义while判断标记?

原因:让被唤醒的线程再一次判断标记

为什么要使用notifyAll?

原因:因为需要唤醒对方线程,notify容易出现只唤醒本方线程的情况,导致程序中的线程都等待。

If notify 只能用于一个线程生产一个线程消费

While notifyAll 用于多个线程生存和多个线程消费

JDK1.5 中提供了多线程升级解决方案

将同步Synchronized替换成了显示Lock操作

将object中的wait,notify,notifyall,替换成了condition物件

该对象可以Lock锁,进行获取

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值