注意,下面即将出现一大波文字解释(可怕)
Java基础知识(一)的续集哈
Java基础知识(二)的续集哈哈
Java基础知识(三)的续集哈哈哈
Java基础知识(四)的续集哈哈哈哈
一、多线程(区别线程与进程)
1.1、了解 线程 与 进程的区别
- 进程: 正在执行的程序(进程中至少有一个线程)
- 线程: 是进程中用于控制程序执行的控制单元(执行路径,执行场景)
对于 JVM 启动时,只有两个线程: JVM 的主线程 和 JVM 的垃圾回收机制线程
- 在程序中定义线程: 对象线程这类事物的描述 Thread
- Thread类定义了: 创建线程对象的方法(构造函数)、 提供被线程执行的代码存储的位置 run() 、 开启线程运行的方法 start()
同时有其他方法用于操作线程: static Thread currendThead(); String getName(); static void sleep(time) throws InterruptedException; 要运行的代码都是后期定义
1.2、多线程的 两种创建方式 以及 区别
1` 创建线程的第一种方式 : 继承 Thread 类 (因为要覆盖 run 方法, 定义线程要运行的代码)
步骤:
- 继承 Thread 类;
- 覆盖 run 方法,将线程要运行的代码定义其中;
- 创建 Thread 类的子类对象,其实就是在创建线程,然后调用 start 方法。
2` 创建线程的第二种方式 : 实现 Runnable 接口,并将多线程要运行的代码,存放在 Runnable 的 run() 方法中(实现 避免了单继承的局限性)
步骤:
- 定义实现 Runnable 接口;
- 覆盖接口的 run() 方法,将多线程要运行的代码存入其中;
- 创建 Thread 类的对象(创建线程),并将 Runnable 接口的子类对象,作为参数传递给 Thread 的构造函数;
- 调用 Thread 对象的 start 方法,开启线程。
注: 第三步中为什么传递? 因为线程要运行的代码在 Runnable 子类的 run 方法中存储,所以将 run 方法所属的对象,传给 Thread , 将 Thread 线程去使用该对象,调用 run() 方法。
两种方式的特点:
- 分别采用继承、实现方式,为避免单继承的局限性,所以创建线程建议,通过实现 Runnable 接口的方式,即第二种
1.3、线程的状态
线程的状态:
- 被创建;
- 运行;
- 冻结;(放弃执行资格)
- 消亡。
注: 还有一种临时状态,其特点具备执行资格,但不具备执行权。
- 多线程具备随机性,因为 cpu 不断的快速切换造成。
- 可能产生多线程的安全问题原因:
- 多线程代码中操作的共享数据;
- 多条语句操作该共享数据。
注: 当具备以上两个关键点时,有一个线程对多条,操作共享数据的代码执行的一部分,还未执行完,另一个线程开始参与执行,就会发生数据错误,即发生 同步(下一点详细解释)
对应的多线程安全问题原因解决方法:当一个线程在执行多条操作共享数据代码时,其他线程即使获取了执行权,也不可以参与操作。
- 多线程的 同步: 将部分操作功能数据的代码进行加锁 (如 火车上的卫生间, emmm 哈哈)
- 同步的表现形式:
- 同步代码块 (synchronized);
- 同步函数。
同步表现形式的区别:
- 同步代码块: 使用的锁匙任意对象(如: this、Object、Demo.class)
- 同步函数: 使用的锁就是 this。
注: static 的同步函数,使用的锁匙 类名.class, 即该类的字节码对象,涉及单例设计模式的懒汉式
- 同步的好处: 解决线程的安全问题
- 同步的弊端: 较为消耗资源; 同步发生嵌套后,容易变成死锁
- 同步的前提(以下两个条件都需要满足):
- 必须是两个或两个以上的线程;
- 必须是多线程使用同一个锁。
注: 死锁代码需要会写,但开发时要避免
1.4、线程间通信: 其实是多个线程在操作同一资源,但操作的动作不同。
- 常见例子 -- 生产者、消费者问题
- 当多个生产者、消费者出现时,需要获取执行权的线程判断标记,通过 while 完成;
- 需要将对方的线程唤醒,仅用 notify 是不可以的,因为可能出现只唤醒本方,可能导致所有线程都等待,所以可以通过 notifyAll 的形式来完成。
- 以下三种方法,都使用在同步中,因为需要对持有监视器( 锁 )的线程操作,所以需要使用在同步中,因为只有同步才具有锁。
wait() 、 notify() 、 notifyAll()
- 为什么这些操作线程的方法,定义在 Object 类中呢?
- 因为它们操作同步中的线程时,必须要标识它们所操作的锁,只有同一锁上的被等待线程,可以被同一锁上 notify 唤醒,不可以对不同锁中的线程进行唤醒, 即 等待 和 唤醒 必须是同一个锁。
- 而 锁 可以是任意对象,所以,可以被任意对象调用的方法,定义在 Object 类中。
注:以上操作的弊端,每次 notifyAll 时,都会唤醒本方,可以只唤醒 对方吗?
- JDK 1.5 版本提供了一些新的对象,优化了 等待唤醒机制:
- 将 synchronized 替换成 Lock 接口,将隐式锁升级为显示锁; Lock 常用方法: 获取锁 Lock()、释放锁 unLock()、获取Condition 对象 new Condition();(注意,释放动作一定要执行,所以通常定义在 finally 中)
- 将 Object 中的 wait、notify、notifyAll替换成 Condition ... await()、 signal()、 signalAll()方法;区别是, 以前是一个同步代码库具备一个锁,该锁具备自己独立的 wait() 和 notify() 方法, 现在是将 wait、notify等方法,封装进一个特有的对象 Condition 中,而一个锁 Lock 上可以有多个 Condition 对象。
// 示例代码如下
Lock lock = new RunntrantLock();
Condition conA = lock.newCondition();
Condition conB = lock.newCondition();
/** con.await(); // 生产...消费
* ...
*/ con.signal(); // 生产
set() {
if(flag) {
conA.await(); // 生产者 conA 等待
}
// .
// .
// code...;
// .
// .
flag = true;
conB.signal(); // 唤醒conB对象
}
out() {
if(!flag) {
conB.await(); // 消费者 conB 等待
}
// .
// .
// code...;
// .
// .
flag = true;
conA.signal(); // 唤醒conA对象
}
- wait 和 sleep 的区别
- wait : 释放 cpu 执行权,释放同步中的锁;
- sleep: 释放 cpu 执行权,不释放同步锁。
// 同步锁示例代码
synchronize (锁) {
wait();
}
- stop 停止线程的原理: 是 run 方法结束, run 方法中通常定义循环,指定控制循环线程,即可结束。
- stop 停止线程的过程:
- 定义结束标记;
- 当线程处于冻结状态,没有执行标记,程序无法结束,此时可以循环,正常退出冻结状态 或 强制结束冻结状态。
注: 强制结束冻结状态interrupt(): 目的是线程强制,从冻结状态恢复到运行状态,但会发生 InterruptedException 异常。
- 线程中常见的一些方法:
- setDaemon(boolean):将线程标记为后台线程,后台线程与前台线程一样开启,一样抢执行权运行,只有结束时有区别,当前台线程(如 main 线程)都运行结束后,后台线程会自动结束;
- join(): 等待线程结束后,当 A线程(如 main 线程)执行了 B线程的 join()方法时, A线程就会处于冻结状态, 当B线程运行结束后,A线程就会具备运行资格,继续执行。
注: 加入线程,可以完成对某个线程的临时加入执行
二、字符串String
- String s1 = "ass"; // s1是一个类类型变量,"ass"是一个对象;
- // 字符串最大的特点: 一旦被初始化就不可以被改变;
- String s2 = new String("ass");
问 :s1 和 s2 有什么区别呢?
答: s1 在内存中有一个对象, s2 在内存中有两个对象
注: String类复写了 Object 类中的 equals方法,该方法判断字符串内容是否相同
2.1、String类常用方法 [ 获取 ]:
[ 获取长度 ]
字符串中包含的字符数,也就是字符串的长度
- int length(): 获取长度
[ 根据位置获取位置上某个字符 ]
- char charAt(int index): 当访问到字符串中不存在的角标时,会发生 StringIndexOutOfBoundsException错误
[ 根据字符获取该字符在字符串中的位置 ]
- int indexOf(int ch): 返回是 ch 在字符串中第一次出现的位置
- int indexOf(int ch, int formIndex): 返回从 formIndex 指定位置开始,获取 ch 在字符串中出现的位置
- int indexOf(String str ): 返回 str 在字符串中第一次出现的位置
- int indexOf(String str , int formIndex): 返回从 formIndex 指定位置开始,获取 str 在字符串中出现的位置
- int lastIndexOf(int ch): 反向查找( 索引 ) ch出现的位置(位置是正常角标值)
// 注: 如果没找到字符,返回 -1
2.2、String类常用方法 [ 判断 ]:
[ 判断字符串中是否有内容 ]
- boolean isEmpty(): 原理是判断长度是否为0
[ 判断字符串是否包含某一个字串 ]
- boolean contains(str): 判断是否包含str字符串
- int indexOf(str):判断str出现的位置,特殊之处: 可以索引 str 第一次出现的位置,如果返回 -1,表示 str 不存在,也可用于判断是否包含;(如: if( str.indexOf("aa") != -1 ){ ... })
[ 判断字符串是否以指定内容开头 ]
- boolean startWith (str);
[ 判断字符串是否以指定内容结尾 ]
- boolean endsWith (str);
[ 判断字符串内容是否相同,复写了 Object 类中的 equals 方法 ]
- boolean equals (str);
[ 判断内容是否相同,并忽略大小写 ]
- boolean equalsIgnoreCase (str);
2.3、String类常用方法 [ 转换 ]:
[ 将字符数组,转换为字符串]
1` 构造方法:
- String(char[] data)
- String(char[] data, int offset, int count) :如 String s = new String(arr, 2, 3),将字符数组 arr中的一部分转换为字符串
2` 静态方法:
- static String copyValueOf(char [] data );
- static String copyValueOf(char [] data, int offset, int count);
- static String ValueOf(char [] data ); (如: String s2 = String.valueOf(arr))
- static String ValueOf(char []data, int offset, int count );
[ ******将字符串,转换为字符数组 ]
- char[] toCharArray();
[ 将字节数,转换为字符串 ]
- String( byte[] );
- String( byte[] , int offset, int count); 将字节数组中的一部分,转换成字符串
[ 将字符串,转换为字节数组 ]
- byte[] getBytes();
[ 将基本数据类型,转换字符串 ]
- static String valueOf(int);
- static String valueOf(double);
- ....
// 3 + ""; // 得到 字符串 "3", 底层发生了 String.valueOf(3);
// 特殊: 字符串 和 字节数组 在转换过程中,是可以指定编码表的
2.4、String类常用方法 [ 替换]:
- String replace (oldChar, newChar); // 若替换的字符不存在,返回的是原字符串
2.5、String类常用方法 [ 切割]:
- String[] split ( regex );
2.6、String类常用方法 [ 字串,获取字符串中的一部分 ]:
- String substring (begin); // 若角标不存在,会出现字符串角标越界异常
- String substring (begin, end);
2.7、String类常用方法 [ 转换 ,去除空格、比较]:
- String toUpperCase (); // 将字符串转成大写
- String toLowerCase (); // 将字符串转成小写
- String trim(); // 将字符串两端的多个空格去除
- int compareTo (String str); // 对两个字符串进行自然顺序的比较
三、StringBuffer:字符串缓冲区,是一个容器
StringBuffer特点:1、长度是可变化的; 2、可以字节操作多个数据类型; 3、最终通过 toString 方法变成字符串。
[ C create 、U update 、R read 、D delete ] 增改读删
3.1、StringBuffer存储
- StringBuffer append(); // 将指定数据作为参数添加到已有的数据结尾处
- StringBuffer insert ( index, data); // 将数据 data 插入到指定 index 位置
3.2、StringBuffer删除
- StringBuffer delete (start, end); // 删除缓冲区的数据,包含 start位置的元素,不包含 end位置的元素
- StringBuffer deleteCharAt ( index); // 删除指定 index 位置的字符
3.3、StringBuffer获取
- char charAt(int index);
- int indexOf(String str);
- int lastIndexOf(String str);
- int length();
- String substring (int start, int end);
3.4、StringBuffer修改
- StringBuffer replace(int start, int end, String str);
- void setCharAt(int index, char ch);
3.5、StringBuffer反转
- StringBuffer reverse();
3.6、StringBuffer将缓冲区指定数据存储到指定字符数组中
- void getChars (int srcBegin, int srcEnd, char[] dst, int dstBegin)
注: JDK1.5版本之后出现了StringBuider
[ 区别 ]
- StringBuffer 是线程同步的
- StringBuider 是线程不同步的 (升级三因素: 1、提高效率; 2、简化书写; 3、提高安全性)
四、基本数据类型对象包装类
byte Byte short Short int Integer long Long boolean Boolean float Float double Double char Character
基本数据类型对象包装类的最常见作用:
用于基本数据类型和字符串类型之间做转换
[ 如:基本数据类型 转成 字符串 的方式如下 ]
- 基本数据类型 + "";
- 基本数据类型.toString(基本数据类型值); // Interger.toString(23); 将整数23转换成字符串"23"
[ 字符串 转成 基本数据类型 的方式如下 ]
格式为: xxx a = Xxx.parseXxx(String);
- int a = Integer.parseInt("35");
- double b = Double.parseDouble("35.68");
- boolean a = Boolean.parseBoolean("true");
- Integer i = new Integer("68");
- int num = i.inValue(); // 68
[ 十进制 转 其他进制 ]
- toBinaryString(); // 转换为二进制
- toHexString(); // 转换为八进制
- toOctalString(); // 转换为十六进制
[ 其他进制 转 十进制 ]
- parseInt(String str, radix);
[ JDK1.5以后的新特性 ] 自动装箱、自动拆箱
// Integer x = new Integer(4); 转变为 Interger x = 4; // 自动装箱, 等同于 new Integer(4);
x = x + 2; // 自动拆箱,变成 int 型,再和 2 进行加法运算 等同于 x = x.intValue() + 2;
// 特例 Integer a = 127; Integer b = 127; System.out.print(a == b); // true 因为 a 和 b 指向同一个 Integer 对象 数值在 byte(0 ~ 127) 范围内时,对于新特性,如果该值存在,则不再开辟新空间
Integer t = new Integer("123"); Integer t2 = new Integer(123); System.out.print( t == t2); // false 因为对比的是元素地址是否相等 System.out.print( t.equals(t2) ); // true 函数 equals 在底层将 t 用 intValue() 转换了,所以值相等
写给自己的随笔,有问题欢迎指出¯\_(ツ)_/¯