线程总结
线程的理解:程序要走的线路的整个过程,是程序执行流的最小单元。
程序可以由多个进程组成,进程有线程组成。如果没有用多线程,那该进程就一个主线程
程序也可以理解为一个进程,如果某个进程新建了多个线程,就会为进程开辟了多个个“线路”,但这个时候还没有开始走。只有当进程调用了 某个线程的启动,CPU才会在这条“线路”上走,这条“线路”才开始分配资源,尤其是栈内存空间。从线程的创建,到线程的启动,以及线程代码的执行,其中还包括wait、sleep、notify等方法导致执行权甚至是执行资格的丧失或是恢复,直到线程正常结束和非正常结束,这才是这条“线路”的整个“过程”,这也是线程真正的含义。
多线程安全问题:
原因:多个线程 操作 共享的数据 的代码不止一条。
解决:同步,synchronized、lock.lock()、lock.unlock()、readwriteLock.getReadLock.lock()、readwriteLock.getWriteLock.lock()...
synchronized:
用了同个锁,不管是不是同个方法 或者 同个同步块,都是同步的,都必须要等持有锁的对象执行完代码释放了锁 后面的线程才可以拿到锁 执行同个 或 不同的 被同步的代码。
同步函数锁的是this
静态函数锁的是该类的Class对象,也就是字节码,对于字节码的比较用 == 显得更专业
ReentrantLock:
和synchronized使用类似,lock方法相当于synchronized{,unlock方法相当于},注意最好将unlock放在finally里
ReentrantReadWriteLock
读和读之间即使上锁了也不会同步的,写锁上锁了 其他线程读和写都不能操作了
getReadLock.lock()、getReadLock.unlock()、getWriteLock.lock()、getWriteLock.unlock()
线程空间分配解析:
创建线程必然有上下文环境,这个上下文环境本省就是一个线程,暂且称之为主线程。
无论是主线程还是子线程1 2 3 之类的,java虚拟机都会为 每个线程 创建一个独立的 栈内存空间,然后方法入栈,临时变量表记录所有变量,PC记录方法走到哪一步。
线程是程序要走的 某一条线路的 整个过程,所以这个线程之间的 栈内存 空间是不会数据共享的,也就是run方法的局部变量是每个线程 私有的。
但是run方法的存在是有上下文的,这个上下文就是类,无论是runnable的实现类,还是runnable实现类Thread的子类,都是一个类,有类就会有成员变量,这些成员变量是共享的数据。
操作线程共享的数据 无论是基本数据变量还是引用变量,都是只有一份,有的指向了常量池、有的指向了堆内存、有的指向了方法区。
如果操作共享数据的代码不止一条,比如先读后写,在读完之后该线程失去了执行权,被其他线程操作了,这个时候就容易发生安全问题。
死锁:
在解决了线程安全的问题后要注意会不会发生死锁,我们是不希望发生死锁的,但要能判断出。
我们在实际应用中是永远也不会让我们去写 死锁程序的,但是面试的时候却会遇到
死锁产生的原因:同步中嵌套同步
同步中嵌套同步,并且持有的锁不一样,多个线程访问的时候就会有问题了。
比如线程1拿到了A锁,没有拿到B锁,线程2拿到了B锁,却没有拿到A锁,但是只有拿到自己想要拿的锁之后才能将代码执行完,才会释放已持有的锁,这就会死锁。
因此,
应避免嵌套同步
(如果同步代码中有同步代码,但是 却是同一个锁,本质上来说并没有嵌套,因为锁是一样的,
如果是同步代码块只要写一个就好了,但是如果是递归调用同步方法却是没法避免的,可这不算嵌套,可以和事务嵌套进行类比区分)
就算是非要嵌套,也不要嵌套多个,最多两个,并且锁的顺序不要颠倒。
比如各个同步块中都是先锁A,后锁B,那么多线程调用也不会出现死锁,
因为假如 线程1拿到A锁 或者 甚至拿到B锁的时候 都持有了A锁,线程2根本拿不到A锁 更加拿不到A锁住的同步代码中的B锁了,那么线程1也不会因拿不到B锁而堵塞
同样的道理,如果非要嵌套多层同步,必须要确保锁的顺序要一致,在各个方法中都必须一致