多线程笔记

多线程

多线程可以从以下几个方面学习:

       什么是进程?  什么是线程? 什么是线程安全?如何确保线程安全?  如何实现多线程?

什么是进程?

电脑会单独的运行许多的程序,每个程序都是一个独立的进程,每个进程之间都是独立存在的。如下图QQ、钉钉、有道云笔记都是一个独立的进程。

什么是线程?

线程是程序中执行的线程,java虚拟机允许程序同时运行多个执行线程。

进程的执行依赖线程。进程的最小执行单位是线程,一个进程种至少有一个线程。

线程有6种状态:新建(NEW),运行(RUNNABLE),阻塞(BLOCKED),等待(WAITING),计时等待(TIMED_WAITING)和终止(TERMINATED)。

什么是多线程?

提到多线程主要搞明白两个概念 ,串行和并发,搞清楚这个,才能更好地理解多线程。

串行,也就是单线程,当我们在下载多个文件地时候,在串行中他是按照一定地顺序进行下载,也就是说当A文件下载完,才能进行B

文件的下载。

并行,下载多个文件的时候,开启多条线程,多个文件同时进行下载,也就是说,在同一个时间发生的事情。

什么是线程安全?

当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。

如何确保线程安全?

Synchronized关键字,用来控制线程同步的,确保在多线程的环境下,不被多个线程同时执行,确保数据的完整性,一般加在方法上。Synchronized锁的是方法括号中的对象,当锁住一个对象之后,别的线程想要获取对象,那么就必须等这个线程执行完释放锁对象之后才可以,否则一直处于等待状态。

Lock 通过手动的获取锁和释放锁

写个主方法测试程序是否正常

输出结果

如何实现多线程?

创建线程对象方法

1、Thread

      自定义线程类基础Thread

       重写run()方法,编写线程执行体

       创建线程对象,调用start()方法启动线程

练习

2.Runnable接口

小结

       继承Thread类

              子类继承Thread类具备多线程能力

              启动线程:子类对象.start()

              不建议使用:避免OOP单继承局限性

       实现Runnable接口

实现Runnable接口具有多线程能力

启动线程:传入目标对象+Thread对象.start()

推荐使用:避免单继承局限性,灵活方便,方便同一对象被多个线程使用

实例

3.实现Callable接口

       1.实现Callable接口,需要返回值类型

       2.重写call方法,需要抛出异常

       3.创建目标对象

       4.创建执行服务:ExecutorService = Executor.newFixedThreadPool(1);

       5.提交执行:Future<Boolean> result1 = ser.submit(t1);

       6.获取结果:boolean r1 = result.get()

       7.关闭服务

         静态代理模式总结

         真实对象和代理对象都要实现同一个接口

         代理对象要代理真实角色

         好处:

         代理对象可以做很多真实对象做不了的事情

         真实对象专注做自己的事情

         Lambda表达式

         避免匿名内部类定义过多

         其实质属于函数时编程的概念

         可以让代码看起来简洁

         去掉了一堆没有意义的代码,只留下核心的逻辑

函数式接口

Functional Interface(函数式接口)

定义:

任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。

对于函数式接口,可以通过Lambda表达式来创建该接口的对象。

总结:

    1.Lambda表达式只能有一行代码的情况下才能简化为一行,如果有多行,那么就用代码块包裹

       2.前提式接口为函数式接口

       3.多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号

线程停止

线程休眠

sleep(时间)指定当前线程阻塞的毫秒数

sleep存在异常InterruptedException                   

sleep时间到达后线程进入就绪状态

sleep可以模拟网络延时,倒计时等

每一个对象都有一个锁,sleep不会释放锁

线程礼让

礼让线程,让当前正在执行的线程暂停,但不阻塞

将线程从运行状态为就绪状态

让cpu重新调度,礼让不一定成功!看cpu心情

join

join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞

可以想象成插队

线程状态观测

Thread.State

线程状态,线程可以处于以下状态之一:

       new 尚未启动的线程处于此状态

       Runnable再java虚拟机中执行的线程处于此状态

       Blocked 被阻塞等待监视器锁定的线程处于此状态

       Waiting 正在等待另一个线程执行特定动作的线程处于此状态。

       Timed Waiting 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态

一个线程可以给定时间点处于一个状态。这些状态时不反应任何操作系统线程状态的虚拟机状态

      

线程优先级

Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有1线程,线程调度器按照优先级决定应该调度哪个线程来执行。

线程的优先级用数字表示,范围从1~10

使用以下方式改变或获取优先级

getPriority().setPriority(int xxx)

注意:先设置优先级,再start线程

守护(daemon)线程

       线程分为用户线程和守护线程

       虚拟机必须确保用户现场执行完毕

       虚拟机不用等待守护线程执行完毕

       如,后台记录操作日志,监控内存,垃圾回收等待。。。

线程同步机制

线程同步

由于同一进程的多个线程共享同一块存储空间,再带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被

访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,

使用后释放锁即可,存在以下问题:

一个线程持有锁会导致其他所有需要此锁的线程挂起;

在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;

如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级导致,引起性能问题。

线程锁

//不安全的买票

//不安全取钱

       //两个人去银行取钱,同一个账户

同步块

Synchronized(Obj){}

Obj称之为同步监视器

       Obj可以是任何对象,但是推荐使用共享资源作为同步监视器

       同步方法无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者class

同步监视器的执行过程

       1.第一个线程访问,锁定同步监视器,执行其中代码

       2.第二个线程访问,发现同步监视器被锁定,无法访问

       3.第一个线程访问完毕,皆出同步监视器

       4.第二个线程访问,发现同步监视器没有锁

              死锁避免方法

              产生死锁的四个必要条件:

              1.互斥条件:一个资源每次只能被一个进程使用。

              2.请求与保持条件:一个进程因请求资源而阻塞时,对以获得的资源保持不妨。

              3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。

              4.循环等待条件:等若干进程之间形成一种头尾相接的循环等待资源关系。

              Lock

  • JDK5.0开始,java提供了更强哥的同步机制——通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当
  • Java.util.concurrent.locks.Lock接口时控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前先获得Lock对象
  • ReentranLock类实现了Lock,它拥有与synchronized相同的并发性和内存予以,在实现线程安全的控制中,比较常用的时ReentrantLock,可以显示加锁、释放锁。

Synchronized与Lock的对比

· Lock是显示锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放

· Lock只有代码块加锁,synchronized有代码块锁和方法锁

· 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

· 优先使用顺序:

· Lock ->同步代码块(已经进入了方法体,分配了想要资源)->同步方法(在方法体之外)

线程通信

应用场景: 生产者和消费者问题

       ·  假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。

     ·  如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。

       ·  如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。

//信号灯法

使用线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发状态下的线程,对性能影响很大。

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活

中的公共·交通工具。

好处:

       提高响应速度(减少了创建新线程的时间)

       降低资源消耗(重复利用线程池中的线程,不需要每次都创建)

       便于线程管理(…)

              corePoolSize:核心池的大小

              maximumPoolSize:最大线程数

              keepAliveTime:线程没有任务是最多保持多长时间会终止

JDK5.0起提供了线程池相关API:

       ExecutorService

       Executors

ExecutorService:

       真正的线程池接口。常见子类ThreadPoolExecutor

              void execute(Runnable command): 执行任务/命令,没有返回值,一般用来执行Runnable

              <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行Callable

              Void shutdown(): 关闭连接池

       Executors:工具类、线程池得工厂类,用于创建并返回不同类型的线程池

             

线程总结

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值