java基础问题,java基础
java基础问题,java基础
java概述
- jdk:java development kit ,java的开发和运行环境,java的工具和jre。
- jre:java runtime enviroment,java运行的时候需要的所需的类库和jvm
- 配置环境变量
classpath是什么?它的作用是什么?
告诉编译器在这个package所在的位置。主要放一些java的主要的lib
path是什么?它的作用是什么
path作用是指定命令搜索路径。在命令行下面执行命令如javac 编译java程序时。执行的命令如javac/java/javadoc等待。 - javac命令和java命令做什么事情呢?
java分两个部分的:一个是编译,一个是运行。
javac:负责的是编译的部分,当执行javac时,会启动java的编译程序。对指定扩展名的.java文件进行编译。生成了jvm可以识别的字节码文件。也就是class文件,也就是java的运行程序。
java:负责运行的部分,也启动jvm.加载运行时所需的类库,并对class文件进行执行。
java语法基础
- 标示符
- 变量的作用域和生存期
变量的作用域:
从变量定义的位置开始,到该变量所在的那对大括号结束。
生命周期:
变量从定义的位置开始就在内存中活了。
变量到达它所在的作用域的时候就在内存中消失。 - 数据基本类型:
byte short int long float double char boolean
除了string 其它都有包装类 -
运算符号
- & | ^ ! && ||
^:异或:
两边结果一样,就为false。
两边结果不一样,就为true。 - 位运算符:用于操作二进制位的运算符
运算符 运算 <<
左移 >>
右移 >>>
无符号右移[只对无符号有影响] &
与运算 ` ` ^
异或运算 ~
反码 - & | ^ ! && ||
-
重载的定义:一个类中,如果出现了两个或者两个以上的同名函数,只要他们的参数的个数活着参数的类型不同,即可称之为该函数重载了。
重写的定义:父类与子类之间的多态性,对父类的函数进行重新定义。如果在子类中定义某方法与其父类有相同的的名称和参数,我们说该方法被重写。 -
java内存管理。
java运行时数据区域
java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和摧毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。java虚拟机所管理的内存如下:内存 aa 方法区 aa 虚拟机栈 bb 本地方法栈 cc 堆 dd 程序计算器 ff -
程序计数器
程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能。
为了现场切换后能恢复到正确的执行位置,每条现场都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。 -
java虚拟机栈
与程序计数器一样,java虚拟机栈也是线程私有的,它的生命周期与线程相同。java方法执行的内存模式,每一个方法被执行时会同时创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。
其中64位长度的long和double类型的数据会占用2个局部变量空间,其余的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定,在方法运行期间不会改变局部变量表的大小。 - 本地方法栈
本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机为虚拟机执行java方法服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言,使用方式与数据结果并没有强制规定。有的是和为一个了。 - java堆
对于大多数的应用,java堆是java虚拟机所管理的内存中最大的一块。java堆是被所有线程共享的一块内存区域。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
java堆是垃圾收集器管理的主要区域,因此很多时候也被称作“GC堆”。如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法。所以java堆中还可以细分为:新生代和老年代;再细致一点的油eden空间、from Survivor空间、To survivor空间等。如果从内存分配的角度。从内存分配的角度看,程共享的java堆中可能划分出多个线程私有的分配缓冲区。
java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。通过-Xmx和-Xms控制。 - 方法区
方法区和java堆一样,是各个线程共享的内存区域,它用于存储已被须立即加载的类信息、常量、静态变量、即时编辑器后的代码等数据。虽然java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却又一个别名叫做Non-heep。
很多人愿意把方法区称为永久代。java对于这个区域的限制非常的宽松,不执行垃圾回收机制。 - 运行时常量池
运行时常量池是方法区的一部分,用于存放编译期生产的各种字面量和符号引用,这部分将在累加载后存放方法区的运行时常量池中。 - 对象访问
在java语言中,对象访问时如何进行的?对象访问在java语言中无处不在,是最普通的程序行为,但即使最简单的访问,也会却涉及java栈、java堆、方法区这三个最重要的内存区域之间的关联关系:
Object object =new Object();
假设这句代码出现在方法体中,那么“Object obj”这部分的语义讲会反映到java栈的本地变量表中,作为一个reference类型数据出现。而“new Object()”这部分的语义讲会反映到java堆中,形成一块存储了Object类型所有实例数据值的结构化内存,根据具体类型以及虚拟机实现的对象内存布局的不同,这块内存的长度是不固定的。另外,在java堆中还必须包含查找到对象类型数据的地址信息,这些类型数据则存储在方法区中。
由于reference的类型在java虚拟机规范里面只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位,以及访问到java堆中的对象的具体位置,因此不同虚拟机实现的对象访问方式会有所不同,主流的访问方式有两种:使用句柄和直接指针。
如果使用句柄访问方式,java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。
如果使用的是直接指针访问方式,java堆对象的布局中就必须考虑如何放置访问类型的相关信息,reference中直接存储的就是对象地址。
这两种对象的访问各有优势,使用句柄访问方式的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要被修改。使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在java中非常频繁,因此这类开销积小成多后也就是一项非常可观的执行成本。
-
java程序编译和运行的过程:
-
编译。创建完源文件之后,程序会先被编译为.class文件。java编译一个类时,如果这个类所依赖的类还没有被编译,编译器就会先编译这个被依赖的类,然后引用,否则直接引用,这个有点像make。如果java编译器在指定目录下找不到该类所其依赖的类.class文件或者.java源文件的话,编译器会报“can’t find symbol”的错误。
编译后的字节码文件格式主要分为两部分:常量池和方法字节码。常量池记录的是代码出现过的所有token(类名、成员变量名等等)以及符号引用(方法引用,成员变量引用等等);方法字节码放的是类中各个方法的字节码。下面显示两张图片:
-
运行:java类运行的过程大概分为两个过程:1 类的加载 2类的执行。需要说明的是:jvm主要在程序第一次主动使用类的时候,才会去加载该类。也就是说,jvm并不是在一开始就把一个程序就所有的类都加载到内存中,而是到不得不用的时候才把它加载进来,而且只加载一次。
下面是程序进行的详细步骤:- 在编译好java程序得到MainApp.class文件后,在命令行上敲java AppMain。系统就会启动一个jvm进程,jvm进程从classpath路径中找到一个名为AppMain.class的二进制文件,将MainApp的类信息加载到运行时数据区的方法区内,这个过程叫做MainApp类的加载。
- 然后jvm找到AppMain的主函数入口,开始执行main函数。
- main函数的第一条命令是Animal animal=new Animal(“Puppy”);就是让jvm创建一个Animal对象,但是这时候方法区中没有Animal类的信息,所以JVM马上加载Animal类,把Animal类的类型信息放到方法区中.
- 加载完Animal类之后,java虚拟机做的第一件事情就是在堆区中为一个新的额Animal实例分配内存,然后调用构造函数初始化Animal实例,这个Animal实例持有着指向方法区的Animal类的类型信息(其中包含有方法表,java动态绑定的底层实现)的引用。
- 当使用animal.printName()的时候,jvm根据animal引用找到Animal对象,然后根据Animal对象持有的引用定位到方法区Animal类的类型信息的方法表,获得printName()函数的字节码的地址。
- 开始运行printName()的函数。
面对对象
类
匿名对象使用场景:
- 当对方法只执行一次调用的时候,可以使用匿名对象。
- 当对象对成员进行多次调用时,不能使用匿名对象。必须给对象起名字。
类中怎么没有定义主函数?
主函数的解释:保证所有类的独立运行,是程序的进入,被jvm调用。
构造函数:用于对对象进行初始化,是给与之对应的对象进行初始化,它具有针对性,函数中的一种。
- 该函数的名称和所在的类的名称相同。
- 不需要定义返回值的类型。
- 该函数没有具体的返回值。
一个类中,可以有多个构造函数,因为它们的函数名称相同,所以只能通过参数列表来区分。所以,一个类中如果出现多个构造函数。它们的存在是以重载体现。
构造代码块和构造函数有什么区别?
构造代码块:是给所有的对象进行初始化,也就是说,所有的对象都会调用一个代码块。只要对象一建立。就会调用这个代码块。
构造函数:是给与之对应的对象进行初始化。它具有针对性。
public class Dev{
static{
system.out.println("静态代码块");
}
{
system.out.println("构造代码块");
}
public Dev(){
system.out.println("我是一个无惨构造方法")
}
public Dev(String name){
System.out.println("我是一个带有参数的构造方法")
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
1. 执行顺序:静态代码块>main方法>构造代码块>构造方法。其中静态代码块只执行一次。构造代码块在每次创建对象是都会执行。
2. 静态代码块的作用:比如我们在调用c语言的动态库时会把.so文件放在此处。
3. 构造代码块的功能:(可以把不同构造方法中相同的共性的东西写在它里面)
Person p=new Persion()
创建一个对象都在内存中做了什么事情?
- 先将硬盘上指定位置的Person.class文件加载进内存。
- 执行main方法时,在栈内存中开辟了main方法的空间(压栈-进栈),然后在main方法的栈区分配一个变量p。
- 在堆内存中开辟一个实体空间,分配了一个内存首地址值。new
- 在该实体空间中进行属性的空间分配,并进行了默认初始化。
- 对空间中的属性进行显示初始化。
- 进行实体的构造代码块初始化。
- 调用该实体对应的构造函数,进行构造函数初始化。
- 将首地址赋值给p,p变量就引用了该实体。(指向了该对象)
封装(面对对象特征之一):是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
好处:将变化隔离;便于使用;提供重用性;安全性;
用this调用构造函数,必须定义在构造函数的第一行。因为构造函数是用于初始化的,所以初始化动作一定要先执行。否则编译失败
static
特点:
- static 变量
按照是否静态的对类成员变量进行分类可分两种:一种是被static修饰的变量,叫静态变量或类变量;另一种是没有static修饰的变量,叫实例变量。两者的区别是:
对于静态变量在内存中只有一个拷贝,jvm只为静态分配一次内存,在加载类的过程完成静态变量的内存分配,可用类名直接访问,当然也可以通过对象来访问。
对于实例变量,没创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中多个拷贝,互不影响。 - 静态方法
静态方法可以直接通过类名调用,任何的实例也都可以调用,因此静态方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法,只能访问所属类的静态成员变量和成员方法。因为实例成员与特定的对象关联!
因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract. - static代码块
static代码块,是在累中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体中,jvm加载类时会执行这些静态的代码块,如何static代码块有多个,jvm将按照他们在类中出现的先后顺序依次执行它们,每个代码块只会被执行依次。
final
根据程序上下文环境,java关键字final有这是无法改变或者终态的含义,它可以修饰非抽象类,非抽象类成员方法和变量。
final类不能被继承,没有子类,final类中的方法默认是final的
final方法不能被子类的方法覆盖,但可以被继承
final成员变量表示常量,只能被赋值一次,赋值后不再改变。
final不能用于修饰构造方法。
继承(面向对象特征之一)
java中对继承,java只支持单继承。java虽然不直接支持多继承,但是可实现多接口。
- 成员变量 :
This:代表是本类类型的对象引用。
Super:代表是子类所属的父类中的内存空间引用。 - 成员函数 :
当子付类中出现一模一样的方法是,建立子类对象会运行子类的方法。好像父类中的方法被覆盖一样。所以这种情况,是函数的另外一个特性:重写。 - 构造函数
发现子类构造函数运行时,先运行了父类的构造函数。为什么呢?
子类的所有构造函数中的第一行,其实都有一条隐身的语句super();
注意:子类中所有的构造函数都会默认访问父类中的空参数的构造函数,因为每一个子类构造内每一行都有默认的语句super();
如果父类中没有空参数的构造函数,那么子类的构造函数内,必须通过super语句指定要访问的父类中的构造函数。
如果子类构造函数中用this来指定调用子类自己的构造函数,那么被调用的构造函数也一样会访问父类中的构造函数。
final特点:
- 这个关键字是一个修饰符,可以修饰类,方法,变量。
- 抽象方法只定义方法什么,并不定义方法实现。
- 抽象类不可以被创建对象(实例化)
- 只有通过子类继承抽象类并覆盖抽象类中的所有抽象方法后,该子类才可以实例化。否则,该子类还是一个抽象类。
抽象类的细节:
- 抽象类中是否有构造函数?有。用于给子类对象进行初始化。
- 抽象类中是否可以定义非抽象方法?可以。其实,抽象类和一般类在定义上,都是需要定义属性和行为的。只不过,比一般类多了一个抽象函数。而且比一般类少了一个创建对象的部分。
- 抽象关键字abstract和那些不可以共存?final,private,static.
- 抽象类中可不可以不定义抽象方法?可以,抽象方法目的仅仅为了不让该类创建对象。
接口
- 是用关键字interface定义的
- 结果中包含的成员,最常见的有全局常量、抽象方法。 成员变量:pubilc static final
成员方法:public abstract
interface Inter{
public static final int x=3;
public abstract void show();
} - 接口中有抽象方法,说明接口不可以实例化。接口的子类必须实现了接口中所有的抽象方法后,该子类才可以实例化。否则,该子类还是一个抽象类。
- 类与类之间存在继承关系,类与接口中间存在的是实现关系。 继承用extends;实现用implements;
- 接口和类不一样的地方,就是接口可以被多实现,这就是多继承改良后的结果。java将多继承机制通过多现实来实现。
- 一个类在继承另一个类的同时,还可以实现多个接口。所以接口的出现避免了单继承的局限性。还可以将类进行功能的扩展。
抽象类与接口:
抽象类:一般用于描述一个体系单元,将一组共性内容进行抽取,特点:可以在类中定义抽象内容让子类实现,可以定义非抽象内容让子类直接使用。它里面定义的都是一些体系中的基本内容。
接口:一般用于定义对象的扩展功能,是在继承之外还需这个对象具备的一些功能。
抽象类和接口的区别:
- 抽象类只能被继承,而且只能单继承。
接口需要被实现,而且可以多实现。 - 抽象类中可以定义非抽象,子类可以直接继承使用。
接口中都是抽象方法,需要子类去实现。 - 抽象类使用的是is a关系。
接口使用的like a关系。 - 抽象类的成员修饰可以自定义。
接口中的成员修饰符是固定。全都是public
多态
多态:函数本身就具备多态性,某一种事物有不同的具体的体现。
体现:弗雷引用或者接口的引用指向了自己的子类对象。父类可以调用子类中覆写过的。
多态的好处:提高了程序的扩展性。继承的父类或者接口一般是类库中的东西,只有通过子类覆写要改变的某一个方法,这样在通过将弗雷的应用指向子类的实例区调用覆写过的方法就行了。
多态的弊端:当父类引用指向子类对象时,虽然提高了扩展性,但是只能访问父类中具备的方法,不可以访问子类中特有的方法。
多态的前提:
- 必须有关系,比如继承、或者实现。
- 通常会有覆盖操作。
具体方法:
- boolean equals(Object obj): 用于比较两个对象是否相等,其实内部比较的就是两个对象地址。
- String toString():将对象变成字符串;默认返回的格式。
- Class getClass():获取任意对象时的所属字节码文件对象。
- int hashCode():返回该对象的哈希码值。支持此方法是为了提高哈希表的性能。
内部类:
内部类直接访问外部类成员,用自己的实例对象;
外部类访问内部类要定义内部类的对象;
异常
- Error 错误,一般情况下,不编写针对性的代码进行处理,通常是jvm发生的,需要对程序进行修正。
- Exception 异常,可以有针对性的处理方式。
throw和throws区别:
throws是用来声明一个方法可能排除的所有异常信息,则throw则是指抛出的一个具体的异常类型。此外throws是将异常但是不处理,而是将异常往上传,谁调用我就交给谁处理。
throws用于抛出异常类,后面跟的异常类名,可以跟多个,用逗号隔开。throws 用在函数上。
多线程
返回当前线程的名称:Thread.currentThread().getName().
线程要运行必须通过类中的指定的方法开启。start方法。(启动后,就多了一条执行路径)start方法:1启动了线程 2 让jvm调用run方法。
Thread类中run()和start()方法的区别:
start():用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用thread类的start方法启动一个线程,这时此线程处于就绪状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,run方法运行结束,此线程随即终止。
run():run()方法只是类的一个普通方法而已,如果直接调用run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。
总结:start()方法最本质的功能是从cpu中申请另一个线程空间来执行run()方法中的代码,它和当前的线程是两条线,在相对独立的线程空间运行,也就是说,如果你直接调用线程对象的run()方法,当然也会执行,但那时,在当前线程中执行,run()方法执行完成后继续执行下面的代码。而调用start()方法后,run方法的代码会和当前线程并发或并行执行。所以请记住一句话:调用线程对象的run方法不会产生一个新的线程,虽然可以达到相同的执行结果,但执行过程和执行效率不同。
创建线程的第一种方式:继承Thread,由子类复写run方法。
- 定义类继承Thread类;
- 目的是复写run方法,将要让线程运行的代码都存储到run方法中;
- 通过创建Thread类的子类对象,创建线程对象。
- 调用线程的start方法,开启线程,并执行run方法。
线程状态:
- 被创建:start()
- 运行:具备执行资格,同时具备执行权;
- 冻结:sleep(time).wait()-notify()唤醒;线程释放了执行权,同时释放执行资格;
- 临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权。
- 消亡:stop()
创建线程的第二种方式:实现一个接口Runnable。
- 定义类实现runnable接口。
- 覆盖接口中的run方法
- 通过Thread类创建线程对象。
- 将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数
- 调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。
Ticket t=new Ticket();
/**
直接创建Ticket对象,并不是创建线程对象。
因为创建对象只能通过new Thread类,或者new Thread类的子类才可以。
所以最终想要创建线程,既然没有了Thread类的子类,就只能用Thread类。
**/
Thread t1=new Thread(t);//创建线程
t1.start();
为什么要有Runnable接口的出现?
- 因为实现Runnable接口可以避免单继承的局限性。
- 实现Runnable接口可以避免单继承的局限性。所以Runnable接口将线程要执行的任务封装成了对象。
synchronized关键字
- 当两个并发线程访问同一个线程对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另外一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
- 然后,当一个线程访问object的一个synchronzied同步代码时,另一个线程仍然可以访问该object中的非synchronized同步代码块。
- 尤其关键的是,当一个线程访问object的一个synchronized同步代码块时,其他线程对object中所有其它synchronized同步代码的访问将被阻塞。
- 第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象有同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞,
- 以上规则对其它对象锁同样适用。
synchronized关键字
synchronzied关键字,它包括两种用法:synchronzied方法和synchronzied方法
public synchronized void accessVal(int newVal)
- synchronized方法控制对类成员变量的访问:每个类实例对应一把锁,每个synchronized方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后该阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为synchronzied的成员函数中至多只有一个处于可执行状态,从而有限避免了类成员变量的访问冲突。
在java中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为synchronzied,以控制其对类的静态成员变量的访问。
synchronized方法的缺陷:若将一个大的方法声明为synchronzied将会大大影响效率,典型地,若将线程类的方法run声明为synchoronzied,由于线程的整个生命期内它一直在运行,因此将导致它对本类任何synchronzied方法的调用都永远不会成功。当然我么可以通过将访问类成员变量的代码放到专门的方法中,将其声明为synchronzied,并在主方法中调用来解决这一问题,但是java为我们提供了更好的解决方法,就就是synchoronzied块。 - synchronized块:通过synchronzied关键字来声明synchronzied块。语法如下:
synchronized(synObject){
//允许访问控制的代码
}
synchronized块是这样一个代码块,其中代码必须获得对象synObject的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。
对synchoronzied的一些理解- 当两个并发线程访问同一个对象object中的这个synchoronzied同步代码块时,一个时间内只能有一个线程得到执行,另外一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
- 然而,当前一个线程访问object的synchoronzied同步代码块时,另外一个线程仍然可以访问该object中的非synchoronzied同步代码块。
- 尤其关键的是,当一个线程访问object的一个synchoronzied同步代码块时,其它线程对object中所有其它synchoronzied同步代码块的访问被阻塞。
- 第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchoronzied同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
- 以上规则对其它对象锁同样适用。
解决安全问题的原理:
只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行就可以解决这个问题。
如果保障共享数据的线程安全呢?
格式:
synchoronzied(对象){//任意对象都可以,这个对象就是共享数据。
需要被同步的代码
}
同步的第二种表现形式:
同步函数:其实就是将同步关键字定义在函数上,让函数具备了同步性。
同步函数是用的哪个锁呢?
通过验证,函数都有自己所属的对象this,所以同步函数所使用的锁就是this锁。
同步代码块和同步函数的区别?
同步代码块使用的锁可以是任意对象。
同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。
懒汉式,延迟加载方式。
class Singel{
private static Single s=null;
private Single(){}
public static Single getInstance(){
if(s==null){
synchronized(Single.class){
if(s==null){
s=new Single();
}
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
等待唤醒机制:
wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。
notify:唤醒线程池中某一个等待线程。
norifyAll:唤醒的是线程池中的所有线程。
注意:
- 这些方法都需要定义在同步中
- 因为这些方法必须要表示所属的锁。
你要知道A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒。 - 这三个方法都定义在Object类中,为什么操作线程的方法定义在Object类中?
因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。
wait和sleep区别:分析这两个方法;从执行权和锁上来分析:
wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。
sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)
**wait:线程会是否执行权,而且线程会释放锁。
sleep:线程会释放执行权,但不是不释放锁。**
线程的停止:通过stop方法就可以停止线程,但是这个方式过时了。
怎么结束run方法?
一般run方法里肯定定义循环。所以只要结束循环即可。
第一种方式:定义循环的结束标记。
第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。
interrupt():中断线程。
setPriority(int newPriority)更改线程的优先级。
getPriority()返回线程的优先级。
toString():放回该坚持的字符串表示形式,包括线程名称、优先级和线程组。
Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。
setDaemon(true):将该线程标记为守护线程或用户线程。将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,java虚拟机退出。该方法必须在启动线程前调用。
join:临时加入一个线程的时候可以使用join方法。
当A线程执行到了B线程的join方式。A线程处于冻结状态,释放了执行权,B开始执行。A什么时候执行呢?只有当B线程运行结束后,A才从冻结状态恢复运行状态执行。
Lock的出现替代了同步:lock.lock();…lock.unlock();
lock接口:多线程在JDK1.5版本升级时,推出一个接口Lock接口。
解决线程安全问题使用同步的形式,其实最终使用的都是锁机制。
到了后期版本,直接将锁封装成了对象。线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。
集合框架
集合框架,用于存储数据的容器。
对于集合容器,有很多种。因为每一个容器的自身特点不同,其实原理在与每个容易的内部数据结构不同。
集合容器在不断向上抽取过程中。出现了集合体系。
List | 有序(元素存入集合的顺序和去除的顺序一致),元素都有索引。元素可以重复 |
---|---|
ArrayList | 底层的数据结果是数组,线程不同步,ArrayList替代了Vector,查询元素的速度非常快。 |
LinkedList | 底层的数组结构是链表,线程不同步,增删元素的速度非常快。 |
Vector | 底层的数据结构就是数组,线程同步,Vector无论查询和增删都巨慢。 |
可变长度数组的原理:
当元素超出数组长度,会产生一个新数组,将原数组的数据复制到新数组中,在将新的元素添加到新数组中。
ArrayList:是按照原数组的50%延长。构造一个初始容量为10的空列表。
Vector:是按照原数组的100%延长。
Set | Set接口中的方法和Collection中方法一致的。Set接口取出方式只有一种,迭代器 |
---|---|
HashSet | 底层数据结果是哈希表,线程是不同步的。无序,高效;HashSet集合保证元素唯一性;通过元素的hashCode方法,和equals方法完成的。当元素的hashCode值相同时,才继续判断元素的equal是是否为true。 |
LinkedHashSet | 有序,hashset的子类 |
TreeSet | 对Set集合中的元素的进行指定顺序的排序。不同步。TreeSet底层的数据结构就是二叉树。 |
对于ArrayList集合,判断元素是否存在,或者删元素底层依据都是equals方法。
对于HashSet集合,判断元素是否存在,或者删除元素,底层依据的是hashCoede方法和equals方法。
Map | Map集合 |
---|---|
Hashtable | 底层是哈希表数据结构,是线程同步的。不可以存储null键,null值。 |
HashMap | 底层是哈希表数据结构,是线程不同步的。可以存储null键,null值。替代了hashtable。 |
TreeMap | 底层是二叉树结构,可以对map集合中的键进行制定顺序的排序 |
Map集合存储和Collection有着很大不同:
Collection一次存一个元素;Map一次存一对元素。
Collection是单列集合;Map是双列集合。
Map中的存储的一对元素:一个是键,一个是值,键与值之间有对应关系。
想要获得map中的所有元素:
原理:map中是没有迭代器的,Collection具备迭代器,主要将map集合转成set集合,可以使用迭代器。之所以转成set,是因为map集合具备这键的唯一性,其实set集合就来自于map,set集合底层其实用的就是map的方法。
取出map集合中所有元素的方式一:keySet()方式
Set keySet=map.keySet();
Iterator it=keyset.iterator();
while(it.hasNext()){
Object key=it.next();
Object value=map.get(key);
system.out.printlin(key+":"+value)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
取出map集合中所有元素的方式二:entrySet()方法。
Set entrySet=map.entrySet();
Iterator it=entrySet.iterator();
while(it.hasNext()){
Map.Entry me=(Map.Entry)it.next();
System.out.println(me.getKey+":"+me.getValue())
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
将非同步集合转成同步集合的方法:Collections中的XXX synchronziedXXX(XXX);
List synchronizedList(list);
Map synchronizedMap(map);
原理:定义一个类,将集合所有的方法加同一把锁后返回。
Collection 和Collections的区别:
Collections是一个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查询,排序、体会、线程安全化等操作。
Collection是个java.util下的接口,它是各种集合结构的父接口,继承与它的结构主要是 List,提供了关于集合的一些操作,如插入、删除、判断一个元素是否其成员、遍历等。
自动拆装箱:java中数据类型分为两种:基本数据类型 引用数据类型(对象)
在java程序中所有的数据都需要当做对象来处理,针对8中基本数据类型提供了包装类。
int –> Integer
byte –> Byte
short –> Short
long –> Long
char –> Character
double –> Double
float –> Float
boolean –> Boolean
反射技术
反射技术:其实就是动态加载一个指定的类,并获取该类中的所有的内容。并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单来说:反射技术可以对一个类进行解剖。
放射的好处:大大的增强了程序的扩展性。
反射的基本步骤:
- 获得class对象,就是获得指定的名称的字节码文件对象。
- 实例化对象,获得类的属性、方法和构造函数。
- 访问属性、调用方法、调用构造函数创建对象。
获取这个class对象,有三种方式:
- 通过每个对象都具备的方法getClass来获取。弊端:必须要创建该类对象,才可以调用getClass方法。
- 每一个数据类型都有一个静态的属性class。弊端:必须要先声明该类。
前两种方式不利于程序的扩展,因为都需要在程序使用具体的类来完成。 - 使用的class类中的方法,静态的forName方法。
指定什么类名,就获取什么类字节码文件对象,这种方式的扩展性最强,只要将类名的字符串传入即可。
//根据给定的类名来获取用于类加载
String classname=“cn.itcast.reflect.Person”;//来自配置文件
Class clazz=Class.forName(classname);//此对象代表Person.class
//如果拿到了对象,不知道是什么类型 用于获得对象的类型
Object obj=new Person();
Class clazz1=obj.getClass();//获得对象具体的类型
//如果名曲获得某个类的class对象, 主要用于传参
Class clazz2=Person.class
反射的用法:
- 需要活儿java类的各个组成部分,首先需要获得类的class对象,获得class对象的三种方式:
class.forName(classname) 用于做类加载
object.getClass() 用于获得对象的类型
类名.class 用于获得指定的类型、传参用 -
反射类的成员方法:
Class clazz=Person.class; Method method=clazz.getMethod(methodName,new Class[]{paramClazz1,paramClazz2}); method.invoke();
- 1
- 2
- 3
- 4
-
反射类的构造函数
Constructor con=clazz.getConstructor(new Class[]{paramClazz1,paramClazz2,...}); con.newInstance(params...)
- 1
- 2
- 3
-
反射类的属性;
File field=clazz.getField(fileName); file.setAccessible(true); file.setObject(value);
- 1
- 2
- 3
- 4
情况下,被反射的类,内部通常都会提供一个公有的空参数的构造函数。
// 如何生成获取到字节码文件对象的实例对象。
Class clazz = Class.forName("cn.itcast.bean.Person");//类加载
- 1
- 2
- 3
- 4
// 直接获得指定的类型
clazz = Person.class;
// 根据对象获得类型
Object obj = new Person("zhangsan", 19);
clazz = obj.getClass();
Object obj = clazz.newInstance();//该实例化对象的方法调用就是指定类中的空参数构造函数,给创建对象进行初始化。当指定类中没有空参数构造函数时,该如何创建该类对象呢?请看method_2();
public static void method_2() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
//既然类中没有空参数的构造函数,那么只有获取指定参数的构造函数,用该函数来进行实例化。
//获取一个带参数的构造器。
Constructor constructor = clazz.getConstructor(String.class,int.class);
//想要对对象进行初始化,使用构造器的方法newInstance();
Object obj = constructor.newInstance("zhagnsan",30);
//获取所有构造器。
Constructor[] constructors = clazz.getConstructors();//只包含公共的
constructors = clazz.getDeclaredConstructors();//包含私有的
for(Constructor con : constructors) {
System.out.println(con);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
反射指定类中的方法:
//获取类中所有的方法。
public static void method_1() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
Method[] methods = clazz.getMethods();//获取的是该类中的公有方法和父类中的公有方法。
methods = clazz.getDeclaredMethods();//获取本类中的方法,包含私有方法。
for(Method method : methods) {
System.out.println(method);
}
}
//获取指定方法;
public static void method_2() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
//获取指定名称的方法。
Method method = clazz.getMethod("show", int.class,String.class);
//想要运行指定方法,当然是方法对象最清楚,为了让方法运行,调用方法对象的invoke方法即可,但是方法运行必须要明确所属的对象和具体的实际参数。
Object obj = clazz.newInstance();
method.invoke(obj, 39,"hehehe");//执行一个方法
}
//想要运行私有方法。
public static void method_3() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
//想要获取私有方法。必须用getDeclearMethod();
Method method = clazz.getDeclaredMethod("method", null);
// 私有方法不能直接访问,因为权限不够。非要访问,可以通过暴力的方式。
method.setAccessible(true);//一般很少用,因为私有就是隐藏起来,所以尽量不要访问。
}
//反射静态方法。
public static void method_4() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
Method method = clazz.getMethod("function",null);
method.invoke(null,null);
}
相关文章
- 暂无相关文章
用户点评