多线程编程核心技术总结(读周志明书籍的总结)
多线程编程核心技术总结
1.Java多线程基本技能
1.1进程和线程的概念:
进程是独立的程序,线程是在进程中独立运行的子任务。
1.2使用多线程
1.2.1实现方法:继承Thread类,重写Runnable接口。
1.2.2线程安全问题:并发修改公共的实例变量,i++,i--
1.3线程Thread类的一些方法:
currentThread() 放回代码段正在被那个线程调用
isAlive() 判断线程是否处于活动状态
sleep() 使得当前线程退出CPU片段,等待获取锁
1.4停止线程
1.4.1使用interrupt()方法不是使线程马上停止执行,而是打了个暂停的标记。
this.interrupted():测试当前线程是否已经中断,this.isInterrupted():测试当前线程是否已经中断。
1.4.2停止线程常用方法--异常法(抛出异常)
1.4.3 在sleep()时,去interrupted。
1.4.4 yield()方法的作用是放弃当前的CPU资源,放弃多长时间不确定,立即加入到CPU的竞争中。
1.5线程优先级
优先级从1—10不等,设置用setPriority()方法,但是优先级具有随机性,优先级高的不一定先执行。
1.6守护线程
Java线程有两种,一种是守护线程(Daemon)另一是用户线程,垃圾回收线程就是典型的守护线程。守护线程为其它线程的运行提供便利,当其他线程都结束时,守护线程才与JVM一同结束工作。
2.对象及变量的并发访问
线程安全问题出现在“实例变量”中,如果是方法内部的私有变量,则不存在线程安全问题。
2.1synchronized同步方法
(1)Synchronized获得对象锁,那个线程先执行带synchronized关键字的方法;那个线程就持有该方法所属对象的锁,其它访问同一对象的线程就只能等待;
(2)只有共享资源的读写才需要做同步化;
(3)A线程先持有object对象的锁,B线程可以以异步的方式调用object对象的非synchronized方法。
A线程先持有object对象的锁,B线程如果调用object对象的synchronized方法,则需要等待,即同步。
(4) synchronized锁重入:synchronized方法内部调用本类其它的synchronized方法时,是永远可以得到锁的。
(5) 出现异常,锁自动释放。同步不具有继承性。
2.2 synchronized同步语句块
Synchronized方法是对当前对象进行加锁,而synchronized代码块是对某一个对象加锁。Synchronized方法效率太低,synchronized代码块更加细粒化
(1)synchronized同步方法
1)对其他同一对象的synchronized方法或synchronized(this)同步代码块呈阻塞状态。
2)同一时间只有一个线程可以执行synchronized同步方法中的代码
(2)synchronized(this)同步代码块
1)对其他同一对象的synchronized方法或synchronized(this)同步代码块呈阻塞状态。
2)同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码
(3)synchronized(对象x)同步代码块
1)在多个线程持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(对象x)同步代码块;
2)持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(对象x)同步代码块。
对象监视器必须是同一个对象才是同步的,否则是异步调用。
(4)静态同步Synchronized方法与Synchronized(class)代码块
Synchronized关键字用于static方法上时,如果这样写,那是对当前类进行加锁与Synchronized(class)代码块作用一致。Synchronized关键字用于非static方法上时,如果这样写,那是对当前类的对象进行加锁。
(5)多线程死锁:比如两个对象持有对象锁,在同步方法(或者块)内部同时请求另一个对象的对象锁。
(6)只要对象不变,即使对象属性被改变,运行的结果还是同步的。
2.3 volatile修饰属性
(1) volatile强制从公共堆栈中取得变量的值,而不是从线程私有数据栈取得变量值。
(2) volatile与synchronized关键字比较
1)volatile是线程同步的轻量级实现,其性能肯定比synchronized要好,并且volatile只能修饰变量,而synchronized可修饰方法、代码块。
2)多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
3) volatile保证数据的可见性,不保证原子性;而synchronized既可以保证原子性也间接保证可见性。
4) volatile解决变量在多线程之间的可见性,而synchronized解决的是多线程之间访问资源的同步性。线程安全包括原子性和可见性。
线程工作内存中: read (load use asign) store write 括号中的三步非原子性。
(3)synchronized可以保证同一时刻,只有一个线程可以执行某一个方法或某一个代码块。包括两个特征:互斥性和可见性。同步synchronized不仅可以解决一个线程看到对象处于不一致状态,还可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护之前所有的修改效果。
3.线程间通信
3.1等待通知机制的实现
(1)Wait()/notify()/notifyAll()都是Object类的方法。调用这些方法之前需先获得对象锁。调用wait()方法的线程将会在该代码行处停止且释放锁,进入“预执行队列”被唤醒之后才有机会获取锁。执行Notify()方法的线程不会释放锁,随机唤醒一个正在等待“共享资源”的线程,如果一个阻塞的共享资源的线程都没有,则忽略。notifyAll()唤醒全部的,等本线程退出CPU,这些线程去争抢锁。
(2)每个锁对象都有两个队列,一个是就绪队列(可运行),一个是阻塞队列(要唤醒之后才是可运行)。
(3)sleep方法不释放锁。遇到异常会释放锁。
(4)wait(long)等待long时间,如果没有其他线程来唤醒它,自动唤醒。使用wait和notify时要注意wait条件发生变化,容易造成程序逻辑混乱。
(5)生产者、消费者模式实现:生产者中Value值不为空,则lock.wait();否则进行设置值的操作,然后lock.notify()唤醒消费者。消费者中value为空,则lock.wait(),否则进行取值操作,然后lock.notify()唤醒生产者。
多生产-单消费:使用notify会造成假死,使用notifyAll来解决。
(6)通过管道流来进行线程间通信,PipedInputStream,PipedOutputStream,PipedReader和PipedWriter。最后outputStream.connect(inputStream)
3.2方法join的使用
(1)方法join是使所属的线程对象x执行run方法的任务,而使当前线程z进行无限期的阻塞,等待线程销毁后在继续执行z后面的代码。Join过程中,如果当前线程被中断,则出现异常。
(2)join(long)内部是使用wait来实现的,所以会释放锁。Sleep却不会。
3.3类ThreadLocal的使用
(1)这个类解决每个线程都有自己的共享变量的问题。
ThreadLocal t=new ThreadLocal(); 这个对象有get()、set()方法。解决的是变量在不同线程间的隔离性。也就是不同线程可以有自己的值,并放在ThreadLocal中保存。
(2)InheritableThreadLocal类,可以在子线程中取得父线程继承下来的值。
4.Lock的使用
4.1 ReentrantLock类,目的是为了实现同步异步,比synchronized更加灵活。
(1)首先 Lock lock =new ReentrantLock(); 然后在需要同步的方法中的首行使用lock.lock(),方法的结束行使用lock.unlock()。效果和使用synchronized关键字一样。当然也可以去锁一部分代码块。
(2)Condition对象实现指定线程的等待、通知,相当于wait和notify升级版。Synchronized相当于只有一个condition对象。
首先 Lock lock =new ReentrantLock();
Condition condition=new Condition();
在方法中先调用lock.lock()获得同步监视器(否则报异常),然后用condition.await()来让这个线程进入阻塞队列。
(3)使用Condition实现等待通知
conditionA.await()进入阻塞,conditionA.signal()唤醒指定的这个线程。
signalAll()相当于notifyAll。
(4)使用多个Condition实现通知部分线程
Condition conditionA=new Condition();
Condition conditionB=new Condition();
conditionA.await()只有conditionA.signal(All)可以唤醒
(5)也可以实现生产者消费者模式,lock.lock()和signall方法。
(6)公平锁:线程获取的顺序是按照线程加锁的顺序来分配的,先来先得。非公平锁,采用抢占机制,随机获得锁。reentranLock类默认情况使用的是非公平锁。
(7)int getHoldCount()返回当前线程调用lock()方法的次数。
(8)int getQueueLength()返回正在等待获取此锁定的线程估计数。
(9)int getWaitQueueLength(Condition condition )返回等待此锁定相关的给定条件condition的线程估计数。
(10)boolean hasQueuedThread(thread) 查询指定线程是否在等待此锁,
方法boolean hasWaiters(Condition condition) 作用是查询是否有线程正在等待与此锁定有关的condition条件。
(11)可使用多个Condition对象实现业务的顺序执行。交替链接唤醒。
A方法中 conditionA.await()---conditionB.signalAll--
B方法中 conditionB.await()---conditionC.signalAll--
C方法中 conditionC.await()---conditionA.signalAll--
4.2ReentrantReadWriteLock类
读锁是共享锁,写锁是排他锁,读读共享是异步的,写读、写写都是互斥的同步的。
(1)ReentrantReadWriteLock类实现读读共享
ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
lock.readLock().lock();
……代码段
lock.readLock().unlock()
是异步的
(2)ReentrantReadWriteLock类实现写写互斥
ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
lock.WriteLock().lock();
……代码段
lock.WriteLock().unlock()
是同步的
(3)ReentrantReadWriteLock类实现读写互斥
ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
方法1
lock.readLock().lock();
……代码段
lock.readLock().unlock()
方法2
lock.WriteLock().lock();
……代码段
lock.WriteLock().unlock()
两个方法是互斥的。
5.单例模式与多线程
(1)立即加载/饿汉模式
立即加载是在使用类的时候对象就已经创建完毕,常见的实现办法就是new实例化。
Public class MyObject{
private static MyObject myObject=new MyObject();
private MyObject(){
}
public static MyObject getInstance(){
Return myObject
}
}
(2)延迟加载/懒汉模式
在调用getInstance()方法时实例才被创建,在方法中实例化对象。
Public class MyObject{
private static MyObject myObject;
private MyObject(){
}
public static MyObject getInstance(){
if(myObject!=null){
}else{
myObject=new MyObject();
}
return myObject
}
}
懒汉模式在多线程环境下容易出错,无法保持单例。
解决方案:
1.方法声明synchronized-但是效率低下
2.同步代码块—(全部方法)效率低,(关键语句)无法单例。
3.同步代码块,使用DCL双检测,多数采用这种方式。即是说在内部关键方法里再判断一次。
Public class MyObject{
Valotile private static MyObject myObject;
Public static MyObject getInstance(){
if(myObject!=null){
}else{
Thread.sleep(3000);
Synchronized(MyObject.class){
If(myObject==null){
myObject=new MyObject();
}
}
}
return myObject
}
}
4.static代码块实现单例模式
利用静态代码块在使用类时就已经执行的特点。