java多线程、线程同步与线程池
## 1. 线程的基本概念### 1.1 进程
任何的软件存储在磁盘中,运行软件的时候,OS使用IO技术,将磁盘中的软件的文件加载到内存,程序在能运行。
**进程的概念 :** 应用程序(typerpa,word,IDEA)运行的时候进入到内存,程序在内存中占用的内存空间(进程).
### 1.2 线程
线程(Thread) : 在内存和CPU之间,建立一条连接通路,CPU可以到内存中取出数据进行计算,这个连接的通路,就是线程.
一个内存资源 : 一个独立的进程,进程中可以开启多个线程 (多条通路)
并发: 同一个时刻多个线程同时操作了同一个数据
并行: 同一个时刻多个线程同时执行不同的程序
## 2. Java实现线程程序
今天之前的所有程序都有一个共性 : main启动之后,一条线走到底 (单线程)
### 2.1 java.lang.Thread类
一切都是对象,线程也是对象,Thread类是线程对象的描述类
- 实现线程程序的步骤 :
- 定义类继承Thread
- 子类重写方法run
- 创建子类对象
- 调用子类对象的方法start()启动线程
```java
//- 定义类继承Thread
//- 子类重写方法run
public class SubThread extends Thread {
public void run(){
for(int x = 0 ; x < 50 ;x++)
System.out.println("run..."+x);
}
}
```
```java
public static void main(String[] args) {
//创建线程程序
SubThread subThread = new SubThread();
//调用子类对象的方法start()启动线程
//启动线程,JVM调用方法run
subThread.start();
for(int x = 0 ; x < 50 ;x++)
System.out.println("main..."+x);
}
```
### 2.2 线程的内存图
![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b5WUHXfU-1637932370755)(img/线程的内存图.JPG)\]](https://img-blog.csdnimg.cn/39fa566d1a6641e3b32320360cc839ae.JPG?)
### 2.3 Thread类方法
- Thread类的方法 getName()返回线程的名字,返回值是String类型
```java
public class ThreadName extends Thread {
public void run (){
System.out.println("线程名字:: "+ super.getName());
}
}
```
```java
public static void main(String[] args) {
ThreadName threadName = new ThreadName();
//threadName.setName("旺财");
threadName.start();
ThreadName threadName1 = new ThreadName();
//threadName1.setName("小强");
threadName1.start();
}
```
- Thread类静态方法 : Thread currentThread()
- 静态调用,作用是返回当前的线程对象
- "当前" , 当今皇上. 本地主机
```java
//获取当前线程对象,拿到运行main方法的线程对象
Thread thread =Thread.currentThread();
System.out.println("name::"+thread.getName());
```
- Thread类的方法 join()
- 解释,执行join()方法的线程,他不结束,其它线程运行不了
```java
public static void main(String[] args) throws InterruptedException {
JoinThread t0 = new JoinThread();
JoinThread t1 = new JoinThread();
t0.start();
t0.join();
t1.start();
}
```
- Thread类的方法 static yield()
- 线程让步,线程把执行权让出
```java
public void run() {
for(int x = 0 ; x < 50 ;x++){
Thread.yield();
System.out.println(Thread.currentThread().getName()+"x.."+x);
}
}
```
## 3. Java实现线程程序
### 3.1 java.lang.Runnable接口
- 实现线程程序的步骤 :
- 定义类实现接口
- 重写接口的抽象方法run()
- 创建Thread类对象
- Thread类构造方法中,传递Runnable接口的实现类对象
- 调用Thread对象方法start()启动线程
```java
//- 定义类实现接口
// - 重写接口的抽象方法run()
public class SubRunnable implements Runnable{
@Override
public void run() {
for(int x = 0 ; x < 50 ;x++){
System.out.println(Thread.currentThread().getName()+"x.."+x);
}
}
}
```
```java
public static void main(String[] args) {
//创建接口实现类对象
Runnable r = new SubRunnable();
//创建Thread对象,构造方法传递接口实现类
Thread t0 = new Thread(r);
t0.start();
for(int x = 0 ; x < 50 ;x++){
System.out.println(Thread.currentThread().getName()+"x.."+x);
}
}
```
### 3.2 实现接口的好处
接口实现好处是设计上的分离效果 : 线程要执行的任务和线程对象本身是分离的.
继承Thread重写方法run() : Thread是线程对象,run()是线程要执行的任务
实现Runnable接口 : 方法run在实现类,和线程无关,创建Thread类传递接口的实现类对象,线程的任务和Thread没有联系, 解开耦合性
## 4. 线程安全
出现线程安全的问题需要一个前提 : 多个线程同时操作同一个资源
线程执行调用方法run,同一个资源是堆内存的
### 4.1 售票例子
火车票的票源是固定的,购买渠道在火车站买,n多个窗口
```java
/**
* 票源对象,需要多个线程同时操作
*/
public class Ticket implements Runnable {
//定义票源
private int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets > 0) {
try {
Thread.sleep(10);//线程休眠,暂停执行
}catch (Exception ex){}
System.out.println(Thread.currentThread().getName()+" 出售第" + tickets + "张");
tickets--;
}else
break;;
}
}
}
```
```java
public static void main(String[] args) {
Ticket ticket = new Ticket();
//创建3个窗口,3个线程
Thread t0 = new Thread(ticket);
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
t0.start();
t1.start();
t2.start();
}
```
解决线程的安全问题 : 当一个线程没有完成全部操作的时候,其它线程不能操作
![在这里插入图片描述](https://img-blog.csdnimg.cn/240bf80c2cdb484ea62f63f3620bbff9.JPG?)
### 4.2 同步代码块
同步代码块可以解决线程安全问题 : 格式synchronized关键字
```java
synchronized(任意对象){
//线程操作的共享资源
}
```
> 任意对象 : 在同步中这个对象称为对象锁,简称锁,官方的文档称为 对象监视器
>
同步代码块,如何保证线程的安全性.
- 同步代码块的执行原理 : 关键点就是对象锁
- 线程执行到同步,判断锁是否存在
- 如果锁存在,获取到锁,进入到同步中执行
- 执行完毕,线程出去同步代码块,讲锁对象归还
- 线程执行到同步,判断锁所否存在
- 如果锁不存在,线程只能在同步代码块这里等待,锁的到来
![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5LSFBZ4r-1637932370764)(img/同步的原理.JPG)\]](https://img-blog.csdnimg.cn/ea52491500fa4ed38f492da3cbb4694d.JPG?)
> 使用同步 : 线程要先判断锁,然后获取锁,出去同步要释放锁, 增加了许多步骤,因此线程安全运行速度慢. 牺牲性能,不能牺牲数据安全
>
### 4.3 同步方法
当一个方法中,所有代码都是线程操作的共享内容,可以在方法的定义上添加同步的关键字 synchronized , 同步的方法,或者称为同步的函数.
- 同步方法中有对象锁吗 , this对象
- 静态同步方法中有对象锁吗,锁对象是本类.class属性. 这个属性表示这个类的class文件的对象.
```java
@Override
public void run() {
while (true)
sale();
}
private static synchronized void sale(){
//synchronized (Ticket.class) {
if (tickets > 0) {
try {
Thread.sleep(20);//线程休眠,暂停执行
} catch (Exception ex) {
}
System.out.println(Thread.currentThread().getName() + " 出售第" + tickets + "张");
tickets--;
}
//}
}
```
## 5. 死锁
死锁程序 : 多个线程同时争夺同一个锁资源,出现程序的假死现象.
面试点 : 考察开发人员是否充分理解同步代码的执行原理
> 同步代码块 : 线程判断锁,获取锁,释放锁,不出代码,锁不释放
完成死锁的案例 : 同步代码块的嵌套
![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wpyfQ3h7-1637932370767)(img/死锁.JPG)\]](https://img-blog.csdnimg.cn/5092cfa6d98c4071a6ad629933bd56eb.JPG?)
- 死锁代码
```java
/**
* 实现死锁程序
*/
public class ThreadDeadLock implements Runnable{
private boolean flag ;
public ThreadDeadLock(boolean flag){
this.flag = flag;
}
@Override
public void run() {
while (true){
//同步代码块的嵌套
if (flag){
//先进入A锁同步
synchronized (LockA.lockA){
System.out.println("线程获取A锁");
//在进入另一个同步B锁
synchronized (LockB.lockB){
System.out.println("线程获取B锁");
}
}
}else {
//先进入B锁同步
synchronized (LockB.lockB){
System.out.println("线程获取B锁");
//再进入另一个同步锁A锁
synchronized (LockA.lockA){
System.out.println("线程获取A锁");
}
}
}
}
}
}
```
```java
public class LockA {
public static LockA lockA = new LockA();
}
```
```java
public class LockB {
public static LockB lockB = new LockB();
}
```
```java
public static void main(String[] args) {
ThreadDeadLock threadDeadLock = new ThreadDeadLock(true);
ThreadDeadLock threadDeadLock2 = new ThreadDeadLock(false);
new Thread(threadDeadLock).start();
new Thread(threadDeadLock2).start();
}
```
## 6. JDK5新特性Lock锁
JDK5新的特性 : java.util.concurrent.locks包. 定义了接口Lock.
Lock接口替代了synchronized,可以更加灵活
- Lock接口的方法
- void lock() 获取锁
- void unlock()释放锁
- Lock接口的实现类ReentrantLock
```java
/**
*优化为juc包的接口Lock
*/
public class Ticket implements Runnable {
//定义票源
privateint tickets = 100;
//获取Lock接口的实现类对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true)
sale();
}
private void sale(){
//获取锁
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(20);//线程休眠,暂停执行
} catch (Exception ex) {
}
System.out.println(Thread.currentThread().getName() + " 出售第" + tickets + "张");
tickets--;
}
//释放锁
lock.unlock();
}
}
```
## 7. 生产者与消费者
创建2个线程,一个线程表示生产者,另一个线程表示消费者
![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1qYsKQJg-1637994031437)(img/生产者与消费者.JPG)\]](https://img-blog.csdnimg.cn/4f46d120ac894ed28b19560177eda655.JPG?)
```java
/**
* 定义资源对象
* 成员 : 产生商品的计数器
* 标志位
*/
public class Resource {
int count ;
boolean flag ;
}
```
```java
/**
* 生产者线程
* 资源对象中的变量++
*/
public class Produce implements Runnable{
private Resource r ;
public Produce(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true){
synchronized (r) {
//判断标志位,是否允许生产
//flag是true,生产完成,等待消费
if (r.flag )
//无限等待
try{ r.wait();
}catch (Exception ex){}
r.count++;
System.out.println("生产第" + r.count + "个");
//修改标志位,已经生产了,需要消费
r.flag = true;
//唤醒消费者线程
r.notify();
}
}
}
}
```
```java
/**
* 消费者线程
* 资源对象中的变量输出打印
*/
public class Customer implements Runnable{
private Resource r ;
public Customer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true){
synchronized (r) {
//是否要消费,判断标志位 ,允许消费才能执行
if (!r.flag )
//消费完成,不能再次消费,等待生产
try{r.wait();}catch (Exception ex){}
System.out.println("消费第" + r.count);
//消费完成后,修改标志位,变成已经消费
r.flag = false;
//唤醒生产线程
r.notify();
}
}
}
}
```
```java
public static void main(String[] args) {
Resource r = new Resource();
//接口实现类,生产的,消费的
Produce produce = new Produce(r);
Customer customer = new Customer(r);
//创建线程
new Thread(produce).start();
new Thread(customer).start();
}
```
- 线程通信的方法 wait() notify()
- 方法的调用必须写在同步中
- 调用者必须是作为锁的对象
- wait(),notify()为什么要定义在Object类
- 同步中的锁,是任意对象,任何类都继承Object
- 案例改为方法实现
```java
/**
* 定义资源对象
* 成员 : 产生商品的计数器
* 标志位
*/
public class Resource {
private int count ;
private boolean flag ;
//消费者调用
public synchronized void getCount() {
//flag是false,消费完成,等待生产
if (!flag)
//无限等待
try{this.wait();}catch (Exception ex){}
System.out.println("消费第"+count);
//修改标志位,为消费完成
flag = false;
//唤醒对方线程
this.notify();
}
//生产者调用
public synchronized void setCount() {
//flag是true,生产完成,等待消费
if (flag)
//无限等待
try{this.wait();}catch (Exception ex){}
count++;
System.out.println("生产第"+count+"个");
//修改标志位,为生产完成
flag = true;
//唤醒对方线程
this.notify();
}
}
```
```java
/**
* 消费者线程
* 资源对象中的变量输出打印
*/
public class Customer implements Runnable{
private Resource r ;
public Customer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.getCount();
}
}
}
```
```java
/**
* 生产者线程
* 资源对象中的变量++
*/
public class Produce implements Runnable{
private Resource r ;
public Produce(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.setCount();
}
}
}
```
```java
//测试类
package thread.thread09;
public class ThreadTest {
public static void main(String[] args) {
// 接口实现类,生产者,消费者
Resource r = new Resource();
Produce produce = new Produce(r);
Customer customer=new Customer(r);
// 创建线程
new Thread(produce).start();
// Produce produce1=new Produce(r);
// Thread t1=new Thread(produce1);
// t1.start();跟上边效果相同
new Thread(customer).start();
}
}
```
### 7.1 安全问题产生
-线程本身就是一个新创建的方法栈内存 (CPU进来读取数据)
-线程的notify(),唤醒第一个等待的线程
- 解决办法 : 全部唤醒 notifyAll()
-被唤醒线程,已经进行过if判断,一旦醒来继续执行
- 线程被唤醒后,不能立刻就执行,再次判断标志位,利用循环
- while(标志位) 标志位是true,永远也出不去
```java
/**
* 定义资源对象
* 成员 : 产生商品的计数器
* 标志位
*/
public class Resource {
private int count ;
private boolean flag ;
//消费者调用
public synchronized void getCount() {
//flag是false,消费完成,等待生产
while (!flag)
//无限等待
try{this.wait();}catch (Exception ex){}
System.out.println("消费第"+count);
//修改标志位,为消费完成
flag = false;
//唤醒对方线程
this.notifyAll();
}
//生产者调用
public synchronized void setCount() {
//flag是true,生产完成,等待消费
while (flag)
//无限等待
try{this.wait();}catch (Exception ex){}
count++;
System.out.println("生产第"+count+"个");
//修改标志位,为生产完成
flag = true;
//唤醒对方线程
this.notifyAll();
}
}
```
```java
/**
* 生产者线程
* 资源对象中的变量++
*/
public class Produce implements Runnable{
private Resource r ;
public Produce(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.setCount();
}
}
}
```
```java
/**
* 消费者线程
* 资源对象中的变量输出打印
*/
public class Customer implements Runnable{
private Resource r ;
public Customer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.getCount();
}
}
}
```
```java
public static void main(String[] args) {
Resource r = new Resource();
//接口实现类,生产的,消费的
Produce produce = new Produce(r);
Customer customer = new Customer(r);
//创建线程
new Thread(produce).start();
new Thread(produce).start();
new Thread(produce).start();
new Thread(produce).start();
new Thread(produce).start();
new Thread(produce).start();
new Thread(customer).start();
new Thread(customer).start();
new Thread(customer).start();
new Thread(customer).start();
new Thread(customer).start();
new Thread(customer).start();
}
```
![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iTWfqKdT-1638013498865)(img/多生产与多消费.JPG)\]](https://img-blog.csdnimg.cn/51e7febcbbb64431a6a2433d47a9eb0d.JPG?)
### 7.2 线程方法sleep和wait的区别
- sleep在休眠的过程中,同步锁不会丢失 ,不释放
- wait()等待的时候,发布监视器的所属权, 释放锁.唤醒后要重新获取锁,才能执行
### 7.3 生产者和消费者案例性能问题
wait()方法和notify()方法, 本地方法调用OS的功能,和操作系统交互,JVM找OS,把线程停止. 频繁等待与唤醒,导致JVM和OS交互的次数过多.
notifyAll()唤醒全部的线程,也浪费线程资源,为了一个线程,不得以唤醒的了全部的线程.
### 7.4 Lock接口深入
Lock接口替换了同步synchronized, 提供了更加灵活,性能更好的锁定操作
- Lock接口中方法 :newCondition() 方法的返回值是接口 : Condition
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PHlspTLg-1638013498872)(img/线程阻塞队列.JPG)]
### 7.5 生产者与消费者改进为Lock接口
- Condition接口 (线程的阻塞队列)
- 进入队列的线程,释放锁
- 出去队列的线程,再次的获取锁
- 接口的方法 : await() 线程释放锁,进入队列
- 接口的方法 : signal() 线程出去队列,再次获取锁
>线程的阻塞队列,依赖Lock接口创建
```java
/**
*改进为高性能的Lock接口和线程的阻塞队列
*/
public class Resource {
private int count ;
private boolean flag ;
private Lock lock = new ReentrantLock();//Lock接口实现类对象
//Lock接口锁,创建出2个线程的阻塞队列
private Condition prod = lock.newCondition();//生产者线程阻塞队列
private Condition cust = lock.newCondition();//消费者线程阻塞队列
//消费者调用
publicvoid getCount() {
lock.lock();//获取锁
//flag是false,消费完成,等待生产
while (!flag)
//无限等待,消费线程等待,执行到这里的线程,释放锁,进入到消费者的阻塞队列
try{cust.await();}catch (Exception ex){}
System.out.println("消费第"+count);
//修改标志位,为消费完成
flag = false;
//唤醒生产线程队列中的一个
prod.signal();
lock.unlock();//释放锁
}
//生产者调用
publicvoid setCount() {
lock.lock();//获取锁
//flag是true,生产完成,等待消费
while (flag)
//无限等待,释放锁,进入到生产线程队列
try{prod.await();}catch (Exception ex){}
count++;
System.out.println("生产第"+count+"个");
//修改标志位,为生产完成
flag = true;
//唤醒消费者线程阻塞队列中年的一个
cust.signal();
lock.unlock();//释放锁
}
}
```
测试类:
```java
package thread.thread11;
public class test {
public static void main(String[] args) {
Res res = new Res();
Pro pro = new Pro(res);
Cust cust = new Cust(res);
Thread t0 = new Thread(pro);
t0.setName("生产者1号");
Thread t1 = new Thread(pro);
t1.setName("生产者2号");
Thread t2 = new Thread(pro);
t2.setName("生产者3号");
Thread t3=new Thread(cust);
t3.setName("消费者1号");
Thread t4=new Thread(cust);
t4.setName("消费者2号");
Thread t5=new Thread(cust);
t5.setName("消费者3号");
t0.start();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
```
### 7.6 Lock锁的实现原理
使用技术不开源,技术的名称叫做轻量级锁
使用的是CAS锁 (Compare And Swap) 自旋锁
JDK限制 : 当竞争的线程大于等于10,或者单个线程自旋超过10次的时候
JDK强制CAS锁取消.升级为重量级锁 (OS锁定CPU和内存的通信总线)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i7BL35SY-1638013498875)(img/CAS锁.JPG)]
## 8. 单例设计模式
设计模式 : 不是技术,是以前的人开发人员,为了解决某些问题实现的写代码的经验.
所有的设计模式核心的技术,就是面向对象.
Java的设计模式有23种,分为3个类别,创建型,行为型,功能型
### 8.1 单例模式
要求 : 保证一个类的对象在内存中的唯一性
- 实现步骤
- 私有修饰构造方法
- 自己创建自己的对象
- 方法get,返回本类对象
```java
/**
* - 私有修饰构造方法
* - 自己创建自己的对象
* - 方法get,返回本类对象
*/
public class Single {
private Single(){}
//饿汉式
private static Single s = new Single(); // 自己创建自己的对象
// 方法get,返回本类对象
public static Single getInstance(){
return s;
}
}
```
```java
public static void main(String[] args) {
//静态方法,获取Single类的对象
Single instance = Single.getInstance();
System.out.println("instance = " + instance);
}
```
实现步骤
- 私有修饰构造方法
- 创建本类的成员变量, 不new对象
- 方法get,返回本类对象
```java
/**
* - 私有修饰构造方法
* - 创建本类的成员变量, 不new对象
* - 方法get,返回本类对象
*/
public class Single {
private Single(){}
//懒汉,对象的延迟加载
private static Single s = null;
public static Single getInstance(){
//判断变量s,是null就创建
if (s == null) {
s = new Single();
}
return s;
}
}
```
### 8.2 懒汉式的安全问题
一个线程判断完变量 s=null,还没有执行new对象,被另一个线程抢到CPU资源,同时有2个线程都进行判断变量,对象创建多次
```java
public static Single getInstance(){
synchronized (Single.class) {
//判断变量s,是null就创建
if (s == null) {
s = new Single();
}
}
return s;
}
```
性能问题 : 第一个线程获取锁,创建对象,返回对象. 第二个线程调用方法的时候,变量s已经有对象了,根本就不需要在进同步,不要在判断空,直接return才是最高效的.双重的if判断,提高效率Double Check Lock
```java
private static volatile Single s = null;
public static Single getInstance(){
//再次判断变量,提高效率
if(s == null) {
synchronized (Single.class) {
//判断变量s,是null就创建
if (s == null) {
s = new Single();
}
}
}
return s;
}
```
### 8.3 关键字volatile
成员变量修饰符,不能修饰其它内容
- 关键字作用 :
- 保证被修饰的变量,在线程中的可见性
- 防止指令重排序
- 单例的模式, 使用了关键字,不使用关键字,可能线程会拿到一个尚未初始化完成看的对象(半初始化)
```java
public class MyRunnable implements Runnable {
private volatile boolean flag = true;
@Override
public void run() {
m();
}
private void m(){
System.out.println("开始执行");
while (flag){
}
System.out.println("结束执行");
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
```
```java
public static void main(String[] args) throws InterruptedException {
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
Thread.sleep(2000);
//main线程修改变量
myRunnable.setFlag(false);
}
```
## 9. 线程池ThreadPool
线程的缓冲池,目的就是提高效率. new Thread().start() ,线程是内存中的一个独立的方法栈区,JVM没有能力开辟内存空间,和OS交互.
JDK5开始内置线程池
### 9.1 Executors类
- 静态方法static newFixedThreadPool(int 线程的个数)
- 方法的返回值ExecutorService接口的实现类,管理池子里面的线程
- ExecutorService接口的方法
- submit (Runnable r)提交线程执行的任务
### 9.2 Callable接口
实现多线程的程序 : 接口特点是有返回值,可以抛出异常 (Runnable没有)
抽象的方法只有一个, call
启动线程,线程调用重写方法call
- ExecutorService接口的方法
- submit (Callable c)提交线程执行的任务
-Future submit()方法提交线程任务后,方法有个返回值 Future接口类型
-Future接口,获取到线程执行后的返回值结果
```java
public class MyCall implements Callable<String> {
public String call() throws Exception{
return "返回字符串";
}
}
```
```java
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池,线程的个数是2个
ExecutorServicees =Executors.newFixedThreadPool(2);
//线程池管理对象service,调用方法啊submit提交线程的任务
MyRunnable my = new MyRunnable();
//提交线程任务,使用Callable接口实现类
Future<String> future = es.submit(new MyCall());//返回接口类型 Future
//接口的方法get,获取线程的返回值
String str = future.get();
System.out.println("str = " + str);
// es.submit(my);
// es.submit(my);
// es.submit(my);
// es.shutdown();//销毁线程池
}
```
## 10. ConcurrentHashMap
ConcurrentHashMap类本质上Map集合,键值对的集合.使用方式和HashMap没有区别.
凡是对于此Map集合的操作,不去修改里面的元素,不会锁定
## 11. 线程的状态图-生命周期
在某一个时刻,线程只能处于其中的一种状态. 这种线程的状态反应的是JVM中的线程状态和OS无关. Zhili.An 发表于 2021-11-28 17:15
看的头昏脑涨,我用安卓写的时候有个问题。在oncreate里面的new Thread线程中循环 break了之后,不执行new ...
线程A里的代码和线程所属方法中的代码B不会同步执行的,想同步执行,建议把B也做成线程,然后A线程和B线程中间用A.join();去做关联 看的头昏脑涨,我用安卓写的时候有个问题。在oncreate里面的new Thread线程中循环 break了之后,不执行new线程外的代码 。烦恼 好帖{:1_921:},讲得很详细。一直想好好学学Java。 感谢分享。 收藏了,静心后再看,自学真的很费力,这个线程虽然知道大致意思了,用起来太吃力了,消化不好 学到了,写的真不错{:1_893:} 好帖,讲得很详细。 高级干货 感谢分享。。学习
页:
[1]
2