首页 > 其他分享 >多线程笔记-1

多线程笔记-1

时间:2022-11-28 16:02:42浏览次数:33  
标签:加锁 monitor synchronized 笔记 指令 线程 多线程 CPU

多线程学习
(一)线程的介绍
一、创建线程的方式
1. 继承Thread类
public class MyThread extends Thread {

@Override
public void run() {
System.out.println(" hello java! " );
}

public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
2. 实现Runnable结构
public class MyThread2 implements Runnable {

@Override
public void run() {
System.out.println("hello java! ");
}

public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
二、 工作线程和守护线程
工作线程 默认创建的线程 main线程
守护线程 后台运行的线程 jvm后台线程
工作线程结束以后 守护线程会一起结束

三、 父线程和线程组
每个线程都有一个父线程的概念,就是在哪个线程里创建这个线程,
那它的父线程就是谁。
线程组 对多个线程进行管理
enumerate():复制线程组里的线程
activeCount():获取线程组里活跃的线程
getName()、getParent()、list(),等等
interrupt():打断所有的线程
destroy():一次性destroy所有的线程
默认线程会加入父线程的ThreadGroup

四、线程优先级
设置线程优先级,理论上可以让优先级高的线程先执行
实际上CPU不一定按优先级高的先执行
优先级一般设置在1-10之间, 1为最低 5为默认 10为最高
创建线程默认继承父线程的优先级
线程的优先级不能大于线程组的优先级

五、 线程初始化
1. 创建你的线程,就是你的父线程
2. 如果没有置顶ThreadGroup, 线程的ThreadGroup就是父线程的ThreadGroup
3. 线程的daemon状态默认是父线程的daemon状态
4. 线程的优先级默认是父线程的优先级
5. 如果没有执行线程的名称,默认就是Thread-0格式的名称
6. 线程的id是全局递增的,从1开始

六、 线程的启动
1. 一旦启动了线程之后,就不能再重新启动了,多次调用start()方法,因为启动之后,
ThreadStatus就是非0的状态了,此时就不能重新调用了
源码
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
2. 启动线程之后,这个线程就会加入之前处理好的那个线程组中
3. 启动一个线程实际上走的是native方法,start0(), 会实际的启动一个线程
4. 一个线程启动之后就会执行run()方法

七、 线程休眠 Thread.sleep()
JDK 1.5之后就引入了TimeUnit这个类
TimeUnit.HOURS.sleep(1)
TimeUnit.MINUTES.sleep(5)
TimeUnit.SECONDS.sleep(30)
TimeUnit.MILLISECONDS.sleep(500)

八、 线程让度 Thread.yield()
担心说某个线程一直长时间霸占着CPU,导致其他的线程很少得到机会来执行,所以设计了
一个yield方法,你调用之后,可以尝试说当前线程先别执行了,CPU可以去执行其他线程了
这个方法常见于debug和test场景下的程序
在这样的一些场景下,他可以复现因为锁争用导致的一些bug
他也可以用于设计一些并发控制的工具,比如说在java.util.concurrent.locks包下的一些类

九、 线程加入 thread.join()
线程A里面开启了一个线程B,线程A如果对线程B调用了join方法,就会导致线程A阻塞,等待
线程B逻辑执行结束后,才会继续执行线程A

十、 线程中断 thread.interrupt()
修改标志位
interrupt打断一个线程,其实是在修改那个线程里的一个interrupt的标志位,打断他以后,
interrupt标志位就会变成true,所以在线程内部,可以根据这个标志位,
isInterrupted这个标志位来判断,是否要继续运行
并不是说,直接interrupt一下某个线程,直接就不让他运行了

(二)volatile
一、 CPU的内存模型
1. 总线加锁(已淘汰)
2. CPU的 MESI 缓存一致性协议(指令) 变量值修改 其他CPU中缓存立马过期
CPU的 嗅探机制 嗅探发现变量过期,立马重新读取变量值
内存屏障 lock前缀指令 -> 内存屏障

二、 Java内存模型
read(从主存读取)
load(将主存读取到的值写入工作内存)
use(从工作内存读取数据来计算)
assign(将计算好的值重新赋值到工作内存中)
store(将工作内存数据写入主存)
write(将store过去的变量值赋值给主存中的变量)

三、 并发编程过程中,可能会产生的三类问题
1. 可见性
线程间对变量的修改不可见 volatile保证可见性
2. 原子性
i++ 非原子性操作 volatile不保证原子性
3. 有序性
对于代码,同时还有一个问题是指令重排序,编译器和指令器,
有的时候为了提高代码执行效率,会将指令重排序 volatile保证有序性

四、volatile(保证线程间的可见性,不保证原子性,保证有序性)
1. 保证可见性
对volatile修饰的变量,执行写操作的话,JVM会发送一条lock前缀指令给CPU,
CPU在计算完之后会立即将这个值写回主内存,同时因为有MESI缓存一致性协议,
所以各个CPU都会对总线进行嗅探,自己本地缓存中的数据是否被别人修改
如果发现别人修改了某个缓存的数据,那么CPU就会将自己本地缓存的数据过期掉,
然后这个CPU上执行的线程在读取那个变量的时候,就会从主内存重新加载最新的数据了
2. 保证有序性
java中有一个happens-before原则:
编译器、指令器可能对代码重排序,乱排,要守一定的规则,happens-before原则,
只要符合happens-before的原则,那么就不能胡乱重排,如果不符合这些规则的话,
那就可以自己排序
内存屏障 禁止重排序
四种屏障
LoadLoad屏障:
Load1;LoadLoad;Load2,确保Load1数据的装载先于Load2后所有装载指令,
他的意思,Load1对应的代码和Load2对应的代码,是不能指令重排的
StoreStore屏障:
Store1;StoreStore;Store2,确保Store1的数据一定刷回主存,对其他cpu可见,
先于Store2以及后续指令
LoadStore屏障:
Load1;LoadStore;Store2,确保Load1指令的数据装载,先于Store2以及后续指令
StoreLoad屏障:
Store1;StoreLoad;Load2,确保Store1指令的数据一定刷回主存,对其他cpu可见,
先于Load2以及后续指令的数据装载

(三)synchronize
一、 加锁
synchronized可以对两种对象加锁,对象实例,Class对象
对类加锁,也是在针对一个对象实例进行加锁,其实他的意思就是对那个类的Class对象进行加锁
synchronized一个静态方法,就是对这个类的Class对象加锁
二、 monitor
monitorenter指令
每个对象都有一个关联的monitor,比如一个对象实例就有一个monitor,
一个类的Class对象也有一个monitor,如果要对这个对象加锁,
那么必须获取这个对象关联的monitor的lock锁
他里面的原理和思路大概是这样的,monitor里面有一个计数器,从0开始的。
如果一个线程要获取monitor的锁,就看看他的计数器是不是0,如果是0的话,
那么说明没人获取锁,他就可以获取锁了,然后对计数器加1
这个monitor的锁是支持重入加锁的,什么意思呢,好比下面的代码片段
synchronized(myObject) {
// 一大堆的代码
synchronized(myObject) {
// 一大堆的代码
}
}
monitorexit指令
如果一个线程第一次synchronized那里,获取到了myObject对象的monitor的锁,计数器加1
然后第二次synchronized那里,会再次获取myObject对象的monitor的锁,这个就是重入加锁,
然后计数器会再次加1,变成2这个时候,其他的线程在第一次synchronized那里,会发现说
myObject对象的monitor锁的计数器是大于0的,意味着被别人加锁了,然后此时线程就会
进入block阻塞状态,什么都干不了,就是等着获取锁 接着如果出了synchronized
修饰的代码片段的范围,就会有一个monitorexit的指令,在底层。
此时获取锁的线程就会对那个对象的monitor的计数器减1,
如果有多次重入加锁就会对应多次减1,直到最后,计数器是0

三、 线程间的通信 wait和notify/nofifyAll
wait与sleep的区别:前者释放锁,后者不释放锁
wait(),必须是有人notify唤醒他
wait(timeout),阻塞一段时间,然后自己唤醒,继续争抢锁
wait与notify,必须在synchronized代码块中使用,因为必须是拥有monitor lock的线程才可以执行wait与notify操作
因此wait与notify,必须与synchornized一起,对同一个对象进行使用,
这样他们对应的monitor才是一样的
notify()与notifyall():前者就唤醒block状态的一个线程,后者唤醒block状态的所有线程




标签:加锁,monitor,synchronized,笔记,指令,线程,多线程,CPU
From: https://www.cnblogs.com/imtm/p/16932415.html

相关文章

  • 从JMM模型复盘Java多线程
    从JMM模型复盘Java多线程多线程的由来任何事情都是有一个发展的历程,多线程也是,我们聊聊多线程的历史。最开始的时候什么都没有,只有CPU和磁盘的概念。之后人们觉得CPU......
  • velocity-1.7学习笔记
    Velocity是由Apache软件组织提供的一项开放源码项目,它是一个基于Java的模板引擎。通过Velocity模板语言(VelocityTemplateLanguage,VTL)定义模板(Template),并且在模板中不包......
  • 吴军《浪潮之巅(下)》阅读笔记---信息时代的科学基础
    吴军《浪潮之巅(下)》阅读笔记---信息时代的科学基础工业革命和颠覆式创新的范式:现有产业+新技术=新产业。从工业革命之前一个世纪开始一直到二战之前,科学基础......
  • 罗剑锋的C++实战笔记-学习笔记(3)
    书接上文,三句名言镇楼。三句名言镇楼任何人都能写出机器能看懂的代码,只有优秀的程序员才能写出人看懂的代码两种写程序的方式:把代码写的非常复杂,以至于"看不出明显......
  • 全链路压测效能10倍提升的压测工具实践笔记【开源】【原创】
    BSF全链路自动化测试工具(autotest),可批量导入样本,自动录制样本,自动样本清洗,自动化压测,自动输出压测报告,让开发和压测人员性能测试的效能提升10倍!!!背......
  • 罗剑锋的C++实战笔记-学习笔记(2)
    书接上文,三句名言镇楼。三句名言镇楼任何人都能写出机器能看懂的代码,只有优秀的程序员才能写出人看懂的代码两种写程序的方式:把代码写的非常复杂,以至于"看不出明显......
  • redisOject 和 底层数据结构对应 学习笔记
    笔记摘抄自https://pdai.tech/md/db/nosql-redis/db-redis-data-type-enc.htmlredisObject查看编码命令setk11objectencodingk1setk2helloobjectencoding......
  • 罗剑锋的C++实战笔记(学习笔记1)
    本系列文章记载学习一门在线课程罗剑锋的C++实战笔记过程中的心得体会,只会记录新增加的知识点,那些心中已熟透的知识点,不会重复记录。c++的主战场在Linux上,现在开发Wi......
  • 驱动开发学习笔记---阻塞和非阻塞IO
    一、阻塞和非阻塞简介当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式IO就会将应用程序对应的线程挂起,直到设备资源可以获取为止。对于非阻塞IO......
  • Java多线程中锁的理解与使用
    1.简介锁作为​​并发​​共享数据,保证一致性的工具,在JAVA平台有多种实现(如synchronized和ReentrantLock等)。2.Java锁的种类公平锁/非公平锁可重入锁独享锁/共享锁互......