本文涉及该书的一到三章.并按自己的习惯调整了相关内容的位置.仅供参考.很多内容可能不太详细,不太适合第一次阅读.
使用多线程的原因:
提升系统的运行效率
实现多线程的两种方法:
- 继承Thread类
- 实现Runnable接口
区分原因:Java不支持多继承,所以已经有继承的情况下,选择实现Runnable接口
两者使用上的不同:
继承ThreadA的线程类获得start()等方法,而实现Runnable接口的ThreadC并没有那些方法.
所以:通常实现Runnable接口的类用如下方式:
操作新建的thread对象即可.
线程状态图:
通过线程的方法可以更改线程的状态.
需要注意的是: 线程执行run()方法会直接开始运行线程run()方法里面的方法体.而start()方法,wait()等方法,是进入一个runnable的状态,稍区别于running状态
sleep()方法:
休眠this.currentThread()返回的线程
注意点: Thread.currentThread()和this的区别:
将线程对象(myThread)以构造参数的方式传递给Thread对象(newThread)进行start(),直接启动的是newThread,newThread的start方法会调用myThread中的run方法.这种情况下Thread.currentThread和this的引用是不同的
suspend()方法:
挂起一个线程,但是不会释放锁
resume()方法:
将一个suspend()的线程恢复至运行状态
缺点:这两个方法极易造成对公共同步对象的独占,所以已经废弃;
如: suspend的线程内在执行printIn()方法,因为这个方法是同步的(内是synchronized),如果这个线程suspend将导致其他线程无法使用printIn()方法.
另外,他们也很容易因为线程暂停而导致数据不同步;
如:suspend的线程里还有对成员变量的赋值操作,但是在赋值前被挂起,则其他线程获得的成员变量的值还是修改前的,数据则出现不同步
yield()方法:
放弃当前CPU资源,但是放弃的时间不定,可能放弃后马上重新获取,也可能长时间以后获取
stop()方法:
强制线程停止.
缺点:
- 强制停止,可能导致有一些请理性的工作还得不到完成;
- stop()的同时会释放锁,这可能导致数据的不一致
wait()方法:
线程暂停,释放持有锁.
需要注意的是: 如果一个wait的线程被notify唤醒,执行结束后,没有其他的notify语句,那么等待该锁的其他wait的线程依旧会处在等待的状态.不会因为这个锁对象不再被占用了就重新开始执行.
wait(long)方法:
等待某一段时间,如果在期间内没被唤醒,则超时唤醒.
notify()方法:
执行后,待synchronized的方法体执行完毕退出代码块后,释放锁
notifyAll()方法:
会唤醒所有wait等待锁的线程
join()方法:
使所需线程对象x正常执行完run中的任务,而当前线程无限期阻塞,等x线程销毁后,再执行join方法后面的代码
join()方法与synchronized类似,但是join的原理是内部使用wait等待,而synchronized则是使用对象监视器原理
join方法执行的过程中当前线程被中断则当前线程报出异常
join(long)方法:
设定等待的时间
join(long)类似于sleep(),区别是join内部使用wait,所以会释放锁,而sleep()方法,不会释放锁对象
join方法在使用过程中可能会遇到join方法后面的代码先执行的以外,原因是join也是在等待锁对象,如果先抢到锁,则导致他后面的代码可能提前执行.
synchronized相关知识
- 同一个锁对象,A,B线程访问同一个对象的synchronized,非静态方法(即便是不同的方法)是同步的,非synchronized方法可异步执行;同一个对象的synchronized的静态方法,也是同步的;两个相同的类的对象的非静态方法,是异步的;两个相同的类的对象的static方法,是同步的(synchronized关键字加到static静态方法上是对该类class加锁).
synchronized(class),同步代码块的锁为类对象,那么效果跟synchronized关键字加到静态方法上一致.
需要注意的是:
- String对象有常量池缓存功能,所以避免使用string对象作为锁,会有意想不到的结果.
此处引申:String a = "AA";String b = "AA";此时b对象跟a对象指向一个"AA"对象;但是如果String b =new String("AA");则b跟a对象不是一个对象.详细请移步String常量池功能.- 锁对象为数据类型,两个类取同一个数据,如果第二个类取该数据时,数据对象已发生变化了,如锁对象String a = "aa";a = "bb";对象修改发生在第二个类取该数据之前,那么这两个方法方法异步. 注意:如果是个bean类,如new Student(); 调用student对象的setName()方法,修改对象的属性值,锁依旧还是student这个对象,方法依旧是同步的.
- 如果一个类对象的实例变量get方法非synchronized,而set方法synchronized,那么set阻塞时,get得到的值可能跟预期的不同,形成脏读.解决办法是两个方法都设置为synchronized
- synchronized拥有锁重入的功能,得到锁的线程可重复得到锁(即在同步方法里调用其他同步的方法是可以的),且支持父子类的继承环境(即使同步方法是从父类继承的亦可).
- 而子类重写父类的方法,如果父类该方法是synchronized的,子类不添加该关键字,该方法是不同步的.即synchronized关键字不继承.
解决同步方法执行时间过长的弊端:
- 可以用同步代码块,将不影响的语句放在同步代码块外,提升效率
- 互不影响的同步代码块,可以设置不同的锁对象,这两个同步代码块就变成异步执行的.
JVM -Server时可能出现的问题:公共堆栈及线程私有堆栈值不同步问题
在JVM在-server模式下,为了获取线程运行的效率,线程一直在私有堆栈中取值,更新公共堆栈的值可能线程并不能看到.
解决办法:
如图:使用volatile关键字,让线程强制从公共堆栈取值
volatile修饰的变量值,在多个线程中可见,且强制从公共堆栈中取变量的值,而不是从线程私有数据栈中取得变量的值.
volatile的致命缺点的是不支持原子性
synchronized,volatile比较
~ | volatile | synchronized |
---|---|---|
修饰 | 只用于变量 | 修饰方法或代码块 |
性能 | 轻量级,性能更好 | 随着JVM更新,性能有所提升 |
多线程访问 | 不阻塞 | 阻塞 |
数据可见性,原子性 | 可见,但不原子 | 可见并原子 |
用途 | 解决变量在多个线程之间的可见性 | 解决多个线程之间访问资源的同步性 |
注意:
通常用原子类解决volatile关键字的原子性问题,从而克服线程安全问题,但是要注意,原子类方法是线程安全的,但是原子类的方法之间不是原子的.多个方法被调用,依旧会有线程安全问题.此时需要加synchronized关键字同步.
对synchronized原子性的解释:
synchronized可以保证在同一时刻,只有一个线程可执行一个方法或一个代码块,同步synchronized不仅可以解决一个线程看到对象不一致的状态,还可以保证进入同步方法或代码块的每个线程都看到由同一个锁保护之前所有的修改效果.
ThreadLocal,每个线程自己的共享变量
get()返回放入的值,没有则返回Null
set()方法,存入值
继承ThreadLocal重写initialValue()方法可以使之具有初始值
InheritableThreadLocal可在子线程中取得父线程继承下来的值.
要修改继承的值,重写childValue()方法
注意:如果子线程取值的同时,主线程修改了值,子线程取的值还是旧值.
举例:
主方法中,新建线程a,b,那么ab就是main线程的子线程
继承InheritableThreadLocal,设置初始化值为 new Date().getTime();子类继承时,获得的是childValue的结果
下面是AB线程类的代码
结果:
在主方法中,B线程启动前,InheritableThreadLocal的值被修改了.所以b线程获得的初始化值变化了.而A的值未变.