JAVA-线程

线程

进程 — 计算机中在执行的任务 — 在CPU上执行和计算。
线程 — 进程中的小任务 — 多线程
注意:一个核上往往只能执行一个进程中的一个线程。—计算机看起来像是在运行多个进程,实际上是因为在计算机中任务切换速度非常快,超过人的反应。— 进程的执行在宏观上并行的,在微观上是串行的。
jvm虚拟机本身也是个进程,可以通过代码在jvm虚拟机进程中开启多个线程,从而开发多线程的程序。

定义线程

  1. 写一个类继承Thread类,将要执行的逻辑放到run方法中,创建线程对象,调用start方法来启动线程执行任务
  2. 写一个类实现Runnable接口,重写run方法,创建Runnable对象,然后将Runnable对象作为参数传递到Thread对象中,利用Thread对象来启动线程
  3. 写一个类实现Callable接口,重写call方法。 — 现阶段要求知道就行
    通过观察发现,多个线程的执行的先后顺序并没有任何规律,谁抢到cpu谁就执行。

线程中的常用方法

static Thread currentThread() 获取当前正在执行的线程
long getId() 获取线程的编号,每个线程在执行的过程中,jvm都会为其分配独一无二的编号来唯一的进行表示,可以通过这个方法获取线程的id
String getName() 获取线程的名称,每个线程在执行的过程中,jvm都会为其分配一个名称,这个名称可以通过getName来获取
void setName(String name) 设置线程的名字
Thread.State getState() 获取线程的状态
void interrupt() 中断线程
static void sleep(long millis) 让当前线程进入休眠状态,被挂起,放弃执行权,不再抢夺cpu,持续指定的时间
int getPriority() 获取当前线程的优先级
void setPriority(int newPriority) 设置当前线程的优先级 优先级的取值范围为1~10 越大优先级越高 如果不设置 优先级为5

多线程的并发安全问题

多个线程同时执行,而多个线程在执行的时候是相互抢占资源导致出现了不合常理的数据的现象—多线程的并发安全问题
并发安全问题产生的条件:
多个线程参与
有共享资源
同时操作 且 对共享资源的修改
注意:多线程在执行的时候是相互抢占,而且抢占是发生在线程执行的每一步过程中

同步锁机制

利用synchronized — 同步代码块解决多线程并发安全问题
synchronized(lock){
需要同步的代码
}
同步 — 一段逻辑在同一时间只能有一个线程执行
异步 — 一段逻辑在同一时间能有多个线程执行
同步一定是安全的吗? — 是
安全一定是同步的吗? — 不一定
异步不一定不安全
不安全一定是异步的
注意:从微观上而言,同步一定是安全的,安全也一定是同步的。从宏观上,同步一定是安全的,安全不一定是同步的
需要一个锁对象 — 要求锁对象要被所有的线程都认识:共享对象,类的字节码(方法区是被线程所共享的),this(必须是同一个对象开启了多个线程)

扩展:
堆内存、方法区是被线程共享的
栈内存、本地方法栈、PC计数器是每一个线程所独有的

如果同步方法是一个非静态方法,那么以this作为锁对象;如果同步方法是一个静态方法,那么以当前的类作为锁对象
public synchronized void mx(){}
死锁 —多个共享资源
多个线程
同步嵌套 - 所谓的同步嵌套 就是多个syncronized代码块存在嵌套关系 这就意味着 会产生 持有一个资源 要求另一个资源的情况
由于锁之间相互嵌套并且锁对象不同导致线程之间相互锁死,致使代码无法继续往下 — 避免死锁:统一锁对象,减少锁的嵌套
活锁 — 这个资源没有被任何的线程持有占用,导致程序无法往下执行

共享内存

共享内存机制 通过 在多个线程都可以访问到的内存位置保存并访问数据 来进行信息的传递
一般用在 多个线程之间 需要 传递 属性 信息等场景下
案例:某一个线程通过控制一个布尔类型的信号量 控制另一个线程执行的流程
/**

  • 通过共享内存方式 在线程间通信
    */
class Ma implements Runnable{
   @Override
   public void run() {
   	System.out.println("慢悠悠的走到了比赛的起点。。。");
   	while(!Demo10.canRun){
   		try {
   			Thread.sleep(100);
   		} catch (InterruptedException e) {
   			e.printStackTrace();
   		}
   	}
   	System.out.println("跑出去。。。。");
   }
}
public class Demo10 {
   public static boolean canRun = false;
   public static void main(String[] args) throws Exception {
   	new Thread(new Ma()).start();
   	Thread.sleep(3000);
   	canRun = true;
   }
}

等待唤醒机制

wait,notify来调节线程的执行顺序。
注意:等待唤醒机制必须结合锁来使用,而且锁对象是谁就用谁进行等待唤醒。

练习:生产消费模型

一个线程表示生产者,一个线程表示消费者 — 商品 — 总数量不能超过1000
生产者生产了300个商品,消费者此次最多消费300个商品
消费者消费了130个商品,剩余170个商品
生产者生产了550个商品,消费者此次最多消费720个商品
消费者消费了50个商品,剩余670个商品
生产者生产了330个商品,消费者此次最多消费1000个商品
消费者消费了0个商品,剩余1000个商品
生产者生产了0个商品,消费者此次最多消费1000个商品

package thread;

public class WaitNotifyAllDemo {
   public static void main(String[] args) {

   	Product p = new Product();
   	new Thread(new Producer2(p)).start();
   	new Thread(new Producer2(p)).start();
   	new Thread(new Consumer2(p)).start();
   	new Thread(new Consumer2(p)).start();
   }
}
class Product {
   private int count;
   // 规定如果flag为TRUE,则表示生产商品
   // 规定如果flag为FALSE,则表示消费商品
   public boolean flag = true;
   public int getCount() {
   	return count;
   }
   public void setCount(int count) {
   	this.count = count;
   }
}
class Producer2 implements Runnable {
   private Product p;
   public Producer2(Product p) {
   	this.p = p;
   }
   @Override
   public void run() {
   	while (true) {
   		synchronized (p) {
   			while (p.flag == false)//被唤醒之后重新判断,把if改为while
   				try {
   					p.wait();
   				} catch (InterruptedException e) {
   					e.printStackTrace();
   				}
   			// 计算本次所能生产的最大数量
   			int max = 1001 - p.getCount();
   			// 计算本次实际的生产数量
   			int count = (int) (Math.random() * max);
   			// 计算本次提供的商品数量
   			p.setCount(count + p.getCount());
   			System.out.println("本次生产了" + count + "件商品,能提供" + p.getCount() + "件商品");
   			p.flag = false;
   			p.notifyAll();
   		}
   	}
   }
}
class Consumer2 implements Runnable {
   private Product p;
   public Consumer2(Product p) {
   	this.p = p;
   }
   @Override
   public void run() {
   	while (true) {
   		synchronized (p) {
   			while (p.flag == true)//被唤醒之后重新判断状态
   				try {
   					p.wait();
   				} catch (InterruptedException e) {
   					e.printStackTrace();
   				}
   			// 计算本次消费数量
   			int count = (int) ((p.getCount() + 1) * Math.random());
   			// 计算本次剩余商品数量
   			p.setCount(p.getCount() - count);
   			System.out.println("本次消费了" + count + "件商品,剩余了" + p.getCount() + "件商品");
   			p.flag = true;
   			p.notifyAll();
   		}
   	}
   }
}

sleep和wait方法的区别:

sleep是Thread对象上调用的,被设计在Thread类上,本身是一个静态方法
wait是在锁对象上调用的,被设计在Object类上,本身是一个成员方法。
sleep方法会设定一个超时时间时间结束后自动醒来 wait方法可以设定也可以不设定超时时间 可以通过notify或notifyAll唤醒 或如果设定过超时时间 在超时时间结束后自动醒来
wait方法会使当前线程进入挂起状态 会释放cpu 放弃执行权 不再抢夺cpu 释放了锁 sleep方法会使当前线程进入挂起状态 无锁状态下会释放cpu 放弃执行权 不再抢夺cpu 有锁状态下不会释放锁,不放弃执行权!!!

案例:实现一个多线程环境下的阻塞式队列的实现

import java.util.LinkedList;
/**
* 利用多线程等待唤醒机制 实现 多线程队列
*/
class ThreadQueue{
   //1.创建队列 作为内部存储 LinkedList本身线程不安全 需要考虑并发安全问题
   private LinkedList<Integer> queue = new LinkedList<>();
   //2.限定队列的最大大小
   private int len  = 5;
   /**
    * 向当前队列中增加元素
    * 优于内部的LinkedList线程不安全 操作此队列 需要考虑多线程并发安全问题 所以将方法设置为 同步方法
    * @param num
    */
   public synchronized void add(Integer num){
   	if(queue.size()<len){
   		//有空间 直接往里加
   		queue.add(num);
   		//唤醒 又可能存在的等待取数据 但是因为队列是空的而被挂起的线程
   		this.notify();
   	}else{
   		//没有空间 则挂起当前线程 等待有空间释放
   		try {
   			this.wait();
   		} catch (InterruptedException e) {
   			e.printStackTrace();
   		}
   		//当被唤醒 说明空间释放了出来 则将数据加入队列
   		queue.add(num);
   	}
   }
   public synchronized Integer poll(){
   	if(queue.size()>0){
   		//有元素 直接获取返回
   		Integer num = queue.poll();
   		//唤醒 有可能存在的等待存入但因为队列满了而被挂起的线程
   		this.notify();
   		return num;
   	}else{
   		//没有元素 则挂起当前线程 等待有新元素加入
   		try {
   			this.wait();
   		} catch (InterruptedException e) {
   			e.printStackTrace();
   		}
   		//被唤醒 说明有元素被加入了 从队列中取出元素返回
   		return queue.poll();
   	}
   }
}
public class Demo13 {
   public static void main(String[] args) {
   	ThreadQueue tq = new ThreadQueue();
   	
   	new Thread(new producer(tq)).start();
   	new Thread(new consumer(tq)).start();
   }
}

/**
* 消费者
* 不停的从队列中消费数据
*/
class consumer implements Runnable{
   private ThreadQueue tq = null;
   public consumer(ThreadQueue tq) {
   	this.tq = tq;
   }
   
   //--每隔3秒钟从队列中消费一个数据
   @Override
   public void run() {
   	while(true){
   		try {
   			Thread.sleep(1000);
   		} catch (InterruptedException e) {
   			e.printStackTrace();
   		}
   		int num = tq.poll();
   		System.out.println(Thread.currentThread().getName()+"从队列中消费数据。。"+num);
   	}
   }
}

/**
* 生产者
* 不停的向队列中写入数据
*/
class producer implements Runnable{
   private ThreadQueue tq = null;
   
   public producer(ThreadQueue tq) {
   	this.tq = tq;
   }
   
   //--每个两秒向队列中写入数据
   int num = 0;
   @Override
   public void run() {
   	while(true){
   		try {
   			Thread.sleep(2000);
   		} catch (InterruptedException e) {
   			e.printStackTrace();
   		}
   		tq.add(num);
   		System.out.println(Thread.currentThread().getName()+"向队列加入数据。。"+num);
   		num++;
   	}
   }
}

线程的状态

在这里插入图片描述在这里插入图片描述
线程有如下的几个状态
未启动:线程对象被创建出来,但是还没有执行start(),并未执行起来
冻结:线程启动了起来,有抢夺执行权限,参与抢夺cpu,但是未抢到cpu状态下,不能得到执行。
执行:线程启动了起来,抢到了cpu,在执行
挂起:线程启动了起来,但是通过sleep或wait进入挂起状态,没有执行权限,不再抢夺cpu,直到sleep结束或wait()被notify或notifyAll方法唤醒,重新进入冻结状态,开始抢夺cpu
结束:线程启动了起来,执行结束或被中断,则线程结束,释放占用的内存和cpu。

线程的优先级

线程的优先级:1-10
理论上,数字越大优先级越高,抢占到资源的概率就越大
实际上,相邻的两个优先级的差别非常不明显。如果优先级差到5个单位及以上,则结果会相对明显一点点

加入线程—join()

加入线程 当某个线程执行的时候 可以调用join将另外一个线程加入进来 则当前线程会释放执行cpu 并转让执行权 给加入进来的线程 直到加入进来的线程执行完成 当前线程才继续执行
1号线程在执行。。。
调用2号线程的join()
1号线程挂起 将执行权转让给2号线程
2号线程得到执行权 开始执行
直到2号线程执行结束 退出 将执行权再还给1号线程
1号线程才继续执行。。。

守护线程—setDaemon()

守护别的线程。只要被守护的线程结束,那么无论守护线程完成与否都会结束。
在线程中,一个线程要么是守护线程,要么是被守护的线程。当最后一个被守护的线程结束才会导致所有的守护线程结束 — GC

启动线程的情况

  1. 用户请求创建
  2. 系统自启
  3. 被其他线程启动

线程结束的情况

  1. 寿终正寝:线程执行完成之后自然结束
  2. 他杀:线程被其他请求强制结束
  3. 意外事故:线程执行过程中出现了异常或者错误
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值