Java基础知识(五) 多线程(区别线程与进程):线程的两种创建方式、状态、线程间通信; 字符串String、StringBuffer、基本数据类型对象包装等

注意,下面即将出现一大波文字解释(可怕)

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、线程的状态

线程的状态:

  1. 被创建;
  2. 运行;
  3. 冻结;(放弃执行资格)
  4. 消亡。

注: 还有一种临时状态,其特点具备执行资格,但不具备执行权。

  • 多线程具备随机性,因为 cpu 不断的快速切换造成。
  • 可能产生多线程的安全问题原因:
  1. 多线程代码中操作的共享数据;
  2. 多条语句操作该共享数据。

注: 当具备以上两个关键点时,有一个线程对多条,操作共享数据的代码执行的一部分,还未执行完,另一个线程开始参与执行,就会发生数据错误,即发生 同步(下一点详细解释)


  • 对应的多线程安全问题原因解决方法:

当一个线程在执行多条操作共享数据代码时,其他线程即使获取了执行权,也不可以参与操作。

  • 多线程的 同步: 将部分操作功能数据的代码进行加锁 (如 火车上的卫生间, emmm 哈哈)
  • 同步的表现形式:
  1. 同步代码块 (synchronized);
  2. 同步函数。

     


  • 同步表现形式的区别:
  1. 同步代码块: 使用的锁匙任意对象(如: this、Object、Demo.class)
  2. 同步函数: 使用的锁就是 this。

注: static 的同步函数,使用的锁匙 类名.class, 即该类的字节码对象,涉及单例设计模式的懒汉式


  • 同步的好处: 解决线程的安全问题
  • 同步的弊端: 较为消耗资源;  同步发生嵌套后,容易变成死锁

  • 同步的前提(以下两个条件都需要满足): 
  1. 必须是两个或两个以上的线程;
  2. 必须是多线程使用同一个锁。

注: 死锁代码需要会写,但开发时要避免

 

1.4、线程间通信: 其实是多个线程在操作同一资源,但操作的动作不同。

  • 常见例子 -- 生产者、消费者问题
  • 当多个生产者、消费者出现时,需要获取执行权的线程判断标记,通过 while 完成;
  • 需要将对方的线程唤醒,仅用 notify 是不可以的,因为可能出现只唤醒本方,可能导致所有线程都等待,所以可以通过 notifyAll 的形式来完成。
  • 以下三种方法,都使用在同步中,因为需要对持有监视器( 锁 )的线程操作,所以需要使用在同步中,因为只有同步才具有锁。

wait() 、 notify() 、 notifyAll() 


  • 为什么这些操作线程的方法,定义在 Object 类中呢?
  1. 因为它们操作同步中的线程时,必须要标识它们所操作的锁,只有同一锁上的被等待线程,可以被同一锁上 notify 唤醒,不可以对不同锁中的线程进行唤醒, 即 等待 和 唤醒 必须是同一个锁。
  2. 而 锁 可以是任意对象,所以,可以被任意对象调用的方法,定义在 Object 类中。

注:以上操作的弊端,每次 notifyAll 时,都会唤醒本方,可以只唤醒 对方吗? 

  1. JDK 1.5 版本提供了一些新的对象,优化了 等待唤醒机制:
  2. 将 synchronized 替换成 Lock 接口,将隐式锁升级为显示锁;  Lock 常用方法: 获取锁 Lock()、释放锁 unLock()、获取Condition 对象 new Condition();(注意,释放动作一定要执行,所以通常定义在 finally 中)
  3. 将 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 的区别
  1. wait : 释放 cpu 执行权,释放同步中的锁;
  2. sleep: 释放 cpu 执行权,不释放同步锁。
// 同步锁示例代码

synchronize (锁) {
    wait();
}
  • stop 停止线程的原理: 是 run 方法结束, run 方法中通常定义循环,指定控制循环线程,即可结束。
  • stop 停止线程的过程: 
  1. 定义结束标记;
  2. 当线程处于冻结状态,没有执行标记,程序无法结束,此时可以循环,正常退出冻结状态 或 强制结束冻结状态。

注: 强制结束冻结状态interrupt(): 目的是线程强制,从冻结状态恢复到运行状态,但会发生 InterruptedException 异常。

  • 线程中常见的一些方法:
  1. setDaemon(boolean):将线程标记为后台线程,后台线程与前台线程一样开启,一样抢执行权运行,只有结束时有区别,当前台线程(如 main 线程)都运行结束后,后台线程会自动结束;
  2. 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

[ 区别 ]

  1. StringBuffer 是线程同步的
  2. StringBuider 是线程不同步的 (升级三因素: 1、提高效率; 2、简化书写; 3、提高安全性)

 

四、基本数据类型对象包装类

byte      Byte
short     Short
int       Integer
long      Long
boolean   Boolean
float     Float
double    Double
char      Character

基本数据类型对象包装类的最常见作用: 

用于基本数据类型和字符串类型之间做转换

[ 如:基本数据类型 转成 字符串 的方式如下 ]

  1. 基本数据类型 + "";
  2. 基本数据类型.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

[ 十进制 转 其他进制 ]

  1. toBinaryString(); // 转换为二进制
  2. toHexString();  // 转换为八进制
  3. 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() 转换了,所以值相等

 

 

 

写给自己的随笔,有问题欢迎指出¯\_(ツ)_/¯

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值