什么是线程呢?
进程是由一个或者多个线程组成的,线程是进程最小的基本单位。
线程有什么特点?
1.抢占式运行 :给程序分配cpu,按照时间片来执行,单位时间片抢占执行
2.资源共享 :同一个进程,有多个线程,这个多线程是可以共享同一个数据
3.并行:真正的同时执行
4.并发:同时发生,轮流交替执行
线程的生命周期
1.线程的启动 start
2.线程可运行状态 抢占 等待CPU
3.线程运行状态 抢占和执行
4.线程的阻塞 wait sleep锁
5.线程消亡 destroy
创建线程的两种方式
1.继承Thread类,重新run方法 实例化Thread子类,然后启动一个线程start()
class MyThread1 extends Thread{
//1.一个是将一个类声明为`Thread`的子类
@Override
public void run() {
//2.重写run方法
for (int i = 1; i <=9 ; i++) {
for (int j = 1; j <=i; j++) {
System.out.print(i+"+"+j+"="+i*j+" ");
}
System.out.println();
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i <= 9; i++) {
for (int j = 0; j <=i ; j++) {
System.out.print("*");
}
System.out.println();
}
}
}
public class Demo1 {
public static void main(String[] args) {
MyThread1 myThread1 =new MyThread1();
//3.新建线程的实例
myThread1.start();
//4.启动线程 启动
MyThread2 myThread2 =new MyThread2();
myThread2.start();
}
}
2.实现Runnable接口,重写run方法实例化Runnable接口的实现类。然后作为参数给Thread
public class Demo2 {
public static void main(String[] args) {
new Thread(new Runnable() { //1.创建一个Thread线程。2.实现接口Runnable
@Override
public void run() { //3.重写方法
for (int i = 0; i < 1000; i++) {
System.out.println("第一个"+i);
}
}
}).start(); //4.重写方法
new Thread(new Runnable() { //1.创建一个Thread线程。2.实现接口Runnable
@Override
public void run() { //3.重写方法
for (int i = 0; i < 1000; i++) {
System.out.println("第二个"+i);
}
}
}).start(); //4.重写方法
}
}
线程的方法
构造方法:
Thread()
Thread(Runnable target)
分配一个新的Thread对象写法一
Thread(Runnable target, String name)
分配一个新的Thread对象。并且对这个线程起一个名字
普通方法
注意:优先级方法和sleep方法存在线程安全问题,不靠谱,真实开发中不会使用
线程同步和锁
当多个线程同时请求一个数据的时候,会导致数据不准确的情况。相互之间产生问题。
容易出现线程安全的问题。比如多个线程操作同一个数据,都打算修改商品的库存。
线程的同步真实的意思:让你"排队",几个线程之间要排队。一个一个对共享资源进行操作,等一个结束以后,另一个再进来操作。变量是唯一的和准确的
可以加锁
方法一:同步方法
同步方法:
public synchronized void eat () {
}
只能有一个线程进入到方法中,其他线程在方法的外面等待
方法二:同步代码块
同步代码块: 将一段代码放到synchronized 然后括起来,就会对这段代码加上锁
语法格式:
synchronized (this) {
}
java中的锁
synchronized 被成为隐式锁,会自动释放,是一个非公平的锁。
Lock锁 被成为显示锁,Lock需要主动解锁。
Lock锁 只能有一个线程进入到方法中,其他线程在方法的外面等待
使用时候可能会用到try catch finally用来保证解锁
需要主动解锁
他们两个锁都可以解决线程同步的问题。但是synchronized 更加强大,更加粒度化。更加灵活。
守护线程
守护线程是用来守护非守护线程的
非守护线程死,守护线程陪葬
问题:main方法中的非守护线程死亡,为什么控制台还会运行守护线程
主线程虽然结束了,守护线程不会立即结束
因为jvm虚拟机里面有两个线程,一个主线程一个垃圾回收线程。
垃圾回收线程没处理完,所以守护线程是会继续运行极短时间
死锁问题
死锁是一种状态,当两个线程互相持有对象所需要的资源的时候,这两个线程又不主动释放资源就会导致死锁。代码无法正常执行。这两个线程就会僵持住。
应用场景:并发场景,多个线程。线程之间在共享数据的时候 是互不想让的线程枷锁为了线程安全,但是物极必反
Object类下面的和线程相关的方法
wait和notify:
wait()方法和notify()方法调用时,需要保证同一个类 导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法。
class Message {
private String message;//信息
public Message(String message) {//有参构造 获取实例化的message的名字
this.message = message;
System.out.println(this.message);
}
public void setMessage(String message) {//有参无返方法 设置message对象的名字
this.message = message;
}
public String getMessage() { //无参无返 获取message对象的名字
return message;
}
}
class WaitThread implements Runnable { //线程类 等待线程
private Message message;
//把Message类当做属性 为了使用wait notify 时是一个对象调用的
public WaitThread(Message message) { //有参构造 把Message的message值传进去
this.message = message;
}
//等待线程抢到了
//等待线程睡了5秒 然后唤醒线程执行。 synchronized (message)
//message 对象从等待池中国去取的,结果发现没有 阻塞
//回到等待线程睡醒了以后开始 wait等待
@Override
public void run() { //重写run方法
String name = Thread.currentThread().getName(); //显示当前线程是哪个线程
System.out.println(name + "正在等待中....");
synchronized (message) { //上锁防止别的对象继续进入
try {
//当调用wait方法的时候,会自动释放锁,并将对象放到等待池中,让唤醒线程锁来操作这个对象
//
message.wait();//代码走到这一步 当前线程会等待!!!
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name +"被唤醒!!!Line51行的");
System.out.println(message.getMessage());
//验证唤醒线程是否来过,来过会显示修改过的message
}
}
}
//唤醒线程
class NotifyThread implements Runnable {
private Message message; //把Message类定义成属性 为了使用wait notify 时是一个对象调用的
public NotifyThread(Message message ) { //有参构造 把Message实例化的对象传参进来
this.message = message;
}
@Override public void run() {
try {
Thread.sleep(1000); //休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("唤醒线程已经执行");
synchronized (message) { //上锁防止别的对象继续进入
message.setMessage("我是修改之后的message对象");
//修改message,如果输出了,说明唤醒线程去过了等待池
//message.notify();
message.notifyAll();
//执行唤醒
}
}
}
public class Demo1 {
public static void main(String[] args) {
Message message = new Message("我是message对象"); //实例化Message
WaitThread waitThread = new WaitThread(message); //实例化普通类1
WaitThread waitThread1 = new WaitThread(message); //实例化普通类2
NotifyThread notifyThread = new NotifyThread(message); //实例化普通类3
new Thread(waitThread1, "wait2线程").start(); //普通类1转换成等待线程1
new Thread(waitThread, "wait1线程").start(); //普通类2转换成等待线程2
new Thread(notifyThread, "notify线程").start(); //普通类3转换为唤醒线程1
}
}
join方法
join的作用:让主线程等待、一直等到子线程结束后,主线程才会执行
底层是wait notify
问题:让线程1执行然后再接着线程2,怎么办
在主线1中实例化线程2并启动调用join()方法
线程的经典案例
卖家买车案例
汽车生产商(卖家)
收到买家的购车需求,如果有车直接购买,如果没有车,让买家等待(wait)等待生产出来之后,通知买家(唤醒notify)买家去提车
购车用户(买家)
向卖家提出购车需求,如果有车直接买,如果没车,进入等待状态(wait)
class Goods{ //商品类 可以看做生产者和消费者交流的通道
private String name; //商品名字
private double price; // 商品价格
private boolean isProduct; //是否需要生产
public Goods(String name, double price, boolean isProduct) {
this.name = name;
this.price = price;
this.isProduct = isProduct;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public boolean isProduct() {
return isProduct;
}
public void setProduct(boolean product) {
isProduct = product;
}
}
class Cusyomer implements Runnable{ //消费者线程
Goods goods;
public Cusyomer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while (true){ //ture 让消费者一直消费
synchronized (goods){ //锁定商品
if (!goods.isProduct()){ //判断商品是否有库存,是否需要生产 true无库存需要生产 fales有库存不需要生产
System.out.println("消费者买到了一辆"+goods.getName()+"价格为:"+goods.getPrice());
//买车
goods.setProduct(true);
//修改库存
}else { //无库存状态
try {
goods.wait(); //没库存需要等
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class Prodector implements Runnable{ //生产者线程
Goods goods;
public Prodector(Goods goods) { //有参构造 获取商品的当前的属性
this.goods = goods;
}
@Override
public void run() {
int count =0; //计数器,为下面的生产何种车做准备
while (true){ //让生产者一直生产
try {
Thread.sleep(2000); //让生产者睡觉,保证消费者线程先走
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (goods){ //锁定商品
if (goods.isProduct()) { //判断库存状态 true需要生产 fales不需要生产
if (count%2==0){ //偶数生产呜呜车
goods.setName("呜呜车"); //修改商品属性-名字
goods.setPrice(998); //修改商品属性-价格
}else {
goods.setName("滴滴车"); //修改商品属性-名字
goods.setPrice(688); //修改商品属性-价格
}
goods.setProduct(false); //生产好修改商品库存
System.out.println("汽车商生产了:"+goods.getName()+"价格为"+goods.getPrice());
count++; //计数器
goods.notify(); //通知消费者来购买
}else { //有库存状态,通知消费者去购买
try {
goods.wait(); //生产商有库存,陷入等待状态,等消费者购买
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
Goods goods = new Goods("小汽车",5.5,false); //实例化商品
new Thread(new Cusyomer(goods)).start(); //实例化消费者线程并启动
new Thread(new Prodector(goods)).start(); //实例化生产者线程并启动
}
}
线程池
线程池是一个容纳了多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程的对象的操作,无需反复创建线程而消耗更多的资源
问题:创建线程池的方式有几种
1.通过ThreadPoolExecutor手动创建线程池
2.通过Executor执行器自动创建线程池
问题:创建线程池的方法:
1.Executors.newFixedThreadPool创建一个固定大小的线程池,可控制并发线程数,超出的线程会在队列中等待。
2.Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
3.Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。
4.Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。
5.Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。
6.Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
7.ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数。
手动创建线程池的参数含义
最大线程数(MaximumPoolSize):线程池运行的最大线程数量,受属性CAPACITY的限制,最大为(2^29)-1(约5亿)
核心线程数(CorePoolSize):线程池中保持最小活动数的线程数量,并且不允许超时,除非调用allowCoreThreadTimeOut方法,这个时候最小值是0。
多余线程的存活时间(keepAliveTime):存活时间(Keep-alive times):空闲线程等待工作的超时时间(以纳秒为单位)如果当前线程池中的线程数超过了核心线程数,超出的部分线程如果空闲的时长大于存活时长,那么他们将会被终止运行。
时间单位(TimeUnit):TimeUnit 是存活时间的单位。
阻塞队列(BlockingQueue):任何实现了BlockingQueue接口的实现类都可以用来传输和保存提交的任务,阻塞队列的使用和线程池大小相关。
缓冲队列workQueue:当线程数超过了corePoolSize就会进入缓冲队列
线程工程ThreadFactory:用来创建线程。
AbortPolicy线程饱和策略:
ThreadPoolExecutor.AbortPolicy,默认的拒绝策略,简单粗暴,拒绝的时候直接抛RejectedExecutionException异常
ThreadPoolExecutor.CallerRunsPolicy,由调用者执行自身execute方法来运行提交进来的任务,从名字CallerRuns(调用者运行)中就可以看出。它会提供一个简单的反馈控制机制,这种策略将降低新任务被提交上来的速度。
ThreadPoolExecutor.DiscardPolicy,也很简单粗暴,直接丢弃任务,不抛异常。
ThreadPoolExecutor.DiscardOldestPolicy,DiscardOldest丢弃最早的任务,在队列头部也就是最新进入队列的任务会被丢弃,然后尝试提交新任务,如果提交失败会继续重复以上步骤。
也可以自己实现RejectedExecutionHandler接口,并重写rejectedExecution方法来自定义拒绝策略。