多线程
一、相关概念
1、程序:
是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。
2、进程:
是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。-------生命周期
3、线程:
进程可进一步细化为线程,是一个程序内部的一条执行路径
线程分为用户线程和守护线程
**即:线程《线程(一个程序可以有多个线程)
程序:静态的代码 进程:动态执行的程序
线程:进程中要同时干几件事时,每一件事的执行路径成为线程。**
4、并行:
多个CPU同时执行多个任务,比如:多个人同时做不同的事
5、并发:
一个CPU(采用时间片)同时执行多个任务,比如秒杀平台,多个人做同件事
6、线程的相关API
//获取当前线程的名字
Thread.currentThread().getName()
1.start():1.启动当前线程2.调用线程中的run方法
2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3.currentThread():静态方法,返回执行当前代码的线程
4.getName():获取当前线程的名字
5.setName():设置当前线程的名字
6.yield():主动释放当前线程的执行权
7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
8.stop():过时方法。当执行此方法时,强制结束当前线程。
9.sleep(long millitime):线程休眠一段时间
10.isAlive():判断当前线程是否存活
小结:
线程就是独立的执行路径。
在程序执行时,即使没有自己创建线程,后台也会有多个线程,如:主线程、gc线程。
main()称为主线程,为系统的入口,用于执行整个程序。
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,先后顺序不可干预。
对同一个资源操作时,会存在资源抢夺问题,需要加入并发控制。
线程会带来额外开销,如cpu调度时间、并发控制开销。
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
二、多线程的三种创建方式
1、继承Thread类【重点】
不建议使用,避免OOP单继承局限性。
(1)自定义线程类继承Thread类
(2)重写run()方法,编写线程执行体
(3)创建线程对象,调用start()方法启动线程
public class MyThreadDemo extends Thread {
public static void main(String[] args) {
for (int i = 0; i < 200; i++) {
MyThreadDemo myThreadDemo = new MyThreadDemo();
myThreadDemo.start();
System.out.println("这个是主线程_" + i);
}
}
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(("我是run()线程" + i));
}
}
}
2、实现Runnable接口【重点推荐使用】
方便同一个对象被多个线程操作。
(1)自定义线程类实现Runnable接口
(2)实现run()方法,编写线程执行体
(3)创建线程对象,调用start()方法启动线程
public class MyThreadRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("run()方法计数_" + i);
}
}
public static void main(String[] args) {
MyThreadRunnable myThreadRunnable = new MyThreadRunnable();
Thread thread = new Thread(myThreadRunnable);
thread.start();
for (int i = 0; i < 500; i++) {
System.out.println("---------------------");
}
}
}
3、实现Callable接口【了解 】
优点:
(1)实现Callable接口,需要返回值类型
(2)重写call()方法,需要抛异常
(3)创建目标对象
(4)创建执行服务
(5)提交执行
(6)获取结果
(7)关闭服务
4、静态代理
5、Lamda表达式
(1)为什么要学
(2)函数式接口
//例如:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
三、多线程的五大状态
1、创建状态
new Thread()
2、就绪状态
调用start()
3、运行状态
执行线程体的代码块
4、阻塞状态
调用sleep、wait、同步锁定时。
5、死亡状态
线程终止或结束。死亡后不可再次启动。
6、 线程停止
package redis01.demo;
public class ThreadStop implements Runnable {
// 1、设置线程体停止标识
private Boolean flag = true;
@Override
public void run() {
// 2、线程体使用该标识
while (true) {
System.out.println("innit");
}
}
// 3、对外提供方法改变标识
public void stop() {
this.flag = false;
}
public static void main(String[] args) {
ThreadStop threadStop = new ThreadStop();
Thread thread = new Thread(threadStop);
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程===========" + i);
if (i == 9) {
System.out.println("调用stop方法切换标志位让线程停下来了");
threadStop.stop();
}
}
}
}
7、线程睡眠
thread.sleep(1000);
每个对象都有一个锁,sleep不会释放锁。
8、线程礼让
已经进入线程的程序被拉出来和等待进入线程的程序重新排队。
thread.yield();
9、线程强制执行
Jion合并线程,类似于插队。
thread.join();
10、观测线程状态
thread.getState();
11、线程的优先级
thread.setPriority(); // 主线程默认最高优先级 传入1-10
12、守护线程
thread.setDaemon(true);//默认是false表示是用户进程,正常的线程都是用户线程
13、【重点】线程同步
多个线程操作同一个资源。
优点:
队列+锁=安全
缺点:
效率低下
(1)同步方法
给方法加synchronized关键字修饰即可,同步的是本身。
(2)同步代码块
synchronized(被锁的对象){锁一块代码}
14、死锁
死锁是由于两个或以上的线程互相持有对方需要的资源,导致这些线程处于等待状态,无法执行。
(1)产生死锁的四个必要条件
1.互斥性:线程对资源的占有是排他性的,一个资源只能被一个线程占有,直到释放。
2.请求和保持条件:一个线程对请求被占有资源发生阻塞时,对已经获得的资源不释放。
3.不剥夺:一个线程在释放资源之前,其他的线程无法剥夺占用。
4.循环等待:发生死锁时,线程进入死循环,永久阻塞。
(2)产生死锁的原因
1.竞争不可抢占性资源
p1已经打开F1,想去打开F2,p2已经打开F2,想去打开F1,但是F1和F2都是不可抢占的,这是发生死锁。
2.竞争可消耗资源引起死锁
进程间通信,如果顺序不当,会产生死锁,比如p1发消息m1给p2,p1接收p3的消息m3,p2接收p1的m1,发m2给p3,p3, 以此类推,如果进程之间是先发信息的那么可以完成通信,但是如果是先接收信息就会产生死锁。
(3)进程推进顺序不当
进程在运行过程中,请求和释放资源的顺序不当,也同样会导致产生进程死锁。
(4)避免死锁的方法
1.破坏“请求和保持”条件
想办法,让进程不要那么贪心,自己已经有了资源就不要去竞争那些不可抢占的资源。比如,让进程在申请资源时,一次性申请 所有需要用到的资源,不要一次一次来申请,当申请的资源有一些没空,那就让线程等待。不过这个方法比较浪费资源,进程可 能经常处于饥饿状态。还有一种方法是,要求进程在申请资源前,要释放自己拥有的资源。
2.破坏“不可抢占”条件
允许进程进行抢占,方法一:如果去抢资源,被拒绝,就释放自己的资源。方法二:操作系统允许抢,只要你优先级大,可以抢 到。
3.破坏“循环等待”条件
将系统中的所有资源统一编号,进程可在任何时刻提出资源申请,但所有申请必须按照资源的编号顺序(升序)提出
15、lock锁
锁类型
可重入锁:在执行对象中所有同步方法不用再次获得锁
可中断锁:在等待获取锁过程中可中断
公平锁:按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利
读写锁:对资源读取和写入的时候分为为2部分处理,读的时候可以多线程一起读,写的时候必须同步地写
synchronized与Lock的区别
1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
四、线程协作
1、生产者消费者问题
(1)传统的线程通信。
在synchronized修饰的同步方法或者修饰的同步代码块中使用Object类提供的wait(),notify()和notifyAll()3个方法进行线程通信。
关于这3个方法的解释:
(2)使用Condition控制线程通信。
当程序使用Lock对象来保证同步,系统不存在隐式的同步监视器,只能用Condition类来控制线程通信。
(3)使用阻塞队列(BlockingQueue)控制线程通信(也实现了生产者消费者模式)
BlockingQueue提供如下两个支持阻塞的方法:
示例:
package edu.Utils;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* Created by hpp on 2017/7/4.
*/
class Producer extends Thread{
private BlockingQueue bq;
public Producer(BlockingQueue bq){
this.bq = bq;
}
public void run(){
String[] strArr = new String[]{
"java",
"Struts",
"Spring"
};
for(int i = 0;i<99999;i++){
System.out.println(getName() + "生产者准备生产集合元素!");
try{
Thread.sleep(1000);
bq.put(strArr[i%3]);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(getName() + "生成完成:" + bq);
}
}
}
class Consumer extends Thread{
private BlockingQueue bq;
public Consumer(BlockingQueue bq){
this.bq = bq;
}
public void run(){
while(true){
System.out.println(getName() + "消费者准备消费集合元素!");
try{
Thread.sleep(1000);
bq.take();
}catch (Exception e){
e.printStackTrace();
}
System.out.println(getName() + "消费完成:" + bq);
}
}
}
public class BlockingQueueTest {
public static void main(String[] args){
//创建一个容量为1的BlockingQueue
BlockingQueue bq = new ArrayBlockingQueue(1);
//启动3个生产者线程
new Producer(bq).start();
new Producer(bq).start();
new Producer(bq).start();
//启动1个消费者线程
new Consumer(bq).start();
}
}
结果:
Thread-0生产者准备生产集合元素!
Thread-1生产者准备生产集合元素!
Thread-2生产者准备生产集合元素!
Thread-3消费者准备消费集合元素!
Thread-0生成完成:[java]
Thread-0生产者准备生产集合元素!
Thread-1生成完成:[java]
Thread-1生产者准备生产集合元素!
Thread-3消费完成:[java]
Thread-3消费者准备消费集合元素!
Thread-2生成完成:[java]
Thread-2生产者准备生产集合元素!
Thread-3消费完成:[java]
Thread-3消费者准备消费集合元素!
Thread-0生成完成:[Struts]
Thread-0生产者准备生产集合元素!
Thread-3消费完成:[Struts]
Thread-3消费者准备消费集合元素!
五、线程池
https://blog.csdn.net/fanrenxiang/article/details/79855992
1、使用背景
java中经常需要用到多线程来处理一些业务,我们非常不建议单纯使用继承Thread或者实现Runnable接口的方式来创建线程,那样势必有创建及销毁线程耗费资源、线程上下文切换问题。同时创建过多的线程也可能引发资源耗尽的风险,这个时候引入线程池比较合理,方便线程任务的管理。java中涉及到线程池的相关类均在jdk1.5开始的java.util.concurrent包中,涉及到的几个核心类及接口包括:Executor、Executors、ExecutorService、ThreadPoolExecutor、FutureTask、Callable、Runnable等。
2.线程池的创建及重要参数
线程池可以自动创建也可以手动创建,自动创建体现在Executors工具类中,常见的可以创建
newFixedThreadPool
newCachedThreadPool
newSingleThreadExecutor
newScheduledThreadPool
手动创建体现在可以灵活设置线程池的各个参数,体现在代码中即ThreadPoolExecutor类构造器上各个实参的不同:
3、ThreadPoolExecutor中重要的几个参数详解
4、常见的几种自动创建线程池方式