title: java基础知识点
date: 2017-12-08 15:35:31
updated: 2020-03-15 17:33:58
categories: java
tags:
- java
10天恶补java的学习笔记
用法:一共27章笔记,每章都很少,按顺序看完即可。有问题想深入的可以百度+b站视频,以下是看的过程中我另外加餐深入了解的:
博客_String相关
b站_多线程
b站_集合
b站_IO
JAVA 工程师技术路线参考:
总结:以阳哥笔记为主线往下看,附加string相关+三章的b站视频即可。(截止写完这篇博客时,阳哥笔记还剩网络编程、反射两章,估计还要配合b站视频看)
疯狂看的话,一周半应该能看完(符合学习一门语言10天的节奏,时间太长收益不大)。把阳哥看完,java基础应该差不多了
java教程
java菜鸟教程(含jdk、eclipse下载、环境变量设置)
JDK 1.6 在线中文手册
int a = 20;
System.out.println(new Integer(a).toString()); //int to String
String ss = "25";
System.out.println(Integer.parseInt(ss)); //String to int
静态方法中只能访问静态成员变量,静态方法中也只能调用静态方法
静态方法:----不能操作对象(虽然使用对象也可以调用类的静态方法,但容易混淆并且与个别对象毫无关系,因此建议使用类名调用静态方法)
静态方法是一种不能向对象实施操作的方法(eg. Math.pow()),在运算时,不使用任何对象。可以认为静态方法是没有this参数的方法(在一个非静态方法中,this参数表示这个 方法的隐式参数)
静态方法不能访问实例域(因为不能操作对象),但可以访问静态域,如下:
public static int getID()
{
return nextId; //public static int nextId;
}
可以通过类名调用静态方法:
Employee.getID();
在下面两种情况下使用静态方法:
一个方法不需要访问对象状态,其所需参数都是通过显式参数提供(Math.pow(2,3))
一个方法只需要访问类的静态域,而不访问实例域( Employee.getID(); )
问:是否可以从一个static方法内部发出对非static方法的调用? 不可以
不可以。因为非static方法是要与对象关联在一起的,必须创建一个对象后,才可以在该对象上进行方法调用,而static方法调用时不需要创建对象,可以直接调用。也就是说,当一个static方法被调用时,可能还没有创建任何实例对象,如果从一个static方法中发出对非static方法的调用,那个非static方法是关联到哪个对象上的呢?这个逻辑无法成立,故不可以
举例:
public class xix {
// 静态成员
public static String string="static成员";
// 普通成员
public String string2="非static成员";
// 静态方法
public static void method(){
string="sss";
//string2="sss";编译报错,因为静态方法里面只能调用静态方法或静态成员
//method2();
System.out.println("这是static方法,static方法与对象无关");
}
// 普通方法
public void method2(){
string ="string1";
string2="string2";
method(); //非静态方法里面可以发出对static方法的调用
System.out.println("这是非static方法,此方法必须和指定的对象关联起来才起作用");
}
public static void main(String[] args) {
xix x=new xix();
x.method2();// 引用调用普通方法
x.method();// 引用调用静态方法
}
}
final 修饰类中的属性或者变量
无论属性是基本类型还是引用类型,final 所起的作用都是变量里面存放的"值"不能变。
这个值,对于基本类型来说,变量里面放的就是实实在在的值,如 1,“abc” 等。
而引用类型变量里面放的是个地址,所以用 final 修饰引用类型变量指的是它里面的地址不能变,并不是说这个地址所指向的对象或数组的内容不可以变,这个一定要注意。
例如:类中有一个属性是 final Person p=new Person(“name”); 那么你不能对 p 进行重新赋值,但是可以改变 p 里面属性的值 p.setName(‘newName’);
final 修饰属性,声明变量时可以不赋值,而且一旦赋值就不能被修改了。对 final 属性可以在三个地方赋值:声明时、初始化块中、构造方法中,总之一定要赋值。
2、final修饰类中的方法
作用:可以被继承,但继承后不能被重写。
3、final修饰类
作用:类不可以被继承。
final 变量:
final 变量能被显式地初始化并且只能初始化一次。被声明为 final 的对象的引用不能指向不同的对象。但是 final 对象里的数据可以被改变。
也就是说 final 对象的引用不能改变,但是里面的值可以改变。
final 修饰符通常和 static 修饰符一起使用来创建类常量。
final 方法
类中的 final 方法可以被子类继承,但是不能被子类修改。
声明 final 方法的主要目的是防止该方法的内容被修改。
public class Test{
public final void changeName(){
// 方法体
}
}
final 类
final 类不能被继承,没有类能够继承 final 类的任何特性。
public final class Test {
// 类体
}
抽象类:
抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。
一个类不能同时被 abstract 和 final 修饰。如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误。
抽象类可以包含抽象方法和非抽象方法。
abstract class Caravan{
private double price;
private String model;
private String year;
public abstract void goFast(); //抽象方法
public abstract void changeColor();
}
抽象方法
抽象方法是一种没有任何实现的方法,该方法的的具体实现由子类提供。
抽象方法不能被声明成 final 和 static。
任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。
如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。
抽象方法的声明以分号结尾,例如:public abstract void sample();
public abstract class SuperClass{
abstract void m(); //抽象方法
}
class SubClass extends SuperClass{
//实现抽象方法
void m(){
.........
}
}
抽象类除了不能实例化对象之外,抽象类必须被继承,才能被使用
抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
构造方法,类方法(用static修饰的方法)不能声明为抽象方法。
抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
synchronized 关键字声明的方法同一时间只能被一个线程访问。
synchronized 修饰符可以应用于四个访问修饰符。
public synchronized void showDetails(){
.......
}
序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。
该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。
public transient int limit = 55; // 不会持久化
public int b; // 持久化
volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
一个 volatile 对象引用可能是 null。
public class MyRunnable implements Runnable
{
private volatile boolean active;
public void run()
{
active = true;
while (active) // 第一行
{
// 代码
}
}
public void stop()
{
active = false; // 第二行
}
}
通常情况下,在一个线程调用 run() 方法(在 Runnable 开启的线程),在另一个线程调用 stop() 方法。 如果 第一行 中缓冲区的 active 值被使用,那么在 第二行 的 active 值为 false 时循环不会停止。
但是以上代码中我们使用了 volatile 修饰 active,所以该循环会停止。
子类是父类的类型,但父类不是子类的类型。
子类的实例可以声明为父类型,但父类的实例不能声明为子类型。
class Vehicle {}
public class Car extends Vehicle {
public static void main(String args[]){
Vehicle v1 = new Vehicle(); //父类型
Vehicle v2 = new Car(); //子类的实例可以声明为父类型
Car c1 = new Car(); // 子类型
Car c2 = new Vehicle(); //这句会报错,父类型的实例不能声明为子类型
//Car(子类)是Vehicle(父类)类型, Vehicle(父类)不是Car(子类)类型
boolean result1 = c1 instanceof Vehicle; // true
boolean result2 = c1 instanceof Car; // true
boolean result3 = v1 instanceof Vehicle; // true
boolean result4 = v1 instanceof Car; // false
boolean result5 = v2 instanceof Vehicle; // true
boolean result6 = v2 instanceof Car; // true
System.out.println(result1);
System.out.println(result2);
System.out.println(result3);
System.out.println(result4);
System.out.println(result5);
System.out.println(result6);
}
}
当变量的值与 case 语句的值相等时,那么 case 语句之后的语句开始执行,直到 break 语句出现才会跳出 switch 语句。
output:
b
c
haha
public class Test{
public static void main(String[] args)
{
int num = 11; //打印一个一行最多num个*的菱形
int k = num; //通过数学计算,我们可知菱形的行数总数也为num
for(int i=0; i<(k+1)/2; ++i) //升序排序打印输出
{
int mid = (k-1)/2; //mid是每行的居中点下标
for(int j=0; j<k; ++j)
{
int temp = j-mid;
if(java.lang.Math.abs(temp) <= i)
{
System.out.print("*");
}
else
{
System.out.print(" ");
}
}
System.out.println();
}
for(int i=(k-1)/2-1; i>=0; i--) //降序排序打印输出
{
int mid = (k-1)/2; //mid是每行的居中点下标
for(int j=0; j<k; ++j)
{
int temp = j-mid;
if(java.lang.Math.abs(temp) <= i)
{
System.out.print("*");
}
else
{
System.out.print(" ");
}
}
System.out.println();
}
}
}
注意:String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了。
如果需要对字符串做很多修改,那么应该选择使用 StringBuffer & StringBuilder 类。
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
Java 中 StringBuffer 和 String 是有一定的区别的,首先,String 是被 final 修饰的,他的长度是不可变的,就算调用 String 的
concat 方法,那也是把字符串拼接起来并重新创建一个对象,把拼接后的 String 的值赋给新创建的对象,而 StringBuffer 的长度是可变的,
调用StringBuffer 的 append 方法,来改变 StringBuffer 的长度,并且,相比较于 StringBuffer,String 一旦发生长度变化,是非常耗费内存的!
String 长度大小不可变
StringBuffer 和 StringBuilder 长度可变
StringBuffer 线程安全 StringBuilder 线程不安全
StringBuilder 速度快
length()方法,length属性和size()的方法的区别:
1.length()方法是针对字符串来说的,要求一个字符串的长度就要用到它的length()方法;
2.length属性是针对Java中的数组来说的,要求数组的长度可以用其length属性;
3.java中的size()方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看!
java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。
具有以下功能:
String str = “helloworld”;
char[] data = str.toCharArray();// 将字符串转为数组
标识:
'-' 在最小宽度内左对齐,不可以与"用0填充"同时使用
'#' 只适用于8进制和16进制,8进制时在结果前面增加一个0,16进制时在结果前面增加0x
'+' 结果总是包括一个符号(一般情况下只适用于10进制,若对象为BigInteger才可以用于8进制和16进制)
' ' 正值前加空格,负值前加负号(一般情况下只适用于10进制,若对象为BigInteger才可以用于8进制和16进制)
'0' 结果将用零来填充
',' 只适用于10进制,每3位数字之间用","分隔
'(' 若参数是负数,则结果中不添加负号而是用圆括号把数字括起来(同'+'具有同样的限制)
转换方式:
d-十进制 o-八进制 x或X-十六进制
上面的说明过于枯燥,我们来看几个具体的例子。需要特别注意的一点是:大部分标识字符可以同时使用。
'-' 在最小宽度内左对齐,不可以与"用0填充"同时使用
'+' 结果总是包括一个符号
' ' 正值前加空格,负值前加负号
'0' 结果将用零来填充
',' 每3位数字之间用","分隔(只适用于fgG的转换)
'(' 若参数是负数,则结果中不添加负号而是用圆括号把数字括起来(只适用于eEfgG的转换)
转换方式:
'e', 'E' -- 结果被格式化为用计算机科学记数法表示的十进制数
'f' -- 结果被格式化为十进制普通表示方式
'g', 'G' -- 根据具体情况,自动选择用普通表示方式还是科学计数法方式
'a', 'A' -- 结果被格式化为带有效位数和指数的十六进制浮点数
3.对字符进行格式化:
对字符进行格式化是非常简单的,c表示字符,标识中’-'表示左对齐,其他就没什么了。
这个格式化没有太掌握,用的时候还需要进一步学习
sleep()使当前线程进入停滞状态(阻塞当前线程),让出CPU的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会。
你可以让程序休眠一毫秒的时间或者到您的计算机的寿命长的任意段时间。
例如,下面的程序会休眠3秒:
Thread.sleep(1000*3); // 休眠3秒
output:3001
我们现在已经能够格式化并创建一个日期对象了,但是我们如何才能设置和获取日期数据的特定部分呢,比如说小时,日,或者分钟? 我们又如何在日期的这些部分加上或者减去值呢? 答案是使用Calendar 类。
Calendar类的功能要比Date类强大很多,而且在实现方式上也比Date类要复杂一些。
Calendar类是一个抽象类,在实际使用时实现特定的子类的对象,创建对象的过程对程序员来说是透明的,只需要使用getInstance方法创建即可。
1.Calendar c = Calendar.getInstance();//默认是当前日期
java.util.regex包
java.util.regex 包主要包括以下三个类:
Pattern 类:pattern 对象是一个正则表达式的编译表示。Pattern 类没有公共构造方法。要创建一个 Pattern 对象,你必须首先调用其公共静态编译方法,它返回一个 Pattern 对象。该方法接受一个正则表达式作为它的第一个参数。
Matcher 类:Matcher 对象是对输入字符串进行解释和匹配操作的引擎。与Pattern 类一样,Matcher 也没有公共构造方法。你需要调用 Pattern 对象的 matcher 方法来获得一个 Matcher 对象。
PatternSyntaxException:PatternSyntaxException 是一个非强制异常类,它表示一个正则表达式模式中的语法错误。
1.匹配一组
String content = "I am noob " + "from runoob.com.";
String pattern = ".*runoob.*";
boolean isMatch = Pattern.matches(pattern, content);
System.out.println("字符串中是否包含了 'runoob' 子字符串? " + isMatch); //true
// 按指定模式在字符串查找
String line = "This order was placed for QT3000! OK?";
String pattern = "(\\D*)(\\d+)(.*)";
// 创建 Pattern 对象
Pattern r = Pattern.compile(pattern);
// 现在创建 matcher 对象
Matcher m = r.matcher(line);
if (m.find( )) {
System.out.println("Found value: " + m.group(0) );
System.out.println("Found value: " + m.group(1) );
System.out.println("Found value: " + m.group(2) );
System.out.println("Found value: " + m.group(3) );
} else {
System.out.println("NO MATCH");
}
JDK 1.5 开始,Java支持传递同类型的可变参数给一个方法。
在方法声明中,在指定参数类型后加一个省略号(…) 。
一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。
Java 允许定义这样的方法,它在对象被垃圾收集器析构(回收)之前调用,这个方法叫做 finalize( ),它用来清除回收对象。
例如,你可以使用 finalize() 来确保一个对象打开的文件被关闭了。
在 finalize() 方法里,你必须指定在对象销毁时候要执行的操作。
finalize() 一般格式是:
protected void finalize()
{
// 在这里终结代码
}
关键字 protected 是一个限定符,它确保 finalize() 方法不会被该类以外的代码调用。
当然,Java 的内存回收可以由 JVM 来自动完成。如果你手动使用,则可以使用上面的方法。
所有的异常类是从 java.lang.Exception 类继承的子类。
Exception 类是 Throwable 类的子类。除了Exception类外,Throwable还有一个子类Error 。
Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。
Error 用来指示运行时环境发生的错误。
例如,JVM 内存溢出。一般地,程序不会从错误中恢复。
异常类有两个主要的子类:IOException 类和 RuntimeException 类。
从大体来分异常为两块:
1、error—错误 : 是指程序无法处理的错误,表示应用程序运行时出现的重大错误。例如jvm运行时出现的OutOfMemoryError以及Socket编程时出现的端口占用等程序无法处理的错误。
2、Exception — 异常 :异常可分为运行时异常跟编译异常
3、java处理异常的机制:抛出异常以及捕获异常 ,一个方法所能捕捉的异常,一定是Java代码在某处所抛出的异常。简单地说,异常总是先被抛出,后被捕捉的。
4、throw跟throws的区别:
public void test() throws Exception {
throw new Exception();
}
从上面这一段代码可以明显的看出两者的区别。throws表示一个方法声明可能抛出一个异常,throw表示此处抛出一个已定义的异常(可以是自定义需继承Exception,也可以是java自己给出的异常类)。
5、接下来看一下如何捕获异常:
1)首先java对于异常捕获使用的是try—catch或try — catch — finally 代码块,程序会捕获try代码块里面的代码,若捕获到异常则进行catch代码块处理。若有finally则在catch处理后执行finally里面的代码。然而存在这样两个问题:
a.看如下代码:
try{
//待捕获代码
}catch(Exception e){
System.out.println("catch is begin");
return 1 ;
}finally{
System.out.println("finally is begin");
}
在catch里面有一个return,那么finally会不会被执行呢?答案是肯定的,上面代码的执行结果为:
catch is begin
finally is begin
也就是说会先执行catch里面的代码后执行finally里面的代码最后才return1 ;
b.看如下代码:
try{
//待捕获代码
}catch(Exception e){
System.out.println("catch is begin");
return 1 ;
}finally{
System.out.println("finally is begin");
return 2 ;
}
在b代码中输出结果跟a是一样的,然而返回的是return 2 ; 原因很明显,就是执行了finally后已经return了,所以catch里面的return不会被执行到。也就是说finally永远都会在catch的return前被执行。(这个是面试经常问到的问题哦!)
6、对于异常的捕获不应该觉得方便而将几个异常合成一个Exception进行捕获,比如有IO的异常跟SQL的异常,这样完全不同的两个异常应该分开处理!而且在catch里处理异常的时候不要简单的e.printStackTrace(),而是应该进行详细的处理。比如进行console打印详情或者进行日志记录。
注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。
注意:super 语句必须是子类构造方法的第一条语句。不能在子类中使用父类构造方法名来调用父类构造方法。 父类的构造方法不被子类继承。调用父类的构造方法的唯一途径是使用 super 关键字,如果子类中没显式调用,则编译器自动将 super(); 作为子类构造方法的第一条语句。这会形成一个构造方法链。
静态方法中不能使用 super 关键字。
class SuperClass {
private int n;
SuperClass(){
System.out.println("SuperClass()");
}
SuperClass(int n) {
System.out.println("SuperClass(int n)");
this.n = n;
}
}
class SubClass extends SuperClass{
private int n;
SubClass(){
super(300);
System.out.println("SubClass");
}
public SubClass(int n){
System.out.println("SubClass(int n):"+n);
this.n = n;
}
}
public class TestSuperSub{
public static void main (String args[]){
SubClass sc = new SubClass();
SubClass sc2 = new SubClass(200);
}
}
output:
SuperClass(int n)
SubClass
SuperClass() //一定注意这一点,在子类构造方法之前,首先是调用父类的构造方法
SubClass(int n):200
2.又一个例子很重要
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}
class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
public void bark(){
System.out.println("狗可以吠叫");
}
}
public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象 //———如果改成Dog b = new Dog();就对了
a.move();// 执行 Animal 类的方法
b.move();//执行 Dog 类的方法
b.bark();
}
}
output:
TestDog.java:30: cannot find symbol
symbol : method bark()
location: class Animal
b.bark();
这是由于在编译阶段,只是检查参数的引用类型。如果编译通过,在运行时,Java虚拟机(JVM)指定对象的类型并且运行该对象的方法。
因此在上面的例子中,之所以能编译失败,是因为Animal类中不存在move方法,改成Dog b = new Dog();就对了
强烈建议直接看菜鸟教程的多态和多态前一节(重载、重写)
多态存在的三个必要条件
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
(1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
(2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
(3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
重载与重写的简明理解:
重载反映的是"随机应变". 同样一项功能, 根据数据类型的不同, 采用不同的处理方式.
重写反映的是"父子差异". 你"继承"了父亲吃火锅的爱好, 但是吃同一份鸳鸯锅(注意, 数据类型相同) , 你喜欢涮红汤, 你父亲喜欢涮清汤.
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
类的多继承是不合法,一个类只能继承一个类,可以实现多个接口,但接口允许多继承.
1.接口可以多继承
2.接口的方法声明必须是 public abstract 即便不写默认也是
3.接口里面不能包含方法具体实现
4.类实继承接口必须实现接口里申明的全部方法,除非该类是抽象类
5.类里面可以声明 public static final 修饰的变量
6.接口不能被实例化,但是可以被实现类创建
接口里面定义的成员变量都是 public static final修饰,抽象类中的变量是普通变量
抽象类和接口的区别:
抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
一个类只能继承一个抽象类,而一个类却可以实现多个接口。
什么时候使用抽象类和接口:
如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。
如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。
如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。
Java 提供了三种创建线程的方法:
总结:Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
实现Runnable接口比继承Thread类所具有的优势:
1)适合多个相同的程序代码的线程去处理同一个资源
2)可以避免java中的单继承的限制
3)增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4)线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
1、调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。
Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
static int MAX_PRIORITY
线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY
线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY
分配给线程的默认优先级,取值为5。
Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
2、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
3、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
4、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
5、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。
Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。
线程示例1(这个可以作为模板,请记住套路)
/**
* 功能描述: 三个售票窗口同时出售20张票
* 程序分析:1.票数要使用同一个静态值
2.为保证不会出现卖出同一个票数,要java多线程同步锁。
*设计思路:
1.创建一个站台类Station,继承Thread,重写run方法,在run方法里面执行售票操作!
售票要使用同步锁:即有一个站台卖这张票时,其他站台要等这张票卖完!
2.创建主方法调用类
@Author:braincao
@Date: 2018/1/3 10:43
*/
public class Test
{
public static void main(String[] args)
{
Station a = new Station("A");
Station b = new Station("B");
Station c = new Station("C");
a.start();
b.start();
c.start();
}
}
class Station extends Thread{
private String name;
private static int tickets = 20; //为了保持票数的一致,票数要静态
public Station(String name)
{
super(name);
}
static Object key = "key"; //创建一个静态钥匙
public void run()
{
while(tickets>0)
{
synchronized(key) //这个很重要,必须使用一个锁,进去的人会把钥匙拿在手上,出来后才把钥匙让出来
{
if(tickets>0)
{
tickets--;
System.out.println(getName() + "窗口卖票一张 余票:" + tickets);
}
else
{
System.out.println("窗口无法买票,票买完了");
}
}
try
{
sleep(1000); //休息一秒,这里休息是必要的,否则很容易只让一个线程一直卖票,休息了给其他线程让出机会
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
}
线程示例2:建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决
/**
* 功能描述:建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,
* 要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决
* @Author:braincao
* @Date: 2018/1/3 14:44
*/
package com.multithread.wait;
public class Test implements Runnable {
private String name;
private Object prev; //前一个对象锁
private Object self; //自身对象锁
private Test(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
@Override
public void run() {
int count = 10;
while (count > 0) {
synchronized (prev) {
synchronized (self) {
System.out.print(name);
count--;
self.notify(); //释放自身对象锁
}
try {
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
Test pa = new Test("A", c, a);
Test pb = new Test("B", a, b);
Test pc = new Test("C", b, c);
new Thread(pa).start();
Thread.sleep(1000); //确保按顺序A、B、C执行
new Thread(pb).start();
Thread.sleep(1000);
new Thread(pc).start();
Thread.sleep(1000);
}
}
输出结果:
ABCABCABCABCABCABCABCABCABCABC
先来解释一下其整体思路,从大的方向上来讲,该问题为三线程间的同步唤醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。为了控制线程执行的顺序,那么就必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是prev,就是前一个线程所持有的对象锁。还有一个就是自身对象锁。主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,终止当前线程,等待循环结束后再次被唤醒。运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A,C锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B,A锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁,唤醒A。看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。但是这种假设依赖于JVM中线程调度、执行的顺序。
三次握手
在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接
而下图是java socket网络通信的过程。定睛一看,服务器socket与客户端socket建立连接的部分其实就是大名鼎鼎的三次握手。
第一次握手:客户端尝试连接服务器,向服务器发送syn包(同步序列编号Synchronize Sequence Numbers),syn=j,客户端进入SYN_SEND状态等待服务器确认
第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态
第三次握手:第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手
1.抽象方法只能定义在抽象类中,抽象方法和抽象类必须由abstract修饰,abstract关键字只能描述类和方法,不能描述变量。抽象方法只定义方法声明,不定义方法实现。抽象类不可以被实例化(创建对象),只有通过子类继承抽象类并覆盖抽象类中的所有抽象方法后,该子类才可以被实例化,否则该子类还是一个抽象类。抽象类中有构造函数用于给子类对象进行初始化,同时抽象类中可以含有非抽象方法。abstract关键字不可以与final,private,static关键字共存,因为被final修饰的方法不可以被重写,意味着子类不可以重写该方法,如果abstract和final共同修饰父类中的方法,子类要实现抽象方法(abstract的作用),而final又不让该方法重写,这相互矛盾。如果private和abstract共同修饰父类中的方法,private修饰则该方法不可以被子类访问,但是abstract修饰需要子类去实现,两者产生矛盾。如果static和abstract共同修饰父类中的方法,static表示是静态的方法,随着类的加载而加载,则该方法不需要在子类中去实现,这与abstract关键字矛盾。
2.static用于修饰成员变量和成员函数,想要实现对象中的共性数据的对象共享,可以将这个数据进行静态修饰,被静态修饰的成员可以直接被类名调用,静态随着类的加载而加载,而且优先于对象存在。静态方法只能访问静态成员(静态方法和静态变量),不可以访问非静态成员,这是因为静态方法加载时,优先于对象存在,所以没有办法访问对象中的成员。静态方法中不能使用this和super关键字,因为this代表本类对象,super代表父类对象,而静态时,有可能没有对象存在,所以this和super无法使用。
3.final关键字可以修饰类,方法,变量(成员变量内,局部变量,静态变量),被final修饰的类是一个最终类,不可以被继承,被final修饰的方法是一个最终方法,不可以被覆盖,但是可以被继承。被final修饰的变量只能是一个常量,只能赋值一次。内部类被定义在类中的局部位置上时,只能访问局部被final修饰的局部变量。