7、多线程基础

线程名称:  默认Thread开头,也可以指定

ThreadGroup:不是用来管理thread的,比如设置守护线程,销毁等这些都是线程独立的行为。group更像是一个组织thread的功能,表明这些thread是属于同一类同一组。当然threadGroup对thread的批量管理只有挂起interrupt的时候才起作用。 main方法的组属于main。

如果不指定,子线程一般随父线程同组。

守护线程通过设置线程的daemon为ture来设定。守护线程也称为后台进程。能够自动结束自己的生命周期。比如想在主线程运行完之后,结束子线程,就可以把该子线程设置为守护线程。

Runnable 和Thread,创建线程的方式就是一种,构造Thread的构造函数,而实现线程执行单位的有2种方案:1是重写Thread的run方法,2是实现Runnable接口的run方法。

2者区别:runnable能够对一个类的资料进行共享处理,而Thread因为每次new的都是一个新实例,因此不好进行资料共享处理。

线程的几个状态、生命周期:

1、New:先是new创建线程

2、通过start方法启动线程:

Runnable : Ready,可以进行运行。通过cpu的scheduler进行select选择,进入Running状态

         Running: yield / cpu的scheduler swap,进入Runnable状态。 yield不保险,不一定不执行。

3、Blocked,通过sleep/wait/suspend进入阻塞状态

                            通过resume/nofify/notifyAll进入可运行状态

4、Terminated状态, stop,运行完毕,意外等。生命周期结束。

interrupt方法打断堵塞状态,同时抛出异常。

join某个A线程,B线程会堵塞,等A先执行完。


wait/sleep/join可以被中断。


多线程的好处:提高并发量、吞吐率、不会因为一个线程慢而卡住,资源共享,充分利用cpu,简化程序结构。

坏处:安全、死锁、上下文切换、可靠性。

synchronized:jvm指令通过monitor

enter、monitor exit 两个锁机制这段代码块的资源、数据是互斥的,原子性、可见性、有序性。

原子性就是多个操作要么都成功,要么都失败。可见性就是一个线程修改了变量值,其他线程能够被通知,能够知道。有序性就是jvm指令的顺序,最终结果的顺序性。

synchronized通过jvm指令monitor同步机制来保障这3点特性。  volatite通过store、load屏障指令,强迫jvm重读volatile修饰的变量,禁止进行jvm的指令重排。


线程间的通信: wait notify notryall,必须在同步方法中,必须持有monitor所有权。


Hook钩子线程:通过Runtime.getRuntime().addShutdownHook(new

Thread(){})来进行注入。

线程的异常设置:Thread.setDefaultUncaughtExceptionHandler

线程的异常查找过程: thread>group>全局默认>System.err


线程池:管理线程的池子。

         任务队列:缓存提交的任务

         线程数量管理功能:init  max core

         任务拒绝策略:线程太多

         线程工厂:定制

         QueueSize:任务数量

         Keepedalive:维护的时间间隔

通过Executors来构建Executor或者是ExecutroService的线程池实例。


线程上下文与类加载器:jvm的双亲委派机制是要求子类委托父类,让父类优先加载,父类没有寻找到class,则再由一级级的子类进行寻找及加载。但是有时候jvm有些核心接口及类被根加载器或者扩展加载器或者系统加载器加载了,但它又必须实例化它的实现类,而这些实现类却在第三方的jar里面,比如我们常见的mysql的Class.forName(driver)的注册,这时如果不使用线程上下文加载器,则这个实现类是无法加载的。1是无法new,因为不知道实现类是哪个;2是不指定类加载器,则使用当前的类加载器,此时DriverManager的类加载器就是根加载器,肯定是无法加载第三方jar包的类的。


加载阶段---》连接阶段----》初始化阶段

连接阶段:验证、

准备、 解析


类的初始化6种方式:

         new

         访问类静态方法

         访问类静态变量

         初始化子类会导致父类初始化

         反射Class.forName()

         启动类运行main函数

其中

         构造某个类的数组时不会初始化 Simple[] simples = new Simple[10]

         引用类的静态常量不会导致  final static,因为在编译阶段,常量池已经存在了。



         类的加载阶段:将class文件的二进制数据读取到内存,生成一个Class对象,作为访问方法区数据结构的入口。

         还有通过asm在程序运行时动态生成。


         连接阶段,又分三个子阶段:

         验证:文件格式验证:版本号、数据类型、md5、恶意代码等

                     元数据验证:父类、接口是否存在合法;final、抽象类、重写、重载等

                     字节码:控制、分支,jvm指令不会跳转到其他不合法指令中,类型转换合理

                     符号验证:通过描述的字符找到相关的类,私有方法、变量访问等

         准备:类变量(静态)设置初始值,分配到方法区(实例变量被分配到堆内存)

         解析:在常量池中查找这些方法、变量等的符号引用。把符号引用替换成直接引用。


         类的初始化阶段clinit:对类的静态变量进行赋值,顺序是根据源码的顺序一致。静态代码块对后面的变量能够赋值,但不可访问。 clinit不一定非出现,接口只有在静态变量存在时才会有clinit方法。父类的clinit方法先执行。



根类加载器bootstrap:

         -Xbootclasspath指定路径,通过System.getProperty(“sun.boot.class.path”)获取;

扩展类加载器sun.misc.Launcher$ExtClassLoader:

         加载JAVA_HOME的jre\lb\ext子目录下的类库,通过java.ext.dirs进行获取;

系统类加载器ClassLoader:

         classPath下的类库,通过系统属性java.class.path进行获取。

可以通过重写ClassLoader的方法findClass进行自定义的类加载器编写。


双亲委托机制,子类加载器会一步一步查找上级类加载器,从父加载器开始进行查找类,如果没有找到,则委托下一级子类加载器进行查找。

可以这样理解,加载类的优先级逐次替减:bootstrap>ExtClassLoader >classloader>自定义加载器。

当然,可以通过指定某个级别的类加载器的父类加载器,这样既可跳过一些类加载器

比如自定义的类加载器指定父类为ExtClassLoader,则可以跳过classloader系统类加载器,

如果指定父类加载器为null,则只会用自定义的类加载器,而不会使用其他加载器了。

         当然也可以重写loadClass方法。比如tomcat就完全改写了findclass及classload方法。tomcat破坏双亲委派,因为它自定义的catalina\share\webapp\jsp等类加载器不会传递到父类优先进行类的加载,而是各自在各自的目录下进行加载。


         volatile:轻量级锁

CPU:缓存:内存,因为存取的速度,CPU、缓存的数据不会及时刷新到主内存,导致数据存取的时候会产生不一致的现象。通过缓存一致性协议,写数据的时候通知其他cpu。


Java内存模型:每个线程都可以访问主内存的变量,每个线程都有本地内存(工作内存),工作内存存储变量副本,线程只有操作了工作内存之后才能存储到主内存,一个线程写操作之后会通知其他线程。内存模型只是一个抽象概念。


         单列设计模式:holder感觉最好。采用内部类的方式。


         通过wait、notify、notifyAll、synchronize、volatile等可以实现很多线程的设计模式:比如future、latch、挂起、每个消息一个线程、生产者消费者等设计模式。


         对于java.util.concurrent下面也有很多高级的并发的工具类,比如ArrayBlockingQueue\ CountDownLatch\ConcurrentHashMap\ReadWriterLock\ReentrantLock等等,这些类基本会用到Sync,lock方法,这些方法最终会跳转到AbstractQueuedSynchronizer等抽象类,会使用到Unsafe类的方法。而这个类Unsafe中大部分都是native方法,保证原子性、有序性、可见性的,可以直接操作内存等。这个方法后续再进行研究。


         同时对于高并发的框架有netty、 mina,这2个框架也使用了大量的Unsafe中的方法。但对于性能来说,多使用buffer,使用缓存是提升性能最重要的方法。

你可能感兴趣的:(7、多线程基础)