【读书笔记】Java多线程编程核心技术

Java多线程编程核心技术,实在是一本好书,少有的看的进去的,看完这本书,理论知识算是很扎实了,建议有空的同学赶紧翻了看看。

第一章,java多线程技能。

  1. 关键技术点,五个:线程的启动,如何暂停,如何停止线程,线程的优先级,线程安全相关的问题。

  2. 进程可以理解成一个在运行的 exe,线程更细分,比如下载,上传,传输文件等等。

  3. 多线程编程有两种,一种继承thread类,一种实现runnable接口。线程是一个子任务,调用就调用了,自己的事情要继续做,不用等线程的返回。thread的start和run有区别的,前者是异步的,启动就启动了,我执行自己的事情,后者是同步的,要等待run完了才会做自己的事情。注意执行start()方法的顺序不代表线程启动的顺序。不按照代码的顺序来,为啥啊???

  4. 第二种,runnable。使用继承Thread类的方式来开发多线程应用程序在设计上是有局限性的,因为Java是单根继承,不支持多继承,所以为了改变这种限制,可以使用实现Runnable接口的方式来实现多线程技术。

  5. 非线程安全主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况,进而影响程序的执行流程。举例用synchrioned来进行控制。录几个例子,println(i--),虽然printkn内部是同步的,但是i--是在外面就已经乱了,所以外面的方法还是要同步。几个常用的线程方法,currentthread获得当前线程的名字之类,isalive判断线程是否激活状态,额在使用isAlive()方法时,如果将线程对象以构造参数的方式传递给Thread对象进行start()启动时,运行的结果和前面示例是有差异的。造成这样的差异的原因还是来自于Thread.currentThread()和this的差异。额getId()方法的作用是取得线程的唯一标识。

  6. 停止线程。Thread.stop()已经废弃,不要用。一,用interrupt()方法,并没有中止,为啥呢。判断中止的方法有两个:interrupted()方法的解释:测试当前线程是否已经中断。当前是main。连续两次调用该方法,则第二次调用将返回false。另外一个呢,this.isInterrupted():测试线程Thread对象是否已经是中断状态,但不清除状态标志。线程状态虽然是中断,但是并没有停止执行,需要人工干预,判断是中断状态,让他break掉。但是for后面的代码还是会执行,怎么办,中断时候就抛出异常就行了,反正也的确是异常。sleep的时候中止呢,如果在sleep状态下停止某一线程,会进入catch语句,并且清除停止状态值,使之变成false。如果先变成终止状态,让他继续执行,完事后sleep也会抛异常。

  7. Stop的终止比较暴力,不太建议。嗯方法stop()已经被作废,因为如果强制让线程停止则有可能使一些清理性的工作得不到完成。另外一个情况就是对锁定的对象进行了“解锁”,导致数据得不到同步的处理,出现数据不一致的问题。

  8. 判断中断后,可以抛异常让程序不能继续执行,也可以用return。但是这样就结束了,上面并不知道你这里其实异常了。在catch块中还可以将异常向上抛,使线程停止的事件得以传播。

  9. 暂停线程。使用suspend()方法暂停线程,使用resume()方法恢复线程的执行。有个问题是一个线程挂起某个方法,这个方法是同步的,导致其他线程用不了这个方法。这是缺点一。缺点二是不同步的问题,数据不同步。跟第一点如出一辙。

  10. yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。处理速度会变慢,没有讲什么场景这么用。

  11. 线程的优先级。线程的优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A是一样的。高优先级的线程总是大部分先执行完,但不代表高优先级的线程全部先执行完。程的优先级与代码执行顺序无关,有一定的规则性。优先级还有随机性,也就是优先级较高的线程不一定每一次都先执行完。

  12. 守护线程:只有当最后一个非守护线程结束时,守护线程才随着JVM一同结束工作。典型的gc垃圾回收器。

 

第二章,对象及变量的并发访问

  1. 对象锁和方法锁区别需要好好研究下,书里的例子不太好理解。

  2. “可重入锁”的概念是:自己可以再次获取自己的内部锁。比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。嗯当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的。

  3. 当一个线程执行的代码出现异常时,其所持有的锁会自动释放。

  4. 同步不可以继承。所以如果想用子类的某个方法,虽然父类的这个方法是同步的,子类不是。还得在子类的方法中添加synchronized关键字,同一个方法的话。

  5. Sync语句同步块。比如A线程调用同步方法执行一个长时间的任务,那么B线程则必须等待比较长时间。在这样的情况下可以使用synchronized同步语句块来解决。

  6. 虽然使用了synchronized同步代码块,但执行的效率还是没有提高,执行的效果还是同步运行的。怎么提高效率呢,缩小同步块里面的执行内容,不要一大堆内容扔进去。

  7. 不在synchronized块中就是异步执行,在synchronized块中就是同步执行。

  8. synchronized代码块间的同步性。 在使用同步synchronized(this)代码块时需要注意的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同步代码块的访问将被阻塞,这说明synchronized使用的“对象监视器”是一个。两个方法都是同步块,一个线程调用a,一个调用b,后来的只能等a执行完毕后再进入b的同步块。

  9. 和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。

  10. synchronized(非this对象)格式的作用只有1种:synchronized(非this对象x)同步代码块。锁非this对象具有一定的优点:如果在一个类中有很多个synchronized方法,这时虽然能实现同步,但会受到阻塞,所以影响运行效率;但如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,则可大大提高运行效率。证明,   synchronized (Anything)这个anything如果一直是一样的,那么就是会阻塞,如果不一样,那么是多个不同的代码块,跟别人是异步的。list递增的方法,如果不是同步,那么多个会同时更新,导致赃读,应该同一时刻只能有一个线程操作递增。

  11. 1)当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。多个线程同一个同步代码块,肯定同步啊。

  12.     2)当其他线程执行x对象中synchronized同步方法时呈同步效果。方法就是同步的,除非不同的同步代码块,this是同步的,非this是异步的。

  13.     3)当其他线程执行x对象方法里面的synchronized(this)代码块时也呈现同步效果。见上面一点。

  14. 静态同步synchronized方法与synchronized(class)代码块。静态的是给Class类上锁,非得给对象上锁。Class上锁,虽然是不同对象但静态的同步方法还是同步运行。疑问,如果不是静态的同步方法,新建类掉这个方法,就不是同步了么,不是啊,那有啥区别,啥情况用这个静态。

  15. 同步synchronized(class)代码块的作用其实和synchronized static方法的作用一样。

  16. 因此在大多数的情况下,同步synchronized代码块都不使用String作为锁对象,而改用其他,比如new Object()实例化一个Object对象,但它并不放入缓存中。因为string是常量池的,不同线程调用同步块,括号里面的字符串是同一个,所以两个线程持有相同的锁,但是对象是可以的。

  17. 同一个类多个同步方法互相阻塞,容易死锁,用同步块解决。

  18. 内置类与静态内置类,完全没接触过,跳过。

  19. 锁对象的改变。两个线程同时获取lock(123)的锁,那就是同步。如果其中一个慢了点,变成456了,那就是异步了。这个是针对字符串。而对象就不一样了,同一个对象就是同一个锁,即使对象的数值变了,还是同一个锁。

  20. Volatile。主要作用是使变量在多个线程间可见。关键字volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。

  21. 他和sync的区别,一,前者轻量级性能好点,但是只能是变量,后者有方法,同步块,所以后者多。二,前者不会阻塞,后者会。三,volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步。四,解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。

  22. 啊volatile关键字解决的是变量读时的可见性问题,但无法保证原子性,对于多个线程访问同一个实例变量还是需要加锁同步。

  23. 使用原子类进行i++操作,AtomicInteger

  24. 关键字synchronized可以使多个线程访问同一个资源具有同步性,而且它还具有将线程工作内存中的私有变量与公共内存中的变量同步的功能,在此实验中进行验证。

  25. 不太理解,同步synchronized不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护之前所有的修改效果。

 

第三章,线程间通信

  1. 两个线程通信,假如共享一个变量,双方要不停的轮询,比较浪费资源。因此有了等待/通知机制。

  2. wait使线程停止运行,而notify使停止的线程继续运行。可是执行之前如果没有加对象锁,会报异常。两者就这样通过自己的内部代码实现通信,互相知道共享的值。

  3. 当方法wait()被执行后,锁被自动释放,但执行完notify()方法,锁却不自动释放。必须执行完notify()方法所在的同步synchronized代码块后才释放锁。

  4. 当线程呈wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException异常。1)执行完同步代码块就会释放对象的锁。2)在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。 3)在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,而此线程对象会进入线程池,等待被唤醒。

  5. 调用方法notify()一次只随机通知一个线程进行唤醒。一个一个麻烦的话,用notifyall()。

  6. 带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。

  7. 生产者/消费者模式实现。例子太复杂了,用到再研究吧。

  8. 进程间通信:一,字节流:其中管道流(pipeStream)是一种特殊的流,用于在不同线程间直接传送数据。使用代码inputStream.connect(outputStream)或outputStream.connect(inputStream)的作用使两个Stream之间产生通信链接,这样才可以将数据进行输出与输入。二,字符流:也可以。

  9. Join:如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()与interrupt()方法如果彼此遇到,则会出现异常。

  10. 方法join(long)与sleep(long)的区别,两者都可以让线程等待long的时间。当执行wait(long)方法后,当前线程的锁被释放,那么其他线程就可以调用此线程中的同步方法了。但是sleep不会释放锁。

  11. 类ThreadLocal主要解决的就是每个线程绑定自己的值,可以将ThreadLocal类比喻成全局存放数据的盒子,盒子中可以存储每个线程的私有数据。

  12. 使用类InheritableThreadLocal可以在子线程中取得父线程继承下来的值。

 

第四章,lock的使用

  1. 着重讲ReentrantLock和ReentrantReadWriteLock的使用。

  2. 第一个在扩展功能上也更加强大,比如具有嗅探锁定、多路分支通知等功能,而且在使用上也比synchronized更加的灵活。

  3. lock()方法获取锁,调用unlock()方法释放锁。

  4. 关键字synchronized与wait()和notify()/notifyAll()方法相结合可以实现等待/通知模式,类ReentrantLock也可以实现同样的功能,但需要借助于Condition对象。而且还可以多路通知,效率比前者高很多。

  5. 使用方法需要自己敲代码,就没仔细看了,用到的时候记住可以替代notify就行了

  6. 锁Lock分为“公平锁”和“非公平锁”,公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序。

  7. 类ReentrantLock具有完全互斥排他的效果,即同一时间只有一个线程在执行ReentrantLock.lock()方法后面的任务。可以使用读写锁ReentrantReadWriteLock来提升该方法的效率。读写锁即多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作。“读写”、“写读”和“写写”都是互斥的;而“读读”是异步的,非互斥的。

 

第五章,定时器timer

  1. 知识点。如何实现指定时间执行任务。如何实现按指定周期执行任务。

  2. new Timer(true);这个true表示该定时器是守护线程。

  3. 多个timer执行。TimerTask是以队列的方式一个一个被顺序执行的,所以执行的时间有可能和预期的时间不一致,因为前面的任务有可能消耗的时间较长,则后面的任务运行的时间也会被延迟。

  4. 下面的方法可以设置多久之后开始周期性无限循环。schedule(TimerTask task,Date firstTime,long period)

  5. TimerTask类中的cancel()方法的作用是将自身从任务队列中清除。就是继承于timertask的类,然后在run里面 this.cancel()等于把自身这个timer执行一次就取消了。

  6. 与上面不同的是,Timer类中的cancel()方法的作用是将任务队列中的全部任务清空。这个任务队列指的是所有的定时器。全部任务都被清除,并且进程被销毁,按钮由红色变成灰色。但是有时候又不会停止执行,因为没有争抢到queue锁

  7. schedule(TimerTask task,long delay)这个指的是多久后执行一次任务,而不是循环执行。

  8. 方法schedule和scheduleAtFixedRate主要的区别只在于不延时的情况。不延时的时候,前者下次执行时间是从上次的开始时间计算,后者是结束时间算起。

  9. 前者没有追赶特性,后者有。

 

你可能感兴趣的:(JAVA)