一、Java语言的跨平台特点
在运行java程序的操作系统上,安装对应版本的java虚拟机(JVM),由虚拟机来负责java应用程序在该系统中的运行。
JRE
包括java虚拟机和java的核心类库。
JDK
包含java的开发工具。
总结:使用JDK开发编写java程序,交给JRE运行。
二、java代码执行流程
配置环境变量的目的:为了在所有的盘里面都能跑java程序。
当我们配置了path环境变量之后,运行java代码,首先会在当前盘寻找javac.exe文件,如果有就执行,没有就回去path下的路径中寻找javac.exe文件,进而编译代码。
CLASSPATH是寻找java.exe文件,运行字节码文件。1字节=8比特,1KB=1024字节。
小坑
不用IDE编译的时候,出现了一些小问题。在项目路径上编译运行时报错的,但是在jdk\bin的目录下是能正确执行的,排查出来可能是环境变量CLASSPATH配置有问题,还果真如此。。。
修改环境变量CLASSPATH
为(添加分号) 类名必须大写(墨守成规ing)
Success!
三、java类型转换
从小到大:byte short int long float double
大的到小的:强制转换
小的到大的:自动转换
小容器加上大容器需要大容器才能装下。float+int默认是float
四、java内存图
1、java内存划分
栈(Stack):存放的都是局部变量(定义在方法内部或者方法参数上的参数),局部变量都有自己的作用域,一旦超出作用域会立即从栈内存中消失。方法的运行一定在栈中,运行某个方法会把这个方法压入栈中,一旦运行结束即从栈中弹出该方法。
堆(Heap):只要是new出来的东西(成员属性、成员方法等等)都在堆中。堆里面存放的都系都有一个地址值,堆里面的数据也会有以下默认值:
如果是整数 默认为0
如果是浮点数 默认为0.0
如果是字符 默认为'\u0000'
如果是布尔 默认为false
如果是引用类型 默认为null
方法区:存储.class相关信息,包含方法的信息 。
本地方法栈:与操作系统相关。
寄存器:与CPU相关。
2、基本数据类型的数组内存图
array还是main函数中的局部变量,存放在栈区,只不过这个变量new了一块空间,局部变量会指向堆内存中的地址哈希值。
3、单个对象的内存图
one是main函数中的局部变量,存放在栈区,只不过这个变量new了一块空间,这个空间中包含成员属性和成员方法(指向方法区的.class文件),局部变量会指向堆内存中的地址哈希值。调用成员方法的时候先将该方法压入栈中,并根据one指向的内存地址找到方法地址,再根据该地址指向方法区,在栈中执行成员方法,一旦成员方法执行完毕,即从栈中弹出。
4、对象数组内存图
对象数组动态初始化的时候,在堆空间中new出一块空间,刚开始都是null。当你new出对象立即在堆内存中开辟新的空间,把对象放进数组就相当于会在对应位置存放一个内存地址(用于指向new出的Object对象)。
程序执行完毕,自动从栈中清除内存,先进后出。
堆内存是由jvm中垃圾回收机制清除的。
五、java杂七杂八语法
1、| &:按位或和按位与,将数据转换为二进制后计算。
|| &&:按位或和按位与(存在短路原则),将数据转换为二进制后计算。
2、java的条件判断语句必须是boolean类型。
3、Math.random()函数返回的是0-1的随机值,但是不会取到1。
要生成1-10之间的随机整数:(int)(Math.random()*10+1)
4、内层循环直接跳出整个循环的方法:
x:for(int i=0;i<10;i++) {
System.out.println("外层循环");
for(int j=0;j<10;j++) {
System.out.println("内层循环");
break x;
}
}
5、一维数组初始化:
动态初始化(只分配空间):int[] a = new int[2];
静态初始化(分配空间的同时赋值):int[] a = new int[]{1,2};
6、匿名对象:当属性或者方法只需要调用一次的时候,采用匿名对象。
匿名对象:System.out.println(new Test().age);
jvm垃圾回收机制:只要有匿名的对象,该对象就会从堆内存中清掉,因为栈中没有一个变量对它做引用。优化代码的时候,可以手动断开栈内存和堆内存的连接。(p=null)
强转成匿名对象:Test a=new Test(); a=null;
常用来优化代码。
7、java访问控制符:
8、static、this关键字:
static关键字:一般工具类会用静态的方法。
静态方法只能访问静态变量(方法默认会给成员变量套上this),而在方法体中不能用this和super。原因如下:
this:static优先于当前对象而存在,还没new对象的时候,static已经存在。
super:new对象,先出父类再出子类,子类都没有,肯定没有父类。
this关键字:
this代表当前对象
this(…)调用其他构造方法。
9、方法重载:函数名字和返回值类型相同,只有参数列表不相同(参数个数、参数类型不同)。
10、内部类:
① 非静态内部类:写在某个类成员变量位置的没有static修饰的内部类。这个内部类可以直接访问外部类中的所有成员(包括private)。外部类的方法中可以直接通过new一个内部类对象来访问非静态内部类的属性和方法。但是在其它类中如果想要调用内部类的属性或方法的话,必须要有内部类对象,具体如下:
外部类.内部类 对象名 = new 外部类().new内部类()
② 静态内部类:写在某个类成员变量位置并被static修饰的内部类。这个内部类只能访问外部类中的所有静态成员(包括private)。外部类的方法中可以直接访问静态内部类的静态属性或静态方法,但是如果外部类方法要想调用静态内部类中的非静态属性和方法的话,必须new一个内部类对象才能访问静态内部类的非静态属性和方法。不过要想在其它类中调用静态内部类的属性或方法的话,必须要有内部类对象,具体如下:
外部类.内部类 对象名 = new 外部类.内部类()
③ 局部内部类:写在某个类的非静态方法里面,局部内部类的只在当前方法中存在,其他地方都无法访问到。因此可以在局部内部类外,并且在类的非静态方法内部直接new出内部类对象并访问相应的属性和方法。然后外界通过创建外部类对象,调用外部类对象的方法来调用内部类。
④ 匿名内部类:直接实例化一个抽象类或者接口,并“当场”实现其抽象方法。匿名内部类只是省略了类名或者是子类名称(相当于取而代之的是接口名),具体对象名存不存在要看是不是匿名对象。
11、Object对象:
所有类都直接或者间接的继承Object类。
常用的方法:
public int hashCode():返回该对象的哈希码值。支持此方法是为了提高哈希表的性能。
public String toString():一般用于打印对象,但是Object里面打印的是地址值,当我们打印对象的时候,对象会默认的调用toString()方法(打印内存地址)。开发中,一般会在子类重写toString()方法,重写后一般就是打印成员变量值。
public boolean equals(Object obj):子类看情况重写该方法(和hashCode方法一起重写),重写后一般就是比较成员变量的值;不重写比较的是内存地址。Object中equals方法的源码实现就是return this = = obj。
protected void finalize():当垃圾回收器确定不存在该对象的多种引用时,由对象的垃圾回收器调用该方法。
IDEA直接右击重写equals方法:
12、==和equals方法的区别:
equals方法 只能比较引用数据类型;
而 = = 能比较基本数据类型和引用数据类型,一般用于基本数据类型的比较。
13、String str1="hello"和String str2=new String(“hello”)的区别:
14、如何理解String是常量(是被final修饰的类,无法被继承),一经定义便无法修改。
str+="java"只是改变了引用 !
只new了一次对象,只开了一块内存空间;相比于new两次,节省了内存空间,性能得到了优化。
15、String中常用的API:
boolean equalsIgnoreCase(String str):忽略大小写的比较,常用于验证码。
boolean contains(String str):判断大字符串中是否包含小字符串,常用于查询检测。
String replace(String oldstr,String newstr):字符串替换,常用于敏感词替换为***。
boolean startsWith(String str):判断字符串是否以指定字符串开头。常用于文件上传。
boolean endsWith(String str):判断字符串是否以指定字符串结尾。常用于文件上传(后缀名检测)。
16、String、StringBuffer和StringBuilder的区别:
String对字符串进行拼接操作,每次拼接都会构建一个新的String对象,既耗时,又浪费空间。一般用于比较简单的字符串操作,不涉及到字符串的拼接。
StringBuffer拼接之后只有一个对象,并且是变长的。一般用于字符串的拼接,在多线程里面用的特别多(线程安全,速度不是很快)。
StringBuilder:一般用于字符串的拼接,在多线程里面用的特别多(线程不安全,速度很快)。
速度:String < StringBuffer < StringBuilder
String和StringBuffer中所有API只要返回值为String,则调用该API函数的对象自己不会发生变化。
注意:用了StringBuilder就不要再用+拼接字符串了,统统用append!
17、String、StringBuffer相互转换的方法:
//String->StringBuilder
String str = "hello world!";
StringBuffer buff = new StringBuffer(str);
System.out.println(str);
//StringBuilder->String
StringBuffer n_buff = new StringBuffer("hello javase!");
String n_str = n_buff.toString();
System.out.println(n_str);
18、二分查找:必须在有序数组的基础上,查找相应的值。注意对字符串排序的时候,要先统一大小写。
源码(>>>表示无符号右移,>>表示带符号右移):
int Arrays.binarySearch(char[] c, char key)
int low = 0;
int high = c.length - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
char midVal = a[mid];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.
19、包装类
把基本数据类型封装成了对象。好处在于可以直接调用对象中的许多方法。
byte short int long float double char boolean
Byte Short Integer Long Float Double Character Boolean
JDK5.0支持自动装箱。
String和int相互转化方法:
//String->int
String d = "188";
int i = Integer.parseInt(d);
System.out.println(i);
//int->String
int t = 188;
String s = String.valueOf(t);
System.out.println(s);
20、增强型for循环+可变参数
public class Test {
public static int calculate(int... a) {
//可变参数是作为数组传入的
int sum = 0;
for (int i : a)
sum += i;
return sum;
}
public static void main(String[] args) {
int sum = Test.calculate(1, 2, 3, 4, 5);
System.out.println(sum);
}
}
21、System类:
System.out.println()—System类提供标准输入流(in)、标准输出流(out)、标准错误输出流(err),这些都是单独的类。
System.gc()运行JVM的垃圾回收器。
System.currentTimeMillis()方法返回的是当前时间与1970年1月1日午夜之间的时间差(毫秒)。
22、Date类+SimpleDateFormat类:
yyyy-MM-dd HH:mm:ss:表示描述时间和日期格式的模式,每个字母都代表一个数字。
//传参和不传参 获得的都是当前时间
Date date = new Date();
//Date date=new Date(System.currentTimeMillis());
long time = date.getTime(); //得到当前时间的毫秒值
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String str = simpleDateFormat.format(date);
System.out.println(str);
可以得到用户在页面上输入时间的毫秒值。可用于按照时间差距的大小取消订单。
Date d = simpleDateFormat.parse("2019-11-24 09:28:01"); //这个String表示最初定义的规则 long start_time = d.getTime();
23、instanceof
Man man = new Man();
Human human = new Human();
if (man instanceof Human)
System.out.println("这个类是Person本身或者它的子类");
else
System.out.println("这个类不是Person本身或者它的子类");
24、File类:
构造方法:
File(String pathname)
File(String parent, String child)
File(File parent, String child):会自动拼接路径。
常用方法:
public boolean createNewFile():创建新文件。
public boolean mkdir():创建一层文件夹。
public boolean mkdirs():创建多层文件夹。
public boolean isDirectory():判断是否是一个文件夹。
public boolean isFile():判断是否是一个文件。
public boolean exists():判断文件或者文件夹是否存在。
public String getAbsolutePath():获取绝对路径。
public String getPath():获取相对路径。
public String getName():获取文件名。
public long length():获取文件长度。
public String[ ] list():获取文件路径下的文件名字。
public File[ ] listFiles():获取文件路径下的所有文件。
25、IO流:
按流向分类:输入流,输出流(以IDE作为参考)
按数据类型分类:字节流(如果用windows记事本打开看不懂)、字符流(如果用windows记事本打开看的懂);开发中不知道用字符流还是字节流的时候,就用字节流。
字节输入流(Input或者包含Input) 字节输出流(Output或者包含Output)
字符输入流(Reader或者包含Reader) 字符输出流(Writer或者包含Writer)
① 字节输入流
直接new对象来创建字节输入流:InputStream fis = new FileInputStream(...);
FileInputStream的构造方法:
FileInputStream(String name):打开一个到路径名name指定文件的连接创建流。
常用方法:
public int read(int b):从字节输出流中读取一个int类型,根据ASCII码写入对应的字母。读取到末尾则返回-1。
public int read(byte[ ] b):从字节输出流中读取一个字节数组。读取到末尾则返回-1。
public int read(byte[ ] b, int off, int len):从字节输出流中读取字节数组的一部分(开发中常用,并且同缓冲流加速)。
② 字节输出流
多态来创建字节输出流:OutputStream fos = new FileOutputStream(...);
FileOutputStream的构造方法:
FileOutputStream(String name):name表示传入路径,创建字节输出流的时候,会new File的对象。
FileOutputStream(File file):创建一个向File对象表示的文件中写入数据的流。
FileOutputStream(String name, boolean append):append为true表示写入的时候追加到文件末尾。
常用方法:
public void write(int b):向字节输出流中写入一个int类型,根据ASCII码写入对应的字母。
public void write(byte[ ] b):向字节输出流中写入一个字节数组。
public void write(byte[ ] b, int off, int len):向字节输出流中写入字节数组的一部分(开发中常用,并且同缓冲流加速)。
③ 字节缓冲流(一定要用flush刷新,不然可能会写不进入)
构造方法:
BufferedInputStream(InputStream is):InputStream是抽象类,不能直接new对象,要用多态实现传参。
BufferedInputStream(InputStream is, int size):创建一个指定大小的字节缓冲输入流。
BufferedOutputStream(OutputStream os):OutputStream 是抽象类,不能直接new对象,要用多态实现传参。
BufferedOutputStream(OutputStream os, int size):创建一个指定大小的字节缓冲输出流。
常用方法和字节输入输出流同。
用完后一定要关闭,并且关闭的时候要判断是否为空(如果在try里面的打开流之前就处理了异常,则流会是空的)。
int len = 0;
byte[] b = new byte[1024];
bis = new BufferedInputStream(new FileInputStream("E:\\xxx\\告白气球.mp3"));
bos = new BufferedOutputStream(new FileOutputStream("copy_告白气球.mp3"));
while ((len = bis.read(b)) != -1) { //每次从流中读取1024字节到byte数组中,到达流的末尾时返回-1
bos.write(b, 0, len); //不断地在文件最后写数据
bos.flush(); //一定要加上
}
④ 转换流(将字节流转换为字符流)
字符流 = 字节流 + 编码表
字节流操作中文不方便,java提供了转换流(开发中一般不用,一般直接用字符流)。
构造方法:
InputStreamReader isr = new OutputStreamWriter(OutputStream os):用默认编码格式读取。
InputStreamReader isr = new OutputStreamWriter(OutputStream os, Charset ac):指定编码格式读取。
OutputStreamWriter osw = new OutputStreamWriter(OutputStream os):用默认编码格式写入。
OutputStreamWriter osw = new OutputStreamWriter(OutputStream os, Charset ac):指定编码格式写入。
常用方法:
public void write(String str, int off, int len):写入字符串的一部分。
public int read(char[ ] cbuf, int offset, int length):将字符读取到数组里面。读取到末尾则返回-1。
⑤ 字符输入流
构造方法:
FileReader(String fileName):创建一个新的 FileReader ,给定要读取的文件的名称。
⑥ 字符输出流(一定要用flush刷新,不然可能会写不进入)
构造方法:
FileWriter(String fileName):构造一个给定文件名的FileWriter对象。
FileWriter(String fileName, boolean append):append为true表示写入的时候追加到文件末尾。
⑦ 字符缓冲流
构造方法:
BufferedReader(Reader r):参数是Reader抽象类,只能用子类new对象。
BufferedWriter(Writer w):参数是Reader抽象类,只能用子类new对象。
常用方法:
String readLine():读取流中的一行。
void newLine():写入换行。
windos真正的换行是\r。
26、基本数据操作流
DataInputStream
DataOutputStream
27、内存操作流
速度很快,但是数据保存在内存上,开关机后数据就没了。
ByteArrayInputStream
ByteArrayOutputStream
CharArrayReader
CharArrayWriter
StringReader
StringWriter
28、常用的输入方式:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String num = null;
try {
num = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(num);
29、序列化对象
ObjectInputStream(对象的序列化输入):序列化流。
ObjectOutputStream(对象的序列化输出):反序列化流。
对象实现Serializable接口(没有任何方法的标识接口)的时候,系统默认会将一个serialVersionUID写到文件里,当读文件的时候,会将文件的serialVersionUID和类的serialVersionUID比较,不一致会报错。当类里面创建serialVersionUID变量时候,读和写都会用这个serialVersionUID。
六、java面向对象的特点
1、继承
java不支持多继承。java是单继承,多实现的语言。
继承的时候,如果子类和父类成员变量重了,在子类中默认访问的是子类的成员变量。如果一定要访问父类的成员变量,需要加上super(用法和this相似)。
如果某个类实现了有参构造,则会覆盖系统默认的无参构造,即不能调用无参构造。如果非要调用无参构造,则需要实现无参构造。
子类的任何构造方法都会默认去调用父类的无参构造方法,并且必须在第一行。当父类中实现了有参构造的时候,必须再显式实现无参构造,因为此时系统默认的无参构造方法已经被覆盖了。
方法重写:子类可以重写父类的方法(函数名字、参数列表、返回值类型都相同),可以选择性地通过super(…)调用父类对应的方法。
子类重写的方法的访问控制权限必须大于等于父类被重写方法的控制权限。
用final修饰的变量,一经赋值就无法被修改。一般常量都是大写的。
用final修饰的方法,无法被子类重写。
用final修饰的类,无法被继承。
抽象关键字不能和private、final、static这些关键字共存。
private:子类访问不到这个抽象方法,自然无法实现。
final: 用final修饰的方法,无法被子类重写。
static: 可以用类名.方法名访问,但是这时候该抽象方法还没有被子类重写。
抽象类和接口的区别:
抽象类中可以包含普通方法,而接口中都是抽象方法,没写abstract也会默认为是抽象方法。
抽象类中可以包含变量,而接口中只能是常量,没写final也会默认为是常量。
2、封装
将成员变量私有化,外部无法直接访问这些变量,但对外提供公共接口get和set来访问我们的私有属性。
3、多态
多态情况 :父类 父类引用 = new 子类()
父类引用指向子类对象
多态构造方法也是先调用父类构造方法,再调用子类构造方法。
多态规则:
成员变量:编译看左边,运行看左边。其他被隐藏。
成员方法:编译看左边,运行看右边。其他被隐藏。
构造方法:创建子类对象,用父类对象初始化。接口没有构造方法。
静态方法:编译看左边,运行看左边。
多态下调用子类特有方法(强转):
Animals pig = new Pig();
Pig p = (Pig) pig;
p.name = "佩奇";
pig.sleep();
p.attack();
七、java集合框架+泛型
1、Collection类:
Collection是一个接口,只能通过多态new对象,而List也是一个接口。
父类 父类引用 = new 子类()
访问成员变量:编译看左边,运行看左边。
访问成员方法:编译看左边,运行看右边。
常用方法:
add(Object o):增,传入想添加的引用数据。添加1的时候,Integer=1;自动装箱了。
remove(Object o):删,传入想移除的引用数据。
Collection里面不能改。
contains(Object o):查,传入想查的引用数据,存在返回true,不存在返回false。
上述方法都不是方法重写,因为函数的参数类型不同,只是重载,按传入参数类型来确定执行的方法。
2、List类:
List集合是一个有序集合,有下标,存储的数据可以重复。
常用方法:
add(int index, Object o):增,可以在指定位置加入想添加的引用数据。
remove(int index):删,删除指定位置的引用数据。
set(int index, Object o):改,修改指定位置的数据为指定的值。
get(int index):查,必须用包装类接收查询指定位置的应用数据。
size():获取集合的长度。
listIterator():倒序遍历,遍历前要先将此迭代器移到list末尾。
如果只需要遍历一个集合,可以用普通for循环、增强for循环、迭代器。
如果要在遍历的时候操作数据,只能用普通for循环。
ArrayList:底层是数组,查询快,增删慢,线程不安全,效率高(用的多)。
LinkedList:底层是双向链表,查询慢,增删快,线程不安全,效率高(用的少)。
Vector:底层是数组,查询快,增删慢,线程安全,效率低(基本不用)。
3、Set类:
Set集合是一个不包含重复元素的集合,且没有下标。
如果只需要遍历一个集合,可以用增强for循环、迭代器。
HashSet:不保证set的迭代顺序(也就是说遍历输出的顺序可能和输入的顺序不同),底层是哈希表和单向链表实现。每次for循环比较传进来的哈希值和哈希表里面的哈希值对比,相等就不添加,不相等就添加。
LinkedHashSet:有顺序(也就是说遍历输出的顺序一定和输入的顺序一致),底层由哈希表和双向链接列表实现。
TreeSet:两种排序方式:无参构造就是自然顺序升序排列(之所以能排序是因为形参类型实现了Comparator接口) TreeSet()
; 传入参数Comparator进行排序,排序规则通过实现Comparable接口,并重写compareTo方法实现 TreeSet(Comparator<? super E> comparator)
。
TreeSet的add函数调用了TreeMap的put函数, 而TreeMap的实现使用了红黑树数据结构。
4、Map类:
Map集合是一一对应的映射关系的集合,key不能重复,value可重复。
常用方法:
put(key, value):存入数据到集合里。
get(key):从集合中根据key取值。
keySet():返回集合里所有key的set集合。
entrySet():返回包含映射关系的Set视图(javaEE常用)。
remove(Object key):根据key移除value。
containsKey(Object key):判断集合里是否存在这个key。
containsValue(Object value):判断集合里是否存在这个value。
如果只需要遍历一个集合,可以用keySet()、entrySet()。
HashMap:不保证顺序(也就是说遍历输出的顺序可能和输入的顺序不同)。
TreeMap:默认自然排序升序排序(能排序是因为实现了Comparator接口) TreeMap()
;也可以用比较器排序(实现Comparable接口并重写compareTo方法)TreeMap(Comparator<? super K> comparator)
。
LinkedHashSet:保证顺序(也就是说遍历输出的顺序一定和输入的顺序一致)。
HashMap是Hashtable的升级版。
5、Collections类:
常用方法:
Collections.sort(List< >):自然排序。
Collections.sort(List< >):比较器排序,参数Comparable接口中的compareTo方法。
Collections.shuffle(List< >):洗牌。
Collections.reverse(List< >):逆转list。
Collections.max(List< >):返回最大值。
Collections.binarySearch(List< >,E):二分查找(list必须有序)。
6、泛型
就是将类型由原来的具体的类型参数化,然后在使用/调用时传入具体的类型(必须是引用数据类型)。
类名上的泛型:public class User<T>
方法上的泛型:public <T>void hello(T t)
接口上的泛型:public interface Animal<T>; public class Bird<T> implements Animal<T>
虽然java规定泛型左右类型必须一致,但是可以通过泛型通配符来改变这个规则。
泛型通配符:List<? extends Man> list1 = new ArrayList<Man>();//表示Man及其子类可访问
List<? super Person> list2 = new ArrayList<Object>();//表示Person及其父类可访问
八、java异常
Error是java虚拟机的错误,程序员改不了。
Exception是可以被处理的异常。如果没有被处理,程序会将错误抛给JVM,JVM就会将程序中断。
throw方法体内出现某种程序无法执行的情况的时候,直接抛出异常。
异常处理方式:
try…catch…finally:catch(捕获到对应的异常并处理);finally(不管什么时候都会执行,常用来关闭资源)。
throws:方法抛出异常,下次调用的时候必须再抛出异常或者捕获异常并处理,如果到最后都没有处理这个异常,JVM会中断程序。
九、多线程
java开启了多线程以后,程序开始抢CPU的执行权,谁抢到了就谁执行。
1、基本概念
进程和线程:
进程:一个应用程序就是一个进程,对应一个PID。
线程:一个应用程序里执行的多个任务。
多进程的意义:
没有多进程,聊QQ就不能玩游戏,刷网页就不能听歌。
多线程的意义:
不是为了提高执行速度,而是为了提高应用程序的使用率。
并行和并发:
并行:逻辑上同时发生,指某个时间点内同时运行多个程序。
并发:物理上同时发生,指某个时间点内同时运行多个程序。
执行多线程:调用对象.start( )方法。
线程休眠(以毫秒为单位):Thread.sleep( … )。
常用方法:
1、继承了Thread的类可以用getName( );
2、Thread.currentThread().getName( )。
3、Thread对象.setName( … ):设置线程名字。
线程的生命周期:
2、代码实现
多线程+同步锁实现方法(3个电影院卖票):
添加同步锁是为了解决线程不安全问题。比如,西湖区万达抢到了CPU资源并执行num- -,在输出之前被临安区万达抢到了CPU的执行权,也执行num- -,这时候西湖区万达抢到CPU执行权,打印了num,临安区万达再次抢到CPU执行权,也打印和西湖区万达同一张电影票序号,这就是线程不安全的程序。
synchronized:
① 继承Thread类,重写run( )方法------使用同步代码块(任意同一个对象皆可);
MyThread thread = new MyThread (同步锁对象);
public class TicketsThread extends Thread {
static int num = 100; //用静态变量共享票数
private Object object;
public TicketsThread(Object o) {
this.object = o;
}
@Override
public void run() {
//不同区的电影片卖出同一张电影票(同步代码块解决)
//解决思路: 当线程抢到以后, 必须执行完run方法里面的代码后, 再回来继续抢
while (true) {
//同步代码块, 锁的是私有对象
synchronized (object) {
if (num > 0) {
num--;
System.out.println(getName() + "卖出了第" + num + "张票");
} else {
break;
}
}
}
}
}
public class Demo_Sell {
public static void main(String[] args) {
Object object = new Object(); //用同一把 同步锁-同步代码块(随便是啥对象都可以) 锁住三个线程
TicketsThread thread1 = new TicketsThread(object);
TicketsThread thread2 = new TicketsThread(object);
TicketsThread thread3 = new TicketsThread(object);
//设置线程名字
thread1.setName("西湖区万达");
thread2.setName("余杭区万达");
thread3.setName("临安区万达");
thread1.start();
thread2.start();
thread3.start();
}
}
② 实现Runnable接口,重写run( )方法------使用同步方法(同一个Runnable target对象)。
Thread thread=new Thread(Runnable target, String ThreadName);
public class MyRunnable implements Runnable {
private static int num = 100;
private Object object;
MyRunnable(Object o) {
this.object = o;
}
MyRunnable() {
}
//静态方法的锁是 这个类的字节码对象
//同步方法, 锁的是this当前对象
private synchronized void show() {
num--;
if (num > 0)
System.out.println(Thread.currentThread().getName() + "卖出了第" + num + "张票");
}
@Override
public void run() {
while (true) {
show();
if (num < 0)
break;
}
}
}
public class Demo_Sell {
public static void main(String[] args) {
Object object = new Object();
MyRunnable myRunnable = new MyRunnable(object);
Thread thread1 = new Thread(myRunnable, "西湖区万达"); //线程名字
Thread thread2 = new Thread(myRunnable, "余杭区万达");
Thread thread3 = new Thread(myRunnable, "临安区万达");
thread1.start();
thread2.start();
thread3.start();
}
}
Lock :
public class MyRunnable_Lock implements Runnable {
private Lock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
System.out.println(Thread.currentThread().getName() + "得到锁");
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}
public class Demo_Sell {
public static void main(String[] args) {
MyRunnable_Lock myRunnable_lock = new MyRunnable_Lock();
Thread thread1 = new Thread(myRunnable_lock, "西湖区万达"); //线程名字
Thread thread2 = new Thread(myRunnable_lock, "余杭区万达");
Thread thread3 = new Thread(myRunnable_lock, "临安区万达");
thread1.start();
thread2.start();
thread3.start();
}
}
效果:
分析原因:lock是局部变量,三个线程用了三个不同的Lock对象,自然就锁不住。
解决手法:将lock对象传到构造函数里面。
Lock lock = new ReentrantLock();
MyRunnable_Lock myRunnable_lock = new MyRunnable_Lock(lock);
Thread thread1 = new Thread(myRunnable_lock, "西湖区万达"); //线程名字
Thread thread2 = new Thread(myRunnable_lock, "余杭区万达");
Thread thread3 = new Thread(myRunnable_lock, "临安区万达");
thread1.start();
thread2.start();
thread3.start();
效果:
3、死锁
Java死锁出现原因:
线程在获得了锁A并且没有释放的情况下去申请锁B,这时,另一个线程已经获得了锁B,在释放锁B之前又要先获得锁A,因此闭环发生,陷入死锁循环。
public class Person {
public static final Object objectA = new Object();
public static final Object objectB = new Object();
}
public class MyThread2 extends Thread {
private boolean b;
public MyThread2(boolean b) {
this.b = b;
}
public MyThread2() {
}
@Override
public void run() {
if(b) {
synchronized (Person.objectA) {
System.out.println("中国人a");
synchronized (Person.objectB) {
System.out.println("中国人b");
}
}
} else {
synchronized (Person.objectB) {
System.out.println("外国人a");
synchronized (Person.objectA) {
System.out.println("外国人b");
}
}
}
}
}
public class Demo_DeadLock {
public static void main(String[] args) {
Person person = new Person();
MyThread2 myThread1 = new MyThread2(true);
MyThread2 myThread2 = new MyThread2(false);
myThread1.start();
myThread2.start();
}
}
上面的代码不出现死锁只能是以下两种情况(几率极低),其他都会死锁住。
4、匿名内部类的多线程:
① 继承Thread类;
public class Demo_Anonymous_Thread {
public static void main(String[] args) {
new Thread() {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
② 实现Runnable接口。
public class Demo_Anonymous_Thread {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}).start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
5、定时器
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
}
};
timer.schedule(task, 休眠时间); //一段时间后才执行task的代码
十、Socket编程
网络的三要素:协议,IP地址,端口号。
1、UDP
DatagramSocket声明是UDP协议。
DatagramPacket是待发送的数据包,发送方指定目的IP地址和端口号;接收方只需开放端口。
2、TCP
client = server.accept();//服务端的阻塞方法最好放在while循环的开头
十一、反射
1、类初始化时机
1、创建类的实例对象(new 对象)
2、访问类的静态变量,或者为静态变量赋值
3、调用类的静态方法
4、用反射创建某个类或接口对应的java.lang.Class对象(不用new)
5、初始化某个类的子类
2、反射实现
① 通过构造方法得到对象
public class Demo_Constructor {
public static void main(String[] args) {
try {
//获取 公共无参构造
Class p = Class.forName("Reflect.Person"); //得到这个类的字节码对象
Constructor public_con = p.getConstructor();
Object public_object = public_con.newInstance(); //new对象
//获取 私有有参构造
Constructor private_con = p.getDeclaredConstructor(int.class);
private_con.setAccessible(true);
Object private_object = private_con.newInstance(88); //new对象
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
② 反射访问属性
public static void main(String[] args) {
try {
//以前: 对象.属性名
//反射: 属性对象.方法(对象)
Class p = Class.forName("Reflect.Person");
Constructor con = p.getDeclaredConstructor(String.class, int.class);
con.setAccessible(true);
Object object = con.newInstance("xxx", 88);
//公共属性对象
Field String_field = p.getField("name"); //通过class字节码对象取到name的属性对象
System.out.println(String_field.get(object));
//私有属性对象
Field int_field = p.getDeclaredField("money");
int_field.setAccessible(true);
int_field.set(object, 66);
System.out.println(int_field.get(object));
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
③ 反射访问方法
public static void main(String[] args) {
try {
//以前: 对象.方法名
//反射: 方法对象.方法(对象, 参数)
Class p = Class.forName("Reflect.Person");
Constructor con = p.getConstructor();
Object object = con.newInstance();
//公共无参方法
Method public_method = p.getMethod("show1");
public_method.invoke(object);
//私有无参方法
Method private_nop_method = p.getDeclaredMethod("show2");
private_nop_method.setAccessible(true);
private_nop_method.invoke(object);
//私有有参方法
Method private_p_method = p.getDeclaredMethod("show3", int.class); //声明形参类型
private_p_method.setAccessible(true);
private_p_method.invoke(object, 66); //传入实参
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}