多线程总结


一、线程、进程、多线程

1、线程:CPU调度和执行的单位。
2、进程:程序的一次执行过程,是系统资源分配的单位。
3、一个进程至少有一个线程,也可以包含多个线程。


二、并发和并行

1、并行:两个或者多个事件在同一时刻发生。是在不同实体上的多个事件
小明在A座位吃饭,小王在B座位吃饭
2、并发:两个或者多个事件在同一时间段发生。是在同一实体上的多个事件
小明和小王都要在A座位吃饭,小明先吃,小王等小明吃完后再坐上去吃。


三、线程的四种创建方式

1.继承Thread类

1)实现过程

public class Thread implements Runnable {}

创建线程的方式1:
1、继承Thread类
2、重写run()方法
3、创建线程对象,调用start()开启线程

在这里插入图片描述

2)run()和start()的区别

1、run(),是Runnable接口中的方法,接口中只有这也一个方法。start()是Tread类中的方法。
在这里插入图片描述
在这里插入图片描述
2、start()方法是初始化线程的方法。run()只是在当前线程中执行任务
程序中一般会有两部分线程,一部分在run()方法中,一部分在main()方法(称为主线程)中
main()方法里,直接调用run(),那就只是相当于调用了一个普通的run()方法,不涉及多线程
main()方法里,调用start(),代表开启线程,run()方法中的线程和main()方法中的主线程交替执行


2.实现Runnable接口

创建线程的方式2(推荐使用,因为接口可以多继承):
1、实现Runnable接口
2、重写run()方法
3、创建实现类对象
4、创建线程对象,调用start()方法

在这里插入图片描述


3.实现Callable接口

  • 创建线程的方式3(优点:可以定义返回值;可以抛出异常):
    1、实现Callable接口
    2、重写call()方法
    在这里插入图片描述

1)Runnable和Callable的区别

  • Runnable接口的源码
    Runnable接口是一个函数式接口,里面只有一个抽象方法void run(),意思是无返回值
@FunctionalInterface
public interface Runnable {
   /**
    * When an object implementing interface <code>Runnable</code> is used
    * to create a thread, starting the thread causes the object's
    * <code>run</code> method to be called in that separately executing
    * thread.
    * <p>
    * The general contract of the method <code>run</code> is that it may
    * take any action whatsoever.
    *
    * @see     java.lang.Thread#run()
    */
   public abstract void run();
}
  • Callable接口的源码
    Callable接口也是一个函数式接口,同时也是一个泛型接口。里面只有一个抽象方法V call(),其返回值类型就是泛型V
@FunctionalInterface
public interface Callable<V> {
   /**
    * Computes a result, or throws an exception if unable to do so.
    *
    * @return computed result
   * @throws Exception if unable to compute a result
    */
   V call() throws Exception;
}

2)关于实现Callable接口创建线程的方式

  • 第一种方式是配合ExecutorService使用,在ExecutorService接口中声明了若干个submit方法的重载版本。这个方法还未提及,在线程池里会具体讲解,一般情况下都会用这种方式。
  • 第二种方式是配合Thread使用,通过创建FutureTask类型的对象,将此对象传入Thread(),再调用start()方法来开启线程。此方式会在3)中讲解。

3)Future接口和FutureTask实现类的解析

  • 先拿到前面实现Callable接口创建线程的过程截图。
    在这里插入图片描述
  • FuturTask
    1.我们先了解一下Future接口
    Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
    在这里插入图片描述
    2.FutureTask实现类
    1)FutureTask类实现了RunnableFuture接口;RunnableFuture接口继承了Runnable接口和Future接口
    在这里插入图片描述
    在这里插入图片描述
    2)FutureTask类中有两个构造方法。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
    在这里插入图片描述
    3)使用FutureTask + Thread()创建线程时,可以把FutureTask理解成一个包装器。使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
    在这里插入图片描述

4.创建线程池

提前创建多个线程,丢入线程池中,使用时直接获取,使用完放回池中。避免频繁地创建和销毁,实现重复利用。
在这里插入图片描述

1)Excutors

常用的线程池工具类。里面有若干个静态方法。

public class Excutors {
	public static ExecutorService newFixedThreadPool(int nThreads) {
		//ThreadPoolExecutor的这个构造方法中返回了传入的所有参数
		/*
		*public ThreadPoolExecutor(int corePoolSize,
                             int maximumPoolSize,
                             long keepAliveTime,
                             TimeUnit unit,
                             BlockingQueue<Runnable> workQueue) {
      	 this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
            	Executors.defaultThreadFactory(), defaultHandler);
   		}
		*/
		return new ThreadPoolExecutor(nThreads, nThreads,
                                     0L, TimeUnit.MILLISECONDS,
                                     new LinkedBlockingQueue<Runnable>());
	}
	public static ExecutorService newCachedThreadPool() {}
	public static ExecutorService newSingleThreadExecutor() {}
	//public interface ScheduledExecutorService extends ExecutorService {}
	public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {}
}

newFixedThreadPool(int nThreads):创建一个定长线程池可控制线程最大并发数,超出的线程会在队列中等待。
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。(线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程)
newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newScheduledThreadPool(int corePoolSize):创建一个定长线程池,支持定时及周期性任务执行。

2)线程池的核心参数

public class ThreadPoolExecutor extends AbstractExecutorService {
	public ThreadPoolExecutor(int corePoolSize,
                             int maximumPoolSize,
                             long keepAliveTime,
                             TimeUnit unit,
                             BlockingQueue<Runnable> workQueue) {
       this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
            Executors.defaultThreadFactory(), defaultHandler);
   }
}

corePoolSize:核心线程数
maximumPoolSize:最大线程数
keepAliveTime:空闲线程存活时间
unit:时间单位
workQueue:任务队列
defaultThreadFactory:线程工厂
defaultHandler:饱和拒绝策略

3)ExecutorService

public interface Executor {
	void execute(Runnable command);
}
public interface ExecutorService extends Executor { }

execute(Runnable command):履行Ruannable类型的任务。(Executor接口中的抽象方法)
submit(task):可用来提交Callable或Runnable任务,并返回代表此任务的Future对象
shutdown():在完成已提交的任务后封闭办事,不再接管新任务,
shutdownNow():停止所有正在履行的任务并封闭办事。
isTerminated():测试是否所有任务都履行完毕了。,
isShutdown():测试是否该ExecutorService已被关闭

4)线程池的实现

在这里插入图片描述


四、静态代理模式

1.什么是静态代理

参考我的另一篇文章代理模式总结


2.多线程中的静态代理模式

在这里插入图片描述


五、线程状态

1.五大状态

在这里插入图片描述


2.线程方法

在这里插入图片描述


3.停止线程

  • 推荐线程自己停下来。建议使用一个标志位进行终止变量
    当flag==false,则终止线程运行。
    在这里插入图片描述

4.线程休眠(sleep)

  • public static native void sleep(long millis) throws InterruptedException;
    sleep(long millis)指定当前线程阻塞的毫秒数
    存在异常InterruptedException
  • sleep时间到达后,线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • sleep不会释放锁
    在这里插入图片描述

5.线程礼让(yield)

  • public static native void yield();
  • 让当前正在执行的线程暂停,不阻塞,从运行状态直接转为就绪状态
  • 让cpu重新调度,礼让不一定成功,看cpu心情
    可以理解成,让当前运行的线程停止,重新与其他线程竞争

礼让失败
在这里插入图片描述
礼让成功
在这里插入图片描述


6.线程强制执行(join)

  • public final void join() throws InterruptedException
  • 可以理解成让某些线程插队
    在这里插入图片描述

7.观测线程状态

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

1)BLOCKED和WAITING的区别

  • BLOCKED是锁竞争失败后被动触发的状态,WAITING是人为主动触发的状态
  • BLOCKED后的唤醒是自动的,WAITING的唤醒需要通过特定的方法主动唤醒

六、线程优先级

  • 线程调度器按照优先级决定应该调度哪个线程,但也不一定,只能是概率大。具体由CPU调度。
  • 线程的优先级范围从1~10
    Thread.MIN_PRIORITY = 1;
    Thread.MAX_PRIORITY = 10;
    Thread.NORM_PRIORITY = 5;
public final void setPriority(int newPriority) {
    ThreadGroup g;
    checkAccess();
    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
        throw new IllegalArgumentException();
    }
    if((g = getThreadGroup()) != null) {
        if (newPriority > g.getMaxPriority()) {
            newPriority = g.getMaxPriority();
        }
        setPriority0(priority = newPriority);
    }
}

public final int getPriority() {
	return priority;
}

在这里插入图片描述


七、守护线程(daemon)

  • 线程分为用户线程守护线程
  • 虚拟机必须确保用户线程执行完毕(main()
  • 虚拟机不用等待守护线程执行完毕(gc()),即用户线程结束后,程序结束,守护线程可能还在执行
Thread thread = new Thread(daemonThread);
//默认是false,表示用户线程,正常的线程都是用户线程
thread.setDaemon(true);

八、线程同步

  • 并发:同一个对象被多个线程同时操作
  • 线程同步可以理解成一个等待机制
  • 线程同步的形成条件:队列+锁(synchronized)
  • 加锁后存在两个问题
    1)加锁的目的是保护线程安全,但是性能会降低
    2)会导致优先级倒置(优先级高的线程需要等待优先级低的线程释放锁)
  • 线程不安全的例子
    在这里插入图片描述

public synchronized void method(int args){}

  • 同步方法:由于private关键字用来保证数据对象只能被方法访问,所以用synchronized方法控制对对象的访问。每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞。方法一旦执行,就独占该锁,直到该方法返回释放锁,后面被阻塞的线程才能获得这个锁并继续执行
  • 缺陷:若将一个大的方法申明为synchronized将会影响效率。比如一个方法中有两部分,只有一部分需要修改才用到锁,而这种时候两部分都添加了锁,会造成资源浪费。

synchronized(Obj){}

  • 同步代码块
  • Obj同步监视器。Obj可以是任何对象,但推荐使用共享资源作为同步监视器。
  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this(对象本身)。
  • Obj必须是变化的量。比如说增、删、改。
    在这里插入图片描述
  • CopyOnWriteArrayList。线程安全的集合
    在这里插入图片描述
    在这里插入图片描述

九、死锁

多个线程各自占有对方需要的资源,形成僵持。

产生死锁的四个必要条件
1、互斥条件:一个资源每次只能被一个进程使用。
2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3、不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系


十、Lock锁

1.概念

通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当。
可以用可重入锁(ReentrantLock)实现显示加锁、释放锁:ReentrantLock类实现Lock接口

public interface Lock {}
public class ReentrantLock implements Lock, java.io.Serializable {}
public class LockDemo {
    public static void main(String[] args) {
        LockTest lt = new LockTest();

        new Thread(lt).start();
        new Thread(lt).start();
        new Thread(lt).start();
    }
}

class LockTest implements Runnable {
    private int ticketsNums = 10;

    //定义ReentrantLock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                //加锁
                lock.lock();

                //线程运行
                if (ticketsNums > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketsNums--);
                } else {
                    break;
                }
            } finally {
                //解锁
                lock.unlock();
            }

        }
    }
}

2.Lock与synchronized的对比

1、Lock是一个接口;synchronized是一个关键字。
2、Lock是显示锁(需要手动开启和关闭);synchronized是隐式锁,出了作用域自动释放
3、使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。具有更好的扩展性(提供更多的子类)
4、使用顺序:Lock > 同步代码块 > 同步方法


十一、线程协作

1.生产者消费者模式

不是23中设计模式之一,是一个问题!!

  • 应用场景
    1、假设仓库中只能存放一件商品。生产者生产商品,放入仓库,消费者取走商品。
    2、若仓库中没有商品。生产者放入一件商品后,停止生产并等待,消费者取走商品。
    3、若仓库中有商品。消费者取走商品并消费,否则停止消费并等待。
  • 问题分析
    1、这是一个同步问题,生产者和消费者共享同一个资源,并且二者相互依赖,互为条件
    2、对于生产者。生产商品后,通知消费者,如果容器满了,生产者等待
    3、对于消费者。消费后,通知生产者,如果容器空了,消费者等待
  • 结论
    在生产者消费者问题中,仅有synchronized是不够的的。synchronized仅能解决同步,但不能实现不同线程间的通信

2.线程通信

均为Object类的方法,只能在同步方法或同步方法快中只用,否则会抛出异常
在这里插入图片描述


3.解决生产者消费者问题

1)管程法

生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
在这里插入图片描述
在这里插入图片描述

2)信号灯法

添加一个标志位flag,通过flag为true或false来切换线程

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值