多线程变成核心技术笔记(一二)
Posted JathonKatu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程变成核心技术笔记(一二)相关的知识,希望对你有一定的参考价值。
Start:第一章、第二章
Page:1-132
Date:20190126
Title:多线程变成核心技术
第一章的关键点:
线程的启动
如何暂停线程
如何停止线程
线程的优先级
线程安全相关的问题
进程是受操作系统管理的基本运行单元。
线程是进城中独立运行的子任务。
多线程是异步的,被调用的时机是随机的。执行顺序与代码顺序无关。
一个进程在运行时至少会有一个线程在运行。(java中的main方法就是一个线程,由jvm创建的)。
多线程的实现方法:继承Thread;实现Runnable接口。事实上Thread也是实现了Runnable的接口。
并且Thread可以传入一个继承了Runnable的对象,调用Thread的run(直接调用或者start)时,都会调用这个对象的run。
1/继承Thread类的实现类需要重写run方法。
2/实现Runnable接口需要将实现的对象传入到Thread中(这两个方法也可以把Thread对象传入):Thread Thread(Runnable target),Thread Thread(Runnable target,String name)等等。
因为JAVA是单根继承,所以继承Thread类的局限性比较大,可以使用继承Runnable接口实现线程类(接口可以多实现)
System.out.println(i--)是线程不安全的。首先输出是一个步骤,其次i--也分为三个步骤:
首先执行取出i,计算i-1,对i赋值。
当多个线程同时访问i这个变量的时候,一定会出现非线程安全问题。
Thread currentThread()静态方法:
该方法可以返回代码段正在被那个线程调用的信息。常用:
Thread.currentThread().[getName()]|[getId()]....总而言之,就是获取当前执行线程,可以调用其所有方法
Boolean isAlive():该线程是否活跃的(为native方法,即非java实现的)
void sleep(Long millis);线程睡眠millis毫秒,仍然占据资源。(可能会抛InterruptedException)
long getId();底层的计数器是从0开始的,但访问这个计数器的时候就开始自增,所以第一个线程返回的是1,调用的是Thread类的static long nextThreadID()方法做自加;
String getName();初始化的时候是Thread-i,这个i是从0开始(第一个线程默认名为Thread-0),当然可以自己调用setName去重命名,但是计数器不会制空。
void setName();自己设置一个线程名
停止线程:
可以调用线程的interrupte()方法,打上停止标识,但并不会马上暂停。
interrupted();
isInterrupted();
两个方法都是判断线程是否是停止状态,不同的是,第一个方法调用后会重置标识位为false,而第二个方法不会
事实上两个方法底层都是调用的isInterrupted(boolean ClearInterrupted)方法,只是第一个传入的参数是true,第二个方法传入的参数是false,从参数名就可以知道两者的区别。
stop()作为一个过时的方法,会暴力的将线程停止,暴力停止的结果是可能导致线程出现ThreadDeath异常,出现数据的同步问题造成数据不一致,导致流程出错,以及对资源的清理工作不能正常完成。
可以采用:
在线程内判断isInterrupted()的返回值是否为true,如果是的话使用return;,然后在需要暂停线程的地方调用线程的interrupte方法。(循环不间断的监听方法不是很推荐使用,浪费资源)
暂停线程:
suspend与resume方法(前者为暂停,后者为继续。但作为淘汰方法是有原因的,suspend暂停线程的运作并不会释放资源,是一种占着茅坑不拉屎的方法[不是很文雅但是生动形象。])
resume则是唤醒被suspend的线程(作为配套方案一起淘汰理所应当)
这两个方法有两个缺点,一个是独占资源,一个是不同步。下面说说不同步。
很简单,比如你一个对象的原来两个成员变量为("1","11"),然后你有一个方法为调用setValue("a","aa")将原来的两个值set一下。
那么你在suspend这个Thread的时候,是有可能将值变成("a","11"),毕竟java设值是一个一个设置的。
void yield()方法:
又是一个native方法,这里看不到底层代码,只能知道这个方法是释放当前线程的资源,供大家(包括自己)进行争夺。
优先级:
int getPriority();
void setPriority(int newPriority);//设置默认值,里面会对默认值进行判断,不能大于MAX_PRIORITY(10),不能小于MIN_PRIORITY(1),超过会抛IllegalArgumentException
不设置的话,优先级默认(NORM_PRIORITY = 5)
优先级是可以继承的,例如在main函数(没有特殊设定的情况下默认值为5)中new一个Thread,那么新Thread的优先级默认值就是5
优先级并不是越高越先执行,是一个概率性事件,越高先执行完成的概率越大。
数值越小优先级越高,setPriority(1)优先级最高。
守护线程:
线程分两种,一种是用户线程,一种是守护线程,守护线程是一种特殊线程,当进程中不存在非守护线程的时候,守护线程则自动销毁。最典型的守护线程就是垃圾回收线程。
void setDaemon(boolean on)
为true则是守护线程
第二章的关键点:
synchronized对象监视器为Object的使用
synchronized对象监视器为Class时的使用
非线程安全是如何出现的
关键字volatile的主要作用
关键字volatile与synchronized的区别及使用情况
线程在多个线程同时对一个对象中的实例变量进行并发访问时发生,产生的后果就是"脏读",也就是取到的数据其实是被改动过的。
非线程安全问题在于实例变量(方法外的成员变量),如果是方法内部的私有变量就不存在非线程安全问题了。
synchronized关键字:
可以形成同步方法或者同步代码块。
普通同步方法事实上锁对象是当前对象,而静态同步方法的锁对象则是当前对象摸板类对应的class文件:
synchronized public void funtion(){}
//因为使用同步方法会导致整个方法进入同步,使效率下降,推荐使用同步代码块,只同步部分必须同步的代码,从而提高效率。
事实上相当于:
public void funtion(){
synchronized(this) {function body}
}
//这里因为synchronized放在方法体外会报错,所以放在方法发体内。
同步代码块的写法:
synchronized(lock){}//
同步的代码块不一定是整个方法体,也可以是一些零星的需要同步步骤,如果同步的事整个方法就会影响效率,推荐将需要同步,可能出现线程不安全的步骤放入即可。
这里的lock是指锁对象,可以是任意对象(例如新鲜热辣的new String("lock")),也可以是this(也就是当前对象),或者是xxx.class这个xx可以是任何一个类,这里的锁对象就是某个类的字节码文件。
如果锁对象不是同一个对象,那么调用同一方法也是异步的,例如一个类中有这么段代码:
public class a{
Object obj = new Object;
public void function{
synchronized(obj){}
}
}
现有线程Threadab的run方法是 @Override public void run (){ a.function();}
Threadab a = new Threadab(new a());
Threadab b = new Threadab(new a());
那么,这个时候这两个线程就不是同步的//例子写的简单,但真实性经过验证,只求看的时候记得知识点就ok
当用synchronized持有一个锁对象后,另一个请求同一锁对象的synchronized修饰的方法就会等待,直到锁对象被释放后重新争夺。
子类继承父类的所有方法后,如果父类中有方法是synchronized修饰的,子类并不会继承这个关键字(也就是不单独设置的话,这个方法就会变成异步方法);
当同步方法运行的时候,出现了异常,锁对象就会自动释放。
数据类型String的常量池特性:
当锁对象为字符串 且equals = true时,事实上还是同一个锁对象。
例如synchronized(ob){}
然后两个线程的run方法都给这个ob 传入一个"aa"时,其实还是同一个锁对象。
线程无限等待:
当同一对象中有两个同步方法(锁对象就是当前对象),其中一个方法陷入死循环则另一个方法永远无法获取锁对象,也就没法执行。
这时,可以使用同步代码块,设置不同的锁对象。
多线程死锁:
在jdk自带的工具,进入jdk的bin目录下运行cmd,jps就可以看到Run的id,再用jstack -1 上一步得到的id,就可以检测出死锁的线程。
多线程死锁的原因是,两个线程各自持有资源,并且请求对方资源,即造成死锁。这是程序设计的bug,在程序设计的时候要避免双方互相请求对方的锁。
只要互相等待对方释放锁就有可能出现死锁。
内置类与静态类:
内置类的对象被外置类当作锁并持有后,内置对象的同步方法无法执行。
内置静态类持有的锁如果是其它的锁,而外置方法的同步方法持有的是当前对象,则两者不会出现互相干扰的情况。
锁对象的改变:
类中有以下代码块:
private String lock = "123"
synchronized(lock){
这里面将lock改成456
}
此时,如果a线程先start,b线程sleep一段时间后start,则b线程请求的lock对象是456,两个线程是异步进行的。
volatile关键字:
由于线程读取变量是从线程的私有堆栈取,而不是从公共堆栈取,那么想通过控制一个公共变量来控制不同线程的停止似乎不是一个可以办到的事情。
而当对线程所请求的变量加上volatile关键字的时候,强制线程去公共堆栈获取变量,则可以控制线程获取的变量。
volatile只是强制对数据的读写时影响到主内存。
缺点:volatile关键字只是增加了实例变量多个线程之间的可见性,但他并不具备原子性。还是有可能出现脏读现象。
当同步方法/同步代码块获取volatile变量的时候,volatile变量也拥有了原子性。
volatile关键字主要使用在多线程可以感知是系里变量被更改了,并且可以获取最新的值时使用,也就是用多线程读取共享变量时可以获取最新值使用。
变量的工作过程:
1、read 内存读取
2、load 数据载入
3、use 数据使用
4、assign 数据赋值
5、store 数据存储
6、write 回写内存
这里2、3、4步是非原子性的。
原子类:
源自类型Atomicxxxxx(Atomic就是原子的意思)
例如AtomicInteger count = new AtomicInteger(0);//初始值为0
count.incrementAndGet();//这里就是相当于把0自增然后获取,底层代码调用的是return getAndAdd(先取值,并且取到值后吧内存中这个值+1) + 1
事实上getAndAddInt(this, valueOffset, x) 是将x 与 valueoffset做一系列操作(native代码,看不到源码),然后确认数据后返回valueoffset,且将主内存中的valueoffset 设置成 valueoffset + x
但是,原子类的方法虽然是原子的,但是方法和方法之间却不是原子的,因此,还是需要对执行原子操作的方法进行同步(写进同步代码块)
synchronized具有volatie的同步功能,可以使多个线程访问同一资源具有同步性,而且它还具有将线程工作内存中的私有变量与公共内存中的变量同步的功能
synchronized可以保证同一时刻,只有一个线程可以执行某一个方法或者代码块。
它还包含两个特征:互斥行和可见性。
学习多线程并发,要注重互斥和课件,这些是多线程的精髓
以上是关于多线程变成核心技术笔记(一二)的主要内容,如果未能解决你的问题,请参考以下文章