美文网首页
多线程编程

多线程编程

作者: lesline | 来源:发表于2017-07-02 18:28 被阅读7次

    锁:

    公平锁: Threads acquire a fair lock in the order in which they requested it
    非公平锁:a non fair lock permits barging: threads requesting a lock can jump ahead of the queue
    of waiting threads if the lock happens to be available when it is requested.

    公平锁,就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程线程是等待队列的第一个,
    就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己;
    非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式

    **编程注意注意事项 **
    1、多用同步类少用wait 和 notify 首先,CountDownLatch, Semaphore, CyclicBarrier 和 Exchanger 这些同步类简化了编码操作,
    而用wait和notify很难实现对复杂控制流的控制。
    2、多用并发集合少用同步集合 这是另外一个容易遵循且受益巨大的最佳实践,并发集合比同步集合的可扩展性更好,
    所以在并发编程时使用并发集合效果更好。如果下一次你需要用到map, 你应该首先想到用ConcurrentHashMap。


    线程状态机

    1.Java中notify 和 notifyAll有什么区别?
    notify()方法只唤醒一个线程,不能唤醒某个具体的线程,所以只有一个线程在等待的时候它才有用武之地。
    而notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。
    notifyAll使所有原来在该对象上等待被notify的线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。
    notify则文明得多他只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们,
    当第一个线程运行完毕以后释放对象上的锁此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,
    其他wait状态等待的线程由于没有得到该对象的通知,继续处在wait状态,直到这个对象发出一个notify或notifyAll,它们等待的是被notify或notifyAll,而不是锁。

    2. java中wait和sleep有什么区别?
    最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。
    其它不同有:
    sleep是Thread类的静态方法,wait是Object方法。
    wait、notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
    sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。

    yield:
    当前线程从执行状态(运行状态)变为可执行态(就绪状态),cpu会从众多的可执行态里选择.

    interrupt:
    置线程的中断状态(可理解为只是发出中断请求)当线程在阻塞状态下(wait/sleep/join/)发生中断,抛出InterruptedException,清除中断状态;当线程不是在阻塞状态下发生中断,只是设置了线程的中断状态,待被获取,如果不触发InterruptedException,中断状态一直保持。

    isInterrupted:
    判断线程是否已经中断,中断状态不受影响
    interrupted:
    判断线程是否已经中断,清除中断状态。


    如何保证线程安全

    线程不安全原因:
    缓存一致性:多处理器系统中,因为共享同一主内存,当多个处理器的运算任务都设计到同一块内存区域时,将可能导致各自的缓存数据不一致的情况,则同步回主内存时需要遵循一些协议。
    乱序执行优化:为了使得处理器内部的运算单位能尽量被充分利用。

    数据的存放:
    处理器访问数据的速度从快到慢依次为:寄存器,处理器缓存,内存,磁盘,光驱。
    寄存器,处理器缓存都是处理器私有数据,只有内存,磁盘,光驱才是才是所有处理器都可以访问的全局数据,
    如果程序是多线程的,那么不同的线程可能分配到不同的处理器来执行,
    这些处理器需要把数据从主内存加载到处理器缓存和寄存器里面才可以执行,数据执行完成之后,在把执行结果同步到主内存。

    同步方法:
    synchronized关键字、volatile变量、显示锁、原子变量

    • synchronized和Lock:保证原子性,可见性和有序性;
    • volatile:保证可见性和有序性,不保证原子性;
    • atomic:保证原子性;
    • final:保证可见性

    atomic:
    原子操作-不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,不会中间切换到另一个线程。
    set( )和get( )方法:可以原子地设定和获取atomic的数据。类似于volatile.getAndSet() getAndDecrement( )、decrementAndGet、addAndGet()以实现一些加法,减法原子操作

    volatile:
    保证可见性和有序性,不保证原子性;
    A、每次读写volatile变量,都会读主内存或者写主内存
    B、不会重排序:保证volatile变更前面的操作先执行,volatile后面的操作后执行

    synchronized:
    是防止多线程操作的,Servlet采用多线程来处理多个请求同时访问

    final:
    可见性:写final域的重排序规则禁止把final域的写重排序到构造函数之外
    1、变量一旦被初始化为final型,便不可改变,对基本类型来说是其值不可变,而对于对象变量来说其引用不可再变。
    其初始化在两个地方,一是其定义处,二是在构造函数中。两个地方只能选其一,不能同时定义.
    2、final型变量在main方法中声明的变量没有任何意义。


    不可变对象和不可变对象

    不可变对象:

    1. 状态不可修改,所有域都是final类型,以及正确的构造过程(this引用没有逸出-构造函数中调用this或调用其它方法)。
    2. 用volatile发布不可变对象(保证可见性)

    可变对象: 可变对象要安全发布

    1. 在静态初始化函数(方法中)中初始化一个对象引用
    2. 将对象的引用保存到volatile类型的域或者AtomicReferance对象中
    3. 将对象的引用保存到某个正确构造对象的final类型域中
    4. 将对象的引用保存到一个由锁保护的域中。

    ** 不可变对象的生成原则:**
    1)immutable对象的状态在创建之后就不能发生改变,任何对它的改变都应该产生一个新的对象。
    2)Immutable类的所有的属性都应该是final的。
    3)对象必须被正确的创建,比如:对象引用在对象创建过程中不能泄露(leak)。
    4)对象应该是final的,以此来限制子类继承父类,以避免子类改变了父类的immutable特性。
    5)如果类中包含mutable类对象,那么返回给客户端的时候,返回该对象的一个拷贝,而不是该对象本身(该条可以归为第一条中的一个特例)

    安全发布方式:

    静态的初始化器:
    public static Holder holder = new Holder(42);
    静态初始化器由JVM在类的初始化阶段执行。由于在JVM内部存在着同步机制,因此通过这种方式初始化的任何对象都可以被安全地发布。

    线程池

    ExecutorService
    shutDown() 等待线程池中的任务执行完成(包含队列中等待的任务),线程池中不允许新增任务
    shutdownNow() 试图停止正在执行的任务,队列中等待的任务不处理,线程池中不允许新增任务
    awaitTermination 当超过timeout时间后,会监测ExecutorService中所有任务是否完成执行
    isShutdown 表示是是否执行了shutdown方法
    isTerminated 表示调用 shutdown 或 shutdownNow后任务是否执行完成

    **比较 **

    shutDown()
    线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
    当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常


    shutdownNow()
    根据JDK文档描述,大致意思是:执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务
    ,当然,它会返回那些未执行的任务。
    它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep、wait、Condition、定时锁等应用,
    interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。

    ExecutorCompletionService作用
    多个线程执行时,将执行结果放在自已的BlockingQueue变量中,也就是任务完成后能立即得到结果;只用ExecutorService,则只能执行开始的顺序获取,已完成的任务不能立刻看到。

    使用示例:

    /core-quartz/src/main/java/com/jy/modules/biz/bizJob/core/PayDayBeginJob.dealWithMutiTask
    

    线程使用方法

    **join: **
    将线程放到主线程上,当该线程执行完成才能往下执行。

    **ThreadLocal: **
    不是用于解决共享变量的问题的,不是为了协调线程同步而存在,
    而是为了方便每个线程处理自己的状态而引入的一个机制,理解这点对正确使用ThreadLocal至关重要。


    工具类

    1、CountDownLatch:
    计数器,一个线程(主线程)等待其它n个线程执行完后执行。
    2、CyclicBarrier:
    回环栅栏,实现让多个线程先执行一部分任务后再全部同时执行后面的任务
    3、Semaphore:
    信号量, 多个线程访问有限的几个资源。

    比较:

    1、CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
    CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
    而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
    另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
    2、Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。

    Phaser:JDK1.7
    CyclicBarrier和CountDownLatch有些重复
    实现等待多个线程进入某个阶段。

    ReentrantReadWriteLock: 区分读锁和写锁,当写锁锁定时,读锁等待
    Thread.UncaughtExceptionHandler() 未捕获异常
    ConcurrentHashMap

    相关文章

      网友评论

          本文标题:多线程编程

          本文链接:https://www.haomeiwen.com/subject/ccqocxtx.html