Javase-day17-线程

一、多线程

回到目录

1、并发与并行

并发: 两个或多个事件在同一时间段内发生(交替执行);
并行: 两个或多个事件在同一时间段内发生(同时运行);

它们虽然都说是"多个进程同时运行",但是它们的"同时"不是一个概念:
并发的"同时"是经过上下文快速切换,使得看上去多个进程同时都在运行的现象,是一种OS欺骗用户的现象;
并行的"同时"是同一时刻可以多个进程在运行(处于running)。

二者具体的区分参考

2、进程与线程

进程: 是正在进行资源分配和调用的独立单位;
    是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源。

线程: 是进程中的单个顺序控制流,是一条执行路径;

  • 单线程: 一个进程如果只有一条执行路径,则称为单线程程序;
  • 多线程: 一个进程如果有多条执行路径,则称为多线程程序。

二、多线程的实现

回到目录

java.lang.Thread类是程序中执行的线程,JVM允许应用程序同时执行多个线程

1、创建多线程方式一:定义一个子类继承Thread类,这个子类应该重写Thread类的run方法

Thread类中的 start( ) 方法,才可以使线程开始执行,使JVM调用此线程的run方法。
直接调用run方法,没有用
在这里插入图片描述

// MyThread类继承Thread类:
public class MyThread extends Thread {
	@Override 		// 重写run方法
	public void run() {
		// 打印1~99
		for(int i=0; i<100; i++){
			System.out.println(i);
		}
	}
}

// 创建MyThread对象
public class MyThreadDemo {
	public static void main(String[] args) {
		MyThread my1 = new MyThread();
		MyThread my2 = new MyThread();
		
		// 启动线程,调用start方法
		my1.start();
		my2.start();
	}
}

2、设置和获取线程名称

在这里插入图片描述
在这里插入图片描述

在Thread类中,有一个静态方法,static Thread currentThread()———返回对当前正在执行的线程对象的引用。
main方法是在一个叫做main的线程中执行的。

在这里插入图片描述

3、线程调度

在这里插入图片描述
在这里插入图片描述

4、线程控制

  • 线程控制用到的方法
    在这里插入图片描述
  • 演示

sleep()方法

在这里插入图片描述

join()方法

在这里插入图片描述

setDaemon()方法

在这里插入图片描述

5、线程的生命周期

在这里插入图片描述

6、创建多线程方式二:声明一个实现Runnable接口的类

在这里插入图片描述
在这里插入图片描述
相比继承Thread类,实现Runnable接口创建多线程的好处:

  • 避免了Java单继承的局限性(实现类将来还可以有一个直接父类);
  • 增强了程序的扩展性,降低了程序的耦合性(解耦)
    适合多个相同的程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想。

7、匿名内部类方式实现线程创建

在这里插入图片描述
在这里插入图片描述

三、线程安全

回到目录

1、线程安全问题的出现

在这里插入图片描述
代码实现:3个窗口,卖100张票。
在这里插入图片描述

// SellTicket实现类
public class SellTicket implements Runnable {
    private int tickets = 100;

    @Override
    public void run() {
        while (true) {
            if (tickets > 0) {
                // 通过sleep()方法来模拟出票时间
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                tickets--;
            }
        }
    }
}

// 测试类
public class SellTicketDemo {
    public static void main(String[] args){
        SellTicket st = new SellTicket();
        // 创建三个Thread类对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");
        // 启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

上述代码,会产生线程安全问题
在这里插入图片描述

2、判断多线程程序是否有数据安全问题的标准

  • 是否是多线程环境(单线程不会出现线程安全问题);
  • 是否共享数据(如果是多线程,但是没有共享数据,不会出现线程安全问题);
  • 是否有多条语句操作共享数据(如果只有一条语句来控制共享数据,不会有线程安全问题)

上面的代码,三个条件都满足了,存在线程安全问题。

3、解决线程安全问题的三种方法

  • 把多条语句操作共享数据的代码锁起来,让任意时刻,只能有一个线程执行
  • Java提供同步代码块的方式来解决

同步代码块

在这里插入图片描述
在这里插入图片描述
运行测试类,控制台输出没出现线程安全的毛病。

同步方法

  • 同步方法

就是把synchronized关键字加到方法上

格式:
修饰符 synchronized 返回值类型 方法名(方法参数){
   可能会产生线程安全的代码
}

同步方法的【锁对象】:this(this是创建对象之后产生的,静态方法优先于对象,所以静态方法就不能用this)

  • 同步静态方法(比较特殊)

静态与对象无关,只与类有关;静态只能调用静态的东西

格式:
修饰符 static synchronized 返回值类型 方法名(方法参数){
    可能会产生线程安全的代码
}

同步静态方法中的【锁对象】是:本类名称.class(class文件对象,反射)
在这里插入图片描述

Lock锁

java.util.concurrent.locks.Lock是一个接口,该接口提供了比synchronized代码块和synchronized方法更广泛的锁定操作

Lock锁也称为同步锁,加锁与释放锁方法化了,Lock接口中的方法:

  • public void lock() : 加同步锁。
  • public void unlock() : 释放同步锁。

java.util.concurrent.locks.ReentrantLock  implements  Lock接口

在这里插入图片描述
在这里插入图片描述

四、线程状态

回到目录
  线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在API中java.lang.Thread.State这个枚举中给出了六种线程状态:
在这里插入图片描述

1、Timed Waiting(计时等待)

  Timed Waiting状态,在API中的描述为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。就相当于是延时,之前重写run方法中的 sleep(1000); 表示延时(或者说是睡眠)1s,也就是Timed Waiting(计时等待)状态。

  • 进入Timed Waiting状态的一种常见情形是调用sleep方法,单独的线程也可以调用;
  • 将Thread.sleep()方法的调用放在线程run()方法里面,可以保证该线程执行过程中会睡眠;
  • sleep()与锁无关,线程睡眠到期会自动苏醒,并返回Runnable(可运行)状态。

Thread Waiting(计时等待)线程状态图:
在这里插入图片描述

2、Blocked(锁阻塞)

  Blocked(锁阻塞)状态,在API中的介绍为:一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态。

解释:线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。

Blocked 线程状态图:
在这里插入图片描述

3、Waiting(无限等待)

  Waiting(无限等待)状态,在API中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。

  • 在对象上的线程调用了Object.wait()会进入WAITING状态,直到另一个线程在这个对象上调用了Object.notify()或Object.notifyAll()方法才能恢复;
  • 一个调用了Thread.join()的线程会进入WAITING状态直到一个特定的线程来结束

五、生产者消费者模式—等待唤醒机制

1、线程间通信

线程间通信:指多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。

2、生产者消费者模型

在这里插入图片描述

3、实现生产者消费者模型

思路:
在这里插入图片描述
实现:

Box类:
public class Box {
    // 定义成员变量,表示第x瓶奶
    private int milk;
    // 定义一个成员变量,表示奶箱有无牛奶的状态
    private boolean state = false;

    // 提供存储牛奶和获取牛奶的操作
    public synchronized void put(int milk) {
        // 如果有牛奶(state = true),等待消费
        if (state) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 如果没有牛奶,就生产牛奶
        this.milk = milk;
        System.out.println("送奶工将第" + this.milk + "瓶奶放入奶箱");

        //生产完毕之后,修改奶箱的状态
        state = true;

        // 还要唤醒其他等待的线程
        notifyAll();
    }

    public synchronized void get() {
        // 如果没有牛奶,就生产牛奶
        if(!state){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 如果有牛奶,就消费牛奶
        System.out.println("用户拿到第" + this.milk + "瓶奶");
        System.out.println("==============================");

        // 消费完之后,修改牛奶状态
        state = false;

        // 唤醒其他等待的线程
        notifyAll();
    }
}

在这里插入图片描述
结果:
在这里插入图片描述

六、线程池

回到目录

  之前,我们使用线程的时候就去创建一个线程,虽然非常简单,但是会存在着问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率(频繁创建线程、销毁线程都要时间)。

1、线程池概述

线程池: 线程池就是线程的集合,线程池集中管理线程,以实现线程的重用,降低资源消耗,提高响应速度等。

在JDK1.5之后,JDK就内置了线程池,可以直接使用,底层原理是一个队列。

合理利用线程池能够带来三个好处:

  • 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

2、线程池的使用

Executors类,提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口。java并发编程:Executor、Executors、ExecutorService
在这里插入图片描述

线程池的使用步骤:
1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool,生产一个指定线程数量的线程池;
2.创建一个类,实现Runnable接口,重写run方法,设置线程任务;
3.调用ExecutorService中的submit方法,传递线程任务(实现类),开启线程,执行run方法;
4.调用ExecutorService中的shutdown方法,销毁线程池(不建议执行)

代码实现:
在这里插入图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值