多线程

来源:互联网 发布:python telnet expect 编辑:程序博客网 时间:2024/06/07 04:51
避免死锁
1.避免一个线程内同时获取多个锁
2.避免一个线程在锁内同事占用多个资源,尽量保证每个锁只占用一个资源
3.尝试使用定时锁,使用 lock.tryLock()来代替内部锁机制
4.对于数据库锁,加锁和解锁必须在一个数据库连接里




如何减少上下文切换:
1.无锁的编发编程,多线程竞争锁的时候会引起上下文的切换,所以多线程处理数据时可以用一些办法来
避免使用锁,比如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据
2.CAS算法,Java的Atomic包使用CAS算法来更新数据不需要加锁
3.使用最少的线程,避免创建不需要的线程,比如任务很少但是创建了很多线程来处理,这样会造成
大量线程都处于等待状态




volatile修饰的变量在多核处理器下会将当前处理器缓存行的数据写回到系统内存,这个写会
操作会是在其他CPU里缓存了改内存地址的数据无效


偏向锁的撤销:偏向锁默认是开启的,但是需要在程序启动几秒钟之后才会激活,可以设置JVM关闭延迟也可以设置直接关闭
偏向所使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程
才会释放锁。偏向锁的撤销需要一个全局安全点(在这个时间点上没有正在执行的字节码)。他会首先暂停
拥有偏向所的线程,然后检查持有偏向锁的线程是否活着。




锁                优点                        缺点                            使用场景


偏向锁          加锁和解锁不需要额外的如果线程存在锁的竞争会带来          适用于只有1个线程访问同步快的场景
 消耗,和执行费同步方法相比额外的锁的撤销的消耗
 只存在纳秒及的差距


轻量级锁            竞争的线程不会阻塞,提高程序 如果始终得不到锁会造成自旋追求响应速度,同步块执行速度非常快
      的响应速度 使用自旋消耗CPU 




重量级锁  线程竞争不会消耗CPU线程阻塞,响应时间缓慢 追求吞吐量,同步快执行速度较长




JMM通过控制主内存与每个线程的本地内存之间的交互,来为Java程序员提供内存可见性保证


Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序


JMM通过禁止特定类型的编译器重排序和处理器重排序来提供内存可见性的保证


A happen before B JMM并不要求A一定要在B之前执行,JMM仅仅要求钱一个操作的结果对后一个操作的结果可见,且前一个操作顺序排在第二个操作之前


现在的处理器使用写缓冲区想内存写入的数据,写换泄洪区可以保证指令流水持续运行,他可以避免由于处理器停顿下来等待向内存写入数据而产生的延迟,
同时,通过一批处理的方式刷新写缓冲区以及合并写缓冲区中对统一内存地址的多次写,减少对内存总线的占用,虽然写缓冲区有这么多好处,但是每个处理器
上的修缓冲区仅仅对他所在的处理器开一间,这个特性会对内存操作的执行顺序产生重要的影响:处理器对内存的第,写操作的执行顺序呢,不一定与内存实际发生
的读写顺序一致。


L:Load, S:Store
屏障类型              指令实例 说明


LL             L1;LL;L2确保L1数据的装载先于L2及所有后续所有装载指令             

SS S1;SS;S2       确保S1数据对其他处理器可见(刷新到内存)先与S2及所有或许存储指令的存储


LS L1;LS;S1        确保L1的装载先与S2以及所有后续的存储指令刷新到内存


SL S1;SL;L1        确保S1数据对其他处理器变得可见,先于L2以及所有后续装载指令的装载,SL会使该
屏障之前的所有内存访问指令完成之后,才执行该屏障之后的内存访问指令




LL:禁止上面的读和下面的读重排序
LS:禁止下面的写操作对和上面的读操作重排序
SL:禁止上面的写和下面的可能读写重排序
SS:禁止上面的写和下面的写重排序         //这里的读和写没有区分是普通写读还是volatile写读书




公平锁和非公平锁内存语义
公平锁和非公平锁释放时,最后都要写一个volatile变量
公平锁获取时,首先会去读volatile变量




final域的内存语义
1在构造函数内对一个final域的写入,与随后吧这个被构造对象的引用赋值给一个引用变量,这两个操作不能重排序
2初次度一个包含final的对象的引用,与随后初次读这个final域这两个操作不能重排序








happen-before规则 简称HB


1、程序顺序规则:一个线程中的每个操作,HB于改线程中的任意后续操作
2、监视器锁规则:对一个锁的解锁。HB于随后对这个锁的加锁
3、volatile变量规则:对一个volatile的写,HB于任意后续对这个volatile的读
4、传递性:1HB2 ,2HB3,可以知道1HB3 
5、start规则:如果线程A执行操作线程B,那么A线程的ThreadB.start()操作HB于线程B的任意操作
6、join规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作HB于线程A从ThreadB.join()操作成功返回。






线程的状态:
状态名称                     说明


NEW 初始状态,线程被构建,但是还没有调用start方法
RUNNABLE 运行状态,JAVA线程讲操作系统中的就绪和运行两种状态笼统地称作  运行中
BLOCKED 阻塞状态,表示线程阻塞与锁
WATTING 等待状态,表示线程进入等待状态,进入该状态说明当前线程需要等待其他线程做出
一些特定的动作(中断或通知)
TIME_WATTING 超时等待状态,该状态不同于WATTING,他是可以在制定的时间自行返回的
TERMINATED 终止状态,表示当前线程已经执行完毕


**Java将操作系统中的而运行和就绪两个状态合并称为运行状态。阻塞状态是线程阻塞在进入Synchrogazer关键字修饰的
方法或者代码块时的状态,但是阻塞在Java concurrent包中Lock接口的线程状态却是等待状态,因为Java concurrent包中Lock接口的
实现均使用了LockSupport类中相关的方法。


Daemon线程是一种支持性的线程看,因为他主要被作为程序中后台调度以及支持性工作,这意味着,当一个Java虚拟机中不存在非Daemon
线程的时候,Java虚拟机将退出,可以通过调用Thread,setDaemon(TRUE)将线设置为Daemon线程
 
Daemon线程被用于完成支持性的工作,但是在Java虚拟机退出的时候,Daemon线程中的finally快并不一定会执行,所以在构建Daemon线程时,
不能依靠finally快中的内容来保证确保执行关闭或清理资源的逻辑


***不太懂: 一个新构建的线程对象是由其parent线程来进行空间分配的,而child线程继承了parent是否为Daemon线程,优先级
和加载资源的contextClassLoader以及可继承的ThreadLocal,同时还会分配一个唯一的Id来表示这个child线程,
至于一个能运行的线程对象就已经初始化好了。


线程start()方法的含义:当前线程同步告知Java虚拟机,只要线程规划期空间立即启动调用start方法的线程


在class文件中,对同步快Synchronized的实现使用了monitorenter和monitorexit指令,二同步方法是依靠方法修饰符上的
ACC_SYNCHRONIZED来完成的,两种方法都是对一个对象的监视器进行获取,然后获取的过程是排他的。


获取同步资源过程:
任意线程对OBJ访问,首先要获得OBJ的监视器,如果获取失败,线程进入同步队列,线程状态变成BLOCKED。当访问OBJ的钱去释放了锁,
则改释放操作唤醒阻塞在同步队列中的线程,使其重新尝试对监视器的获取。


//todo 不太懂***
public class TestThreadJoin {
    public static void main(String[] args) throws InterruptedException {
        Thread pre = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new demo(pre), String.valueOf(i));
            thread.start();
            pre = thread;
        }
         TimeUnit.SECONDS.sleep(5);
        System.out.println(Thread.currentThread().getName() + " over");


    }


    static class demo implements Runnable {
        private Thread thread;


        public demo(Thread thread) {
            this.thread = thread;
        }


        @Override
        public void run() {
            try {
                thread.join();
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {


            }
            System.out.println(Thread.currentThread().getName() + " overs");
        }
    }
}


线程池:好处是 消除了频繁创建和消亡线程的系统资源开销,另一方面,面对过量任务的提交能平缓的劣化
不用线程池的话创建了数以万计的线程会导致操作系统频繁的进行线程上下文切换,无故增加了系统的负载,而
线程的创建和小王都是需要耗费系统资源的,这样也耗费了系统资源


线程池的本质就是使用了一个线程安全的工作队列链接工作者线程和客户端线程,客户端讲任务放入工作队列后
便返回,而工作者线程则不断从工作队列上取出工作并执行。当工作队列为空的时,所有的工作者线程均等待在工作队列上。
当有客户端提交了一个任务之后会通知任意一个线程,随着大量任务的提交,更多工作者线程会被换唤醒


随着线程池中线程数量的增加,吞吐量也是不断增加的,响应时间也不断变小,但是线程池中线程熟练爱国并不是越多愈好,具体的数量需要
评估每个任务的处理时间以及当前计算机的处理器能力和数量,使用的线程少无法发挥处理器的性能;使用的线程多会增加系统的无故开销。


Tomcat Jetty在处理请求的过程中都使用到了线程池技术.***有时间先看Tomcat权威指南***





































































0 0
原创粉丝点击