Java:多线程
多线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
并发和并行
并发:在同一时刻,有多个指令在单个CPU上交替执行
并行:在同一时刻,有多个指令在多个CPU上同时执行
实现方式
1·继承Thread类的方式进行实现
*多线程的第一种启动方式:
1.自己定义一个类继承Thread
2.重写run方法
3.创建子类的对象,并启动线程
2·实现Runable接口的方式进行实现
多线程的第二种启动方式:
1.自己定义一个类实现Runnable接口
2.重写里面的run方法
3.创建自己的类的对象
4.创建一个Thread类的对象,并开启线程
3·利用Callable接口和Future接口方式实现
1.创建一个类Mycallable实现callable接口
2.重写call(是有返回值的,表示多线程运行的结果)
3.创建Mycallable的对象(表示多线程要执行的任务)
4.创建Futureask的对象(作用管理多线程运行的结果)
5.创建Thread类的对象,并启动(表示线程)
三种方法的区别
Thread中常见的成员方法
线程优先级从1到10; 越低优先级越高
守护线程setDaemon
当其他非守护线程结束时候,守护线程就会陆续结束
出让线程/礼让线程
如果运行两个MyThread类对象则会,会使结果更加均匀,当运行到Thread.yield();就会把使用权让出去,重新分配
插入线程/插队线程
插入线程在某个线程中调用其他线程的jion方法,就会先把其他线程执行完
线程的生命周期
线程安全问题
在多个线程操作同一个数据时有时候会出现问题,问题是由线程执行顺序的随机引起的。
例如:
构造一类用来实现多个窗口的售票
再创建多个窗口来进行多个线程同时售票
结果出现了重复票和顺序和多票的问题,因为每个线程执行到每一步时候,执行权都有可能会被其它线程抢走,导致出现混乱
这时可以用同步代码块,让每一个线程都要执行完某一步骤执行权力才能让出去
同步代码块
把操作共享数据的代码锁起来
锁对象可以是任意的,但一定是唯一的
例如:static Object obj = new Object();(唯一的静态类)
MyThread.class (字节码文件对象,一定是唯一的)
就可以当作锁
特点1:锁默认打开,有一个线程进去了,锁自动关闭
特点2:当里面的代码都执行完毕,线程出来,锁自动打开,线程才可以进去
同步方法
就是把synchronized关键字加到方法上
格式:
特点1:同步方法是锁住方法里面所有的代码
特点2:锁对象不能自己指定:1·如果是静态方法锁对象就是当前方法的调用者:this 2·如果是静态方法,锁对象就是当前类的字节码文件对象
Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题
但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法
void lock();获得锁
void unlock();释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化ReentrantLock的构造方法
ReentrantLock(); 创建一个ReentrantLock的实例
死锁
是一种错误,当线程A拿到A锁的同时,线程B也拿到B锁了,就会导致A、B锁都锁了,线程A停在达到B锁这里,而线程B也无法拿到A锁
等待唤醒机制(生产者和消费者)
生产者和消费者模式是一个十分经典的多线程协作的模式
Java中的等待唤醒机制是一种线程间的通信方式,通过 Object 的 wait() 、 notify() 和 notifyAll() 方法来实现。
- 当一个线程执行到 wait() 方法时,它会释放当前持有的对象锁,并进入等待状态,直到其他线程调用 notify() 或 notifyAll() 方法来唤醒它。
- notify() 方法会随机唤醒一个在该对象上等待的线程,而 notifyAll() 方法则会唤醒所有在该对象上等待的线程。被唤醒的线程不会立即执行,而是要等到调用 notify() 或 notifyAll() 方法的线程释放锁后,才会去竞争锁,获取到锁后才能继续执行。
用阻塞队列实现
线程的六种状态
线程池
主要原理:
1·创建一个池子,池子中是空的
2·提交任务时,池子就会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
3·但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
Executors:线程池的工具类通过调用方法返回不用类型的线程池对象。
1·创建一个没有上线的线程池
2·创建一个最多可以创建3个线程的线程池
自定义线程池
安排任务的顺序
1·先使用核心线程的数量来运行任务
2·诺核心线程数量小于要运行的任务,剩余任务则在阻塞队列排队
3·诺还添加任务到阻塞队列都装不下了,就会将后面装不下的任务交给:“线程池中最大线程数量 - 核心线程数量”的线程处理。也就是说后面的任务会先被其他线程处理(除了核心线程的),而阻塞队列中的任务就接着排队
4·诺线程池中最大的线程数量都已经被使用了,那么就会抛弃再加入的任。
5·诺“线程池中最大线程数量 - 核心线程数量”,已经被弃用“用TimeUnit”指定的时间那么就会从线程池中删除
线程池的拒绝策略
线程池大小选择
1·CPU密集型运算
CPU运行比较多,读取文件较少:最大并行数+1
2·I/O密集型运算
读取文件较多: