第一章 Java多线程技能
1、实现多线程的方式:一种是继承Thread类,另外一种是实现Runnable接口。
这两者的区别是:继承Thread类的本质是多个线程分别完成自己的任务,实现Runnable接口的本质是多个线程共同完成一个任务。
举例如下:
继承Thread类的,我们相当于拿出三件事即三个卖票10张的任务分别分给三个窗口,他们各做各的事各卖各的票各完成各的任务,因为MyThread继承Thread类,所以在new MyThread的时候在创建三个对象的同时创建了三个线程;
实现Runnable的, 相当于是拿出一个卖票10张得任务给三个人去共同完成,new MyThread相当于创建一个任务,然后实例化三个Thread,创建三个线程即安排三个窗口去执行。
此外,第三种方式是实现Callable接口。
2、对于多线程中操作共享数据的问题,比如经典的生产者消费者问题中对共享数据的操作,比如商品售卖过程中对于共享数据的操作问题,都必须采用synchronized关键字的同步方法。
3、终止正在执行的线程有三种方法:
①当run方法完成后线程终止,正常退出
②使用过期的stop方法,不过是过期的,而且是unsafe的
③使用interrupt方法。(仅仅是在当前线程中打一个停止的标记,并不是真正的停止线程)
4、this.interrupted( ) 和 this.isinterrupted( )的区别:
this.interrupted( ) 测试当前线程是否已经是中断状态,执行后具有将状态标志清除为false的功能,底层是static的,正常的重写run方法里面调用该方法,thread.interrupted( )会返回false,因为当前的线程是main线程,想清楚执行任务的线程是thread.currentThread.interrupted( )
this.isInterrupted( ) 测试线程Thread对象是否已经是中断状态,但是不清楚状态标志,底层不是static的。
5、如果想要线程在中断之后仍然执行后面的代码,那么就try catch一下,将想要执行的代码放在catch语句中,这样线程中断后就会进入catch语句中,执行里面的代码。
6、线程sleep的时间过长,如果再线程sleep中中断线程,那么也是会跳到catch语句中,但是需要注意的是,这时会清空停止标志位,即在catch里面执行this.isInterrupted( ) 方法会返回false,意思是线程没有被终止。
7、在Java中线程分成两种,一种是用户线程,另一种是守护线程,典型的守护线程有垃圾回收线程、内存管理线程等等。
将普通线程设置为守护线程的方法是调用 thread.setDaemon(true).
8、thread.start( )和thread.run( )的区别:
start( )方法通知“线程规划器”,此线程已经准备就绪,等待调用线程对象的run( )方法,具有异步执行的效果,start方法的顺序并不是代表线程启动的顺序。
run( )方法不是异步,是同步,此线程对象并不会交给“线程规划器”来进行处理,而是由主线程来直接调用run( )方法,也就是必须等待run方法执行完毕后才能执行后面的代码。
调用start方法,其构造方法的线程是main线程,但是重写的run方法的线程是Thread-0;
调用run方法,其构造方法的线程是main线程,重写的run方法的线程也main线程。
第二章 对象及变量的并发访问
9、synchronized取得的锁是对象锁,而不是把一段代码或者方法当做锁,它是多个对象多个锁。
10、A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法。
A线程先持有object对象的Lock锁,B线程如果这时调用object对象中的synchronized类型的方法则需等待,就是同步。
11、线程的优先级可以继承,但是同步不可以继承。
12、关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。也就是说在一个synchronized方法/块 的内部调用奔雷的其他synchronized 方法/块 时,是永远可以得到锁的。
可重入锁支持在父子类继承的环境中。当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法。
13、synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个长时间的任务,那么线程B则必须等待比较长的时间,这种情况下,使用synchronized 同步语句块来解决。不在synchronized块中的就是异步执行,在synchronized块中的就是同步执行。
14、当一个线程访问object的一个synchronized(this) 同步代码块时,其他线程对同一个object中所有其他 synchronized(this)同步代码块的访问将被阻塞,这说明synchronized使用的“对象监视器”是一个。
15、在Java中支持对“任意对象”作为“对象监视器”来实现同步的功能,这个“任意对象”大多数是实例变量及方法的参数。在多个线程持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块。
16、使用synchronized(非this对象x)同步代码块 格式进行同步操作时,对象监视器必须是同一个对象。如果不是同一个对象监视器,运行的结果就是异步调用了,就会交叉运行。
17、锁非this对象具有一定的优点:如果在一个类中有很多个synchronized方法,这时虽然能实现同步,但是会受到阻塞,所以影响运行效率;但是如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,因此可以提高运行效率。
18、“synchronized(非this对象x)”格式的写法是将x对象本身作为“对象监视器”,这样就可以得到三个结论。
①当多个线程同时执行synchronized(x){ } 同步代码块时呈现同步效果。
②当其他线程执行x 对象中 synchronized 同步方法时呈现同步效果。
③当其他线程执行x 对象方法里面的 synchronized(this)代码块时也呈现同步效果。
19、关键字synchronized还可以应用在static静态方法上,如果这样写,那么对当前的 *.java文件对应的 Class类进行持锁。
synchronized关键字加到static静态方法上是给Class 类上锁,而synchronized 关键字加上非static 静态方法上是给对象加锁。
同步synchronized(*.class)代码块的作用和 synchronized static方法的作用一样。
20、在JVM中,具有String常量池缓存的功能,因此如果是 synchronized(String) 就会持有的同一个锁,如果是多个线程共同访问,就会导致其他线程不能访问的情况,造成死锁,解决的方法是 new Object() 实例化一个Object对象,这样就不会放入到缓存之中。
21、 同步方法与同步代码块的区别:同步方法可能会造成死循环,而同步代码块就会解决此问题。
示例程序: 一个同步方法是 while true,就不停的输出,另一个方法就一个简单的输出。使用同步方法就会造成死锁,使用同步代码块 就不会出现死锁的问题。
22、监测死锁:可以使用JDK 自带的工具来监测是否有死锁的现象,首先进入 CMD,再进入JDK的安装文件夹中的bin 目录,执行jps 命令,如下图
![](https://i-blog.csdnimg.cn/blog_migrate/d76fa9a7415e92e76f9b4ca53978775b.png)
得到运行的线程Run的id 值是3244,再执行jstack 命令,查看结果,如下图
![](https://i-blog.csdnimg.cn/blog_migrate/5f0a4b10aa603edcd37fa093a518df12.png)
监测到有死锁的现象。
![](https://i-blog.csdnimg.cn/blog_migrate/c1ce553db0d6080951750484535b048a.png)
23、只要对象不变,即使对象的属性被改变,运行的结果还是同步。
24、关键字 volatile 的主要作用是使变量在多个线程间可见,它是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。
比如 标志位初始化为true,存在于公共堆栈和线程的私有栈中,当JVM被设置为-server 模式时为了提高线程的运行的效率,线程一直在私有堆栈中取得 标志位的值为true,对标志位更新为false的语句虽然被执行,但是更新的是 公共堆栈中的标志位,因此就有可能造成死循环,解决的方法是在标志位前面加上 volatile关键字,这样当线程访问标志位时,强制从公共堆栈中进行取值。
![](https://i-blog.csdnimg.cn/blog_migrate/d2c28217b9b733165c3c8238b6d6482d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/218cf0c411ed989dc463f872b04f935a.png)
25、volatile的致命缺点是 不支持原子性。
26、关键字synchronized和 volatile的比较:
![](https://i-blog.csdnimg.cn/blog_migrate/d2abf486c438818e58e0e301447f9825.png)
27、在 private static void addCount( )前加入synchronized 关键字,就可以实现线程安全的效果,注意一定要添加static关键字,这样synchronized与 static 锁的内容就是 *.class 了。
28、i++ 实际分三步,一是从内存中取出 i 的值,二是计算 i 的值,三是将 i 的值写到内存中,为了避免脏读,使用synchronized 关键字。
29、关键字volatile出现非线程安全的原因:
![](https://i-blog.csdnimg.cn/blog_migrate/123d3e3aa1017b2d7dfb1f292ddcf4ec.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3e5cad2a25755a5c2b441b2caa94477b.png)
30、对于 i++的操作,除了在使用时使用synchronized 关键字实现同步以外,还可以使用 AtomicInteger原子类进行实现,但是使用原子类实现的时候也不是完全是线程安全的,因为可能原子类是安全的,但是方法与方法之间的调用是 不安全的,因此解决的办法是 相关调用的方法的前面加上 synchronized 关键字。
![](https://i-blog.csdnimg.cn/blog_migrate/2809bb4ea3dcd60d65446529169a256d.png)
31、关键字synchronized可以保证多个线程访问同一个资源具有同步性,而且它还具有将线程工作内存中的私有变量与公共内存中的变量进行同步的功能。
关键字synchronized 可以保证在同一个时刻,只有一个线程可以执行一个方法或者某一个代码块。它包含两个特征:互斥性和可见性。同步synchronized 不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护之前所有的修改效果。
参考资料:《Java多线程编程核心技术》