读书笔记:java多线程编程核心技术

读书笔记,简单记录....(都是从我的有道云笔记直接复制的,没有进行发表修改, 读者见谅!)

 

第一章

掌握线程的启动 暂停 停止 优先级 安全性等

1.1进程 与 线程

进程 操作系统结构的基础,是一次程序的执行,是系统进行资源分配和调度的独立单位,简单理解:一个运行的exe程序就是一个进程

线程 可理解成进程中独立运行的子任务。 使用多线程可以在同一时间内运行更多不同类型的任务

单任务的特点就是排队执行,即同步。使用多线程就是在使用异步,多线程是异步的。

1.2 多线程实现

1.继承Thread类

缺点:无法多继承

public class Mythread extends Thread{

@override

public void run(){

super.run();

syso("mythread---");

main(){

Mythread s = new Mythread();

s.start(); //启动线程

syso("-----main完")

}

2.实现Runnable接口

其实Thread也继承了Runnable接口,他们之间有多态的关系

public class Myrun implements Runnable{

@override

public void run(){

syso("Myrun ---");

main(){

Myrun my = new Myrun ();

Thread s = new Thread(my);

s.start(); //启动线程

syso("-----main完")

}

 

启动线程:

调用start()方法,异步执行,使用多线程,代码执行结果与代码调用顺序是无关的

若是直接调用run()方法,就不是异步执行,与调用普通方法无异。

实例变量与安全性

自定义线程类中的实例变量针对其他线程可以有共享与不共享之分,

1.不共享数据的情况

2.共享数据的情况

在某些jvm中,i-- 的操作要分成如下3步:

l )取得原有 i 值。

2 )计算 i-1。

3.对 i 赋值

在这3个步骤中, 如果有多个线程同时访问, 那么一定会出现非线程安全问题。

解决办法就是 同步 synchronized 使多个线程在执行run方法时, 以排队的方式进行处理。当一个线程调用run前, 先判断m方法有没有被上锁, 如果上锁, 说明有其他线程正在调用m方法, 必须等其他线程对run方法调用结束后才可以执行 run方法。这个线程就会不断尝试拿这把锁, 直到能够拿到为止, 而且是有多个线程 同时去争抢这把锁

synchronized可以在任意对象及方法上加锁, 而加锁的这段代码称为 “互斥区” 或 “临界区”。

非线程安全主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、 值不同步的情况, 进而影响程序的执行流程

println()方法在内部是同步的, i-- 的操作却是在进入print In()之前发生的,应该加同步

 

1.3一些方法

currentThread()方法

可返回代码段正在被哪个钱程调用的信息

isAlive()方法

判断当前的线程是否处于活动状态。活动状态就是线程已经启动且尚未终止。但此值是不准确的。

sleep()方法

作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。“正在执行的线程”是指this.currentThread()返回的线程。

getld()方法

取得线程的唯一标识。

1.4停止线程

停止一个线程可以使用Thread.stop()方法,但最好不用它。虽然它确实可以停止一个正在运行的线程,但是这个方法是不安全的(unsafe),而且是已被弃用作废的(deprecated),在将来的Java版本中,这个方法将不可用或不被支持。

大多数停止一个线程的操作使用Thread.interrupt()方法,尽管方法的名称是“停止,中的意思,但这个方法不会终止一个正在运行的线程,还需要加入一个判断才-可以完成线

 

在Java中有以下3种方法可以终止正在运行的线程:

l )使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。

2)使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用它们可能产生不可预料的结果

3)使用interrupt方法中断线程。

 

stop()会强制停止,使一些清理性工作无法完成。会释放锁,可能造成数据不同步。调用stop会抛出java.lang.ThreadDeath异常,通常不捕获。

intenrupt()方法仅仅是在当前线程中打了一个停 止的标记,并不是真的停止线程。

 

判断线程是否是停止状态

l ) this.interrupted(): 测试当前线程是否已经中断。具有清理状态的功能,连续2次调用

2 ) this. islnterrupted():该方法不是static的 测试线程是否已经中断,不清除状态标志

 

1.抛异常方式停止

先判断线程是否停止,根据判断条件来停止线程,,,出现异常时,锁会自动释放。

2.使用return停止线程

 

停止睡眠的线程

try catch 中,如果在sleep状态下停止某一线程,会进入catch语句,并且清除 停止状态值.使之变成false。java. lang. IncerrupdExcepcion:’sleep interrnpced

暂停 / 恢复线程

suspend()方法暂 停线程,过期方法

resume()方法恢复线程的执行。过期方法

缺陷:独占,不同步

1.如果使用不当,极易造成公共的同步对象的独占,使其他线程无法访问公共同步对象。如:当程序运行到println()方法内部停止时,同步锁未被释放,这导致当前PrintStrearn对象的println()方法一直呈“暂停”状态,并且“锁未释放

2.

yield方法

放弃当前的 CPU 资源. 将它让给其他的任务去占用 CPU 执行时间。 但放弃的时间不确定, 有可能刚刚放弃, 马上又获得 CPU 时间片。

1.5线程优先级

线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。设置线程优先级有助于帮“线程规划器”确定在下一次选择哪一个线程来优先执行。

设置线程的优先级

setPriority()方法,此方法在JDK的源代码如下:

优先级的继承特性

线程的优先级具有继承性,比如A线程启动B线程则B线程的优先级与A是一样的。

优先级具有规则性

高优先级的线程总是大部分先执行完, 但不代表高优先级的线程全部先执行完,谁先执行完和代码的调用顺序无关

优先级具有随机性

优先级较高的线程不一定每一次都先执行完。

1.6守护线程

Java线程中有两种线程,一种是用户线程,另一种是守护线程。

守护线程是一种特殊的线程,它的特性有“陪伴”的含义,当进程中不存在非守护线程了,则守护线程自动销毁。

典型的守护线程就是垃圾回收线程,当进程中没有非守护线程了,则垃圾回收线程也就没有存在的必要了,自动销毁。

 

 

 

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

“非线程安全” 其实会在多个线程对同一个对象中的实例变量进行并发访问时发生, 产生的后果就是 “脏读”,也就是取到的数据其实是被更改过的。

方法内的变量是线程安全的

非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则永远是“线程安全”的。

1.synchronized

用线程访问的对象中如果有多个实例变量, 则运行的结果有可能出现交叉的情况,方法前加同步即可。synchronized可以使多个线程访问同一个资源具有同步性, 而且它还具有将线程工作内存中的私有变量与公共内存中的变量同步的功能,即具有volatile同步的功能。

它包含两个特征:互斥性和可见性

在两个线程访问同一个对象中的同步方法时一定是线程安全的

1.synchronized同步方法

1.多个对象多个锁

两个线程分别访问同一个类两个不同实例相同名称的同步方法,效果是以异步的方式运行的,

关键字synchronized取得的锁都是对象锁 ,因为new一个实例产生了一个锁。锁不统一

一些结论

1.线程A先持有了object对象的锁,但线程B完全可以异步调用非synchronized类型的方法。

2. A线程先持有o对象的Lock锁,B线程如果在这时调用o对象中的synchronized 类型的方法则需等待,也就是同步。

脏读

多个线程调用同一个方法时, 为了避免数据出现交叉的情况,使用synchronized关键字来进行同步。

问题:get 和set 其中一个加同步时,如在赋值时进行了同步, 但在取值时有可能出现一些意想不到的意外, 这种情况就是脏读( dirty Read)。 发生脏读的情况是在读取实例变量时, 此值已经被其他线程更改过了。原因是get方法没有同步

解决方法:set get都加同步

锁重入

synchronized拥有锁重人的功能,也就是在使用synchronized时,当一个线程 得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。这也证明在一个 Synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。

“可重人锁” 的概念是:自己可以再次获取自己的内部锁。如有l条线程获得了某个对象的锁, 此时这个对象锁还没有释放, 当其再次想要获取这个对象的锁的时候还是可以获取的, 如果不可锁重人的话, 就会造成死锁。

存在继承时:当存在父子类继承关系时,子类是完全可以通过可重人锁调用父类的同步方法的。

 

同步不具有继承性

同步不能继承,父类方法加同步时,若调用子类方法还得在子类的方法中添加synchronized关键,才能确保安全。

 

2. synchronized同步语句块

synchronized声明方法在某些情况下是有弊端的,若执行时间过长,会造成长时间等待。可以使用 synchronized同步语句块来解决。

synchronized(this)代码块是锁定当前对象

结论:

1.两个并发线程访问同一对象中的synchronized(this)同步代码块时,一段时间内只能有一个线程被执行,另一线程必须等待当前线程执行完代码块以后才能执行该代码块。

2.当一个线程访问object的一 个synchronized同步代 码块时, 另一线程仍可以访问该object对象中的synchronized(this)同步代码块。

3.当一个线程访问object的一个 synchronjzed(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同 步代码块的访问将被阻塞,这说明synchronized使用的“对象监视器”是一个。

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

 

将任意对象作为对象监视器

多个线程调用同一个对象中的不同名称的 synchronized同步方法或 synchronized(this)同 步代码块时, 调用 的效果就是按顺序执行, 也就是同步的, 阻塞的。

这说明 synchronized同步方法或 synchronized(th.is)同步代码块分别有两种作用。

( I ) synchronized同步方法

l )对其他 synchronized同步方法或 synchronized(this)同步代码块调用呈阻塞状态。

2 )同一时间只有一个线程可以执行 synchronized同步方法中的代码。

( 2 ) synchronized(由is)同步代码块

I )对其他 synchronized同步方法或 synchronized(this)同步代码块调用呈阻塞状态。

2 )同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码。

 

使用 synchronized(th.is)格式来同步代码块, 其实Java 还支持对 “任意对象” 作为 对象监视器 来实现同步的功能。 这个 任意对象” 大多数是实例变量及方法的参数, 使用格式为 synchronized(非 this对象)。

根据前面对 synchronized(this)同步代码块的作用总结可知,

synchronized(非 this对象)格式的作用只有 l 种: synchronized(非 this对象)同步代码块

 

非this对象锁具有一定的优点:如果在一个类中有很多个synchronized方法,虽然能实现同步,但会受到阻塞,所以影响运行效率;

但如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,则可大大提高运行效率。

 

对象监视器不同,运行结果也是异步的。

“synchronized(非this对象x)” 格式的写法是将 x对象本身作为 “对象监视器” ,这样就可以得出以下 3 个结论:

1 )当多个线程同时执行synchronized(x) {}同步代码块时呈同步效果

2) 当其他线程执行x对象中synchronized同步方法时呈同步效果。

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

但需要注意:如果其他线程调用不加synchronized关键字的方法时, 还是异步调用

 

3.静态同步synchronized方法与synchronized(xxx.class)代码块

synchronized应用在static静态方法上,这样写是对当前的*.java文件对应的Class类进行持锁,Class锁可以对类的所有对象实例起作用。同步synchronized(xx.class)代码块的作用其实和synchronized static方法的作用一样。

synchronized关键字加到非static静态方法上是给对象上锁。

 

4.String类型数据的常量池特性

在 JVM 中具有 String 常量池缓存的功能,将 synchronized(string)同步块与 String 联合使用时, 要注意常量池以带来的一些例外。如取到的锁相同造成一个线程不能执行。

大多数的情况下, 同步synchronized 代码块都不使用 String 作为锁对象, 而改用其他, 比如 new Object()实例化Object 对象, 但它并不放入缓存中。

 

5.死循环

同步方法容易造成死循环

如下图:线程B永远得不到运行的机会,锁死了。

解决方法: 可以使用同步块来解决这样的问题。。下下图

 

6.死锁

不同的线程都在等待根本不可能被释放的锁 从而导致所有的任务都无法继续完成,造成线程的 “假死” 。在设计程序时就要避免双方互相持有对方的锁的情况。

最经典的嵌套造成死锁

使用JDK自带的工具来监测是否有死锁的现象。 首先进入CMD工具, 再进入JDK的安装文件夹中的bin目录,执行jps命令,得到运行的线程Run的id值是3244。 再执行jstack命令, 查看结果。

7.内置类与静态内置类

1.在内置类中有两个同步方法,但使用的却是不同的锁,打印的结果 也是异步的。

2.同步代码块synchronized(class2)对class2上锁后,其他线程只能以同步的方式调用class2中的静态同步方法

 

8.锁对象的改变

只要对象不变,既使对象的属性被改变,运行的结果还是同步

如 果同时持有相同的锁对象, 则这些线程之间就是同步的;如果分别获得锁对象, 这些线程之 间就是异步的。

 

2.volatile关键字

volatile的主要作用是使变量在多个线程间可见。强制的从公共内存中读取变量 的值

2.1关键字volatile与死循环

停不下来的原因主要就是main线程 一直在处理while()循环,条件一直成立.导致程序不能继续执行后面的代码。解决的办法当然是用多线程技术。(让另一个线程改变条件)


 

是什么原因造成将jvm设置为-server时就出现死循环呢?

在启动RunThread.java线程时, 变量private boolean isRunning = true;存在于公共堆栈及线程 的私有堆栈中。 在jvm被设置为-server模式时,为了线程运行的效率, 线程一直在私有堆栈 中取得isRunning的值是 true。 而代码 thread.setRunning( false);,虽然被执行, 更新的却是公共堆栈中的isRunning变量值false, 所以一 直就是死循环的状态。

问题其实就是私有堆战中的值和公共堆桔中的值不同步造成的,解决这样的问题就要使用volatile关键字了,它主要的作用就是当线程访问isRunning这个变量时,强制性从公 共堆战中进行取值。

2.2 synchronized与volatile进行一下比较:

volatile关键字特点 增加了实例变量在多个线程之间的可见性。 但最致命 的缺点是不支持原子性即不具备同步性,

l )关键字volatile是线程同步的轻量级实现, 所以volatile性能肯定比synchronized要好。并且volatile只能修饰于变量,而synchronized可以修饰方法, 以及代码块。 随着JDK新版本的发布,synchronized关键字在执行效率上得到很大提升, 在开发中使用synchronized 关键字的比率还是比较大的。

2) 多钱程访问volatile不会发生阻塞,而synchronized会出现阻塞。

3) volatile能保证数据的可见性, 但不能保证原子性;

synchronized可以保证原子性,也可以间接保证可见性, 因为它会将私有内存和公共内存中的数据做同步。使用synchronized同步关键字也就没有必要再使用volatile关键字来声明变量了。

4.关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性

 

但在这里需要注意 的是: 如果修改实例变量中的数据, 比如i++, 也就是 i=i十l, 则这样的操作其实’并不是原子操作, 也就是非线程安全的。 表达式i++的操作步骤分解如下:

l )从内存中取出 i 的值

2)计算 i的值;

3) 将 i 的值写到内存中

假如在第2步计算值的时候, 另外一个线程也修改 i的值, 那么这个时候就会出现脏数据。解决的办法其实就是使用 synchronized关键字

 

变量在内存中的工作过程

 

在多线程环境中,use和assign是多次出现的,但这一操作并不是原子性,也就是在 read和load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,也就是私有内存和公共内存中的变量不同步,也就出现了非线程安全问题。

2.3 使用原子类进行i++操作

除了在i++操作时使用synchronized关键字实现同步外,还可以使用Atomiclnteger原子

进行实现。原子操作是不能分割的整体,没有其他线程能够中断或检查正在原子操作中的变量。一个原子(atomic)类型就是一个原子操作可用的类型,它可以在没有锁的情况下做到线程安全。

但也不完全安全。原子类在具有有逻辑性的情况下输出结果也具有随机性。

 

addAndGet()方法是原子的,但方法和方法之间的调用却不是原子的。解决这样的问题必须要用同步。

 

 

第三章 线程间通信-----------------

待更..........

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值