线程是进程中可独立执行的最小单位,也是 CPU 资源(时间片)分配的基本单位,同一个进程中的线程可以共享进程中的资源,如内存空间和文件句柄。线程有一些基本的属性,如id、name、以及priority。
id:线程 id 用于标识不同的线程,编号可能被后续创建的线程使用,编号是只读属性,不能修改。
name:线程的名称,默认值是 Thread-(id)
daemon:分为守护线程和用户线程,我们可以通过 setDaemon(true) 把线程设置为守护线程。守护线程通常用于执行不重要的任务,比如监控其他线程的运行情况,GC 线程就是一个守护线程。
setDaemon() 要在线程启动前设置,否则 JVM 会抛出非法线程状态异常,可被继承。
priority:线程调度器会根据这个值来决定优先运行哪个线程(不保证),优先级的取值范围为
1~10,默认值是 5,可被继承。Thread 中定义了下面三个优先级常量:
最低优先级:MIN_PRIORITY = 1
默认优先级:NORM_PRIORITY = 5
最高优先级:MAX_PRIORITY = 10
线程池的原理:
ThreadPoolExecutor线程池的执行方法中,我发现一个出现频率很高的 API addWorker , 线程池拿到任务之后丢给了 Worker 类处理, 这个类还创建了 Thread 对象,就是创建线程的时候,传入的并不是我们给线程池的那个 Runnable 对象,而是 Worker 对象本身,也就是说线程 start 的时候,Worker 类的 run 方法会被执行,然后开启了一个 while 循环,循环的意思是:只要 task 对象不为空,那么就会一直调用 task = getTask(),直到获取到的 task 对象为空了才会停止循环,getTask 其实就是往阻塞队列中取出 Runnable 对象。
线程池复用线程的原理,创建 Thread 对象的时候传入的不是我们的 Runnable 对象,而是通过线程池自定义的 Runnable 类,这个类主要的作用不仅是执行我们的 Runnable 对象,当我们传入的任务被某个线程执行完毕之后,它还会遍历阻塞队列中其他未执行的任务,这样就能达到一个线程执行多个 Runnable 对象的效果,这个就是线程池复用线程的原理。
往线程池添加一个新的任务时,如果核心线程处于空闲状态,任务会直接交由核心线程处理,否则任务会存放到阻塞队列中,当阻塞队列中的任务数量超过设定的最大值时,才会开启非核心线程去执行,如果当前任务总量 > 阻塞队列的最大容量 + 最大线程数时,线程池则会拒绝执行该任务。
https://www.jianshu.com/p/a6c3df1f12c4
java的内存模型Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。
原子性
在Java中,为了保证原子性,提供了两个高级的字节码指令monitorenter和monitorexit。在synchronized的实现原理文章中,介绍过,这两个字节码,在Java中对应的关键字就是synchronized。因此,在Java中可以使用synchronized来保证方法和代码块内的操作是原子性的。
可见性
Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值的这种依赖主内存作为传递媒介的方式来实现的。Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。除了volatile,Java中的synchronized和final两个关键字也可以实现可见性。只不过实现方式不同,这里不再展开了。
有序性
在Java中,可以使用synchronized和volatile来保证多线程之间操作的有序性。实现方式有所区别:volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。好了,这里简单的介绍完了Java并发编程中解决原子性、可见性以及有序性可以使用的关键字。读者可能发现了,好像synchronized关键字是万能的,他可以同时满足以上三种特性,这其实也是很多人滥用synchronized的原因。但是synchronized是比较影响性能的,虽然编译器提供了很多锁优化技术,但是也不建议过度使用
https://juejin.cn/post/6844904136937324552