学习——Java多线程编程(简要概括)

1.多线程技能

1.什么是线程?在进程中独立运行的子任务。
2.多线程优点?可以大幅利用CPU的空闲时间来处理其他任务。
3.什么场景使用多线程? 1.阻塞 2.依赖。
注:
1.多线程是异步的。线程被调用时机是随机的。
2.多次调用start()方法,则出现异常Exception in thread"main"java.lang.IllegalThreadStateException.

1.使用多线程

1.继承Thread类
2.实现Runnable接口


start()方法耗时的原因?
1)通过JVM告诉操作系统创建Thread.
2) 操作系统开辟内存并使用Windows SDK中的create Thread()函数创建Thread线程对象。
3)操作系统对Thread对象进行调度,以确定执行时机。
4)Thread在操作系统中被成功执行。


使用Runnable接口实现多线程的优点?
Java并不支持多继承的写法。而Runnable接口可以间接的实现”多继承“的效果。

2.常见命令分析线程的信息

1.使用jps+jstack.exe命令
2.使用jmc.exe命令
3.使用jvisualvm.exe命令

3.线程随机性的展现

线程随机输出的原因是CPU将时间片分给不同的线程,线程获得时间片后就执行任务,所以这些线程在交替地执行并输出,导致输出结果呈现乱序的效果。

时间片即CPU分配给各个线程得时间。

每个线程被分配一个时间片,在当前的时间片内CPU去执行线程中的任务。

4.执行start()的顺序不代表执行run()顺序

5.实现Runnable接口与继承Thread类的内部流程

Runnable run=new Runnable();
Thread t=new Thread(run);
t.start();

JVM调用Thread类下的run()方法源代码

@Override
public void run(){
	if(target!=null){
		target.run();
	}
}

而Thread(run)带参数的底层源码

private void init(ThreadGroup g,Runnable target,String name,long stackSize,AccessControlContext acc,boolean inheritThreadLocals){
	...
	this.targrt=target;
	...
}

init()方法在Thread.java构造方法中被调用的源码

public Thread(Runnable target){
	init(null,target,"Thread-"+nextThreadNum,0);
}

6.解决的问题

1.实例变量共享造成的非线性安全问题与解决方案

1.不共享数据时,并不存在多个线程访问同一个实例变量的情况
2.共享数据的情况时,需要在重写的run()方法前添加synchronized关键字。

2. Servlet技术造成的非线程安全问题的解决方案

在doPost()方法前添加synchronized关键字。

3.留意i–与System.out.println()出现的非线程安全问题。

 System.out.println(i--);
 i--优先于println()方法。
 println()底层源码
 public void println(String x){
	synchronized(this){
		print(x);
		newLine();
	}
}

7.常用的方法

1.currentThread():方法可返回代码段正在被哪个线程调用。
run():立即执行,不启动新的线程。
start():执行run()方法时机不确定,启动新的线程。
2.isAlive()方法的功能是判断当前的线程是否存活。
3.sleep(long millis):方法的作用是在指定的时间(好眠)内让当前”正在执行的线程“休眠,指代的是this.currentThread()返回的线程。
4.sleep(long millis,int nanos):方法的作用是在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠。
5.StackTreaceElement[] getStackTrace():方法的作用是返回一个表示该线程堆栈跟踪元素数组。则其第一个元素代表堆栈顶,它是该数组中最新的方法调用。最后一个元素代表堆栈底,是该数组中最旧的方法调用。
6.static void dumpStack()方法的作用是将当前线程的堆栈跟踪信息输出至标准错误流。
7.static Map<Thread,StackTraceElement[]>getAllStackTraces():方法的作用是返回所有活动线程的堆栈跟踪的一个映射。映射键是线程,而每个映射值都是一个StackTraceElement数组,该数组表示相应Thread的堆栈转存。
8.getId()方法用于取得线程的唯一标识。

8.停止线程

1.三种停止线程的方法

1)使用退出标志使线程正常退出
2)使用stop()方法强行终止线程,但是这个方法不推荐使用,因为stop()和suspend(),resume()一样,都是作废过期的方法,使用它们可能发生不可预料的结果。
3)使用interrupt()方法中中断线程。

2.停不了的线程

interrupt()方法不会马上停止,而是作为一个停止的标记,并不是真正停止线程。

3.判断线程是否为停止状态

判断停止线程的两个Thread.java方法
1)public static boolean interrupted():测试currentThread()当前线程是否已经中断。(具有清楚状态标志位false的功能)
2)public boolean this.isInterrupted():测试this关键字所在类的对象是否已经中断。(不清楚状态标志)

4.能停止的程序——异常法

5.在sleep状态下停止线程

interrupt()和sleep()方法混用会报异常?
1.在sleep状态执行interrupt()方法会出现异常。
2.调用interrupt()方法给线程打了中断的标记,在执行sleep()方法也会出现异常。

6.用stop()方法暴力停止进程

缺点:不能确定在哪里被停止的,造成数据增加得不完整。

使用stop()方法时会抛出java.lang.ThreadDeath异常,但通常情况下,此异常不需要显示地捕捉。

stop()对锁定的对象进行”解锁“,会导致数据得不到同步的处理,进而出现数据不一致的问题。

6.使用"return;"语句停止线程的缺点与解决方案

将interrupt()方法与”return;“结合使用也能实现停止线程的效果。

9.暂停线程

暂停线程意味着此线程还可以恢复运行,在Java多线程中,可以使用suspend()方法暂停线程,使用resume()方法来恢复线程的执行。

1.suspend()方法与resume()方法的使用

2.suspend()方法与resume()方法的缺点—独占,数据不完整

在使用suspend()与resume()方法时也容易出现线程暂停,进而导致数据不完整的情况。

10.yield()方法

yield()方法的作用是放弃当前的CPU资源,让其他任务去占用CPU执行时间,放弃的时间不确定,有可能刚刚放弃,马上又获得CPU时间片。

11.线程的优先级

在操作系统中,线程可以划分优先级,优先级较高的线程得到CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务,其实就是让高优先级的线程获得更多的CPU时间片。

设置线程优先级有助于”线程规划器“确定在下一次选择哪一个线程来优先执行。

Java中,优先级分为1·10共10个等级,如果优先级小于1或大于10,则JDK抛出异常throw new IllegalArgumentException().

1.线程优先级的继承特性

A线程启动B线程,则B线程的优先级与A线程是一样的。

2.优先级的规律性

使用setPriority()方法可以设置线程的优先级。
高优先级的线程总是大部分先执行完,但不代表高优先级的线程全部先执行完。
当线程优先级的等级差距很大时,谁先执行完和代码的调用顺序无关。

3.优先级的随机性

4.优先级对线程运行速度的影响

优先级高的运行的快

12.守护线程

Java中有两种线程:一种是用户线程,也称非守护线程;另一种是守护线程。


什么是守护线程?
守护线程是一种特殊的线程,当程序中不存在非守护线程了,则守护线程自动销毁。经典的守护线程是垃圾回收线程,当进程中没有非守护线程了,则垃圾回收线程也就没有存在的必要了,自动销毁。

守护Daemon线程的作用是为其他线程的运行提供服务。

2.对象及变量的并发访问

1.synchronized同步方法

synchronized可用来保证原子性,可见性,有序性。

1.方法内的变量为线性安全。

2.实例变量非线程安全问题与解决方案

问题:两个线程同时访问同一个业务对象中的一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量,则有可能出现非线程安全问题。需要在该方法前加关键字synchronized.

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

3.同步synchronized在字节码指令中的原理

在方法中使用synchronized关键字实现同步的原因是使用了flag标记ACC_SYNCHRONIZED,当调用方法时,调用指令会检查方法的ACC_SYNCHRONIZED访问标志是否设置,如果设置了,执行线程先持有同步锁,然后执行方法,最后在完成释放锁。

字节码中使用monitorenter和monitorexit指令进行同步处理。
同步:按顺序执行A和B这两个业务,就是同步。
异步:执行A业务的时候,B业务也在同时执行,就是异步。

4.多个对象多个锁

创建两个业务对象,在系统中产生两个锁,线程和业务对象属于一对一的关系,每个线程执行自己所属业务对象中的同步方法,不存在争抢关系,所以运行结果是异步的。可以不需要synchronized。
只有多个线程执行相同的业务对象中的同步方法时,逻辑和业务对象属于多对一的关系,使用synchronized.

5.将synchronized方法与对象作为锁

调用用关键字synchronized声明的方法一定是排队进行运行的,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么就没有同步的必要了。

结论:
1)A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法。
2)A线程先持有object对象的Lock锁,B线程如果在这是调用object对象中的synchronized类型的方法,则需要等待,也就是同步。
3)在方法声明处添加synchronized并不是锁方法,而是锁当前类的对象。
4)在Java中只有“将对象作为锁”这种说法,并没有“锁方法”这种说法。
5)在Java语言中,“锁”就是“对象“,“对象”可以映射成”锁“,哪个线程拿到这把锁,哪个线程就可以执行这个对象中的synchronized同步方法。
6)如果在X对象中使用了synchronized关键字声明非静态方法,则X对象就被当成锁。

6.脏读

脏读的原因:在读取实例变量时,此值已经被其他线程更改过了。

7.synchronized锁重入

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

"可重入锁"是指自己可以再次获取自己的内部锁。

8.所重入支持继承的环境

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

9.出现异常,锁自动释放

当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
除Thread.java中的suspend()方法和sleep(millis)方法被调用后并不释放锁。

10.重写方法不使用synchronized

重写方法如果不使用synchronized关键字,即是非同步方法,使用后变成同步方法。

11.public static boolean holdLock(Object obj)方法的使用

作用是当currentThread在指定的对象上保持锁定时,才返回true.

2.synchronized同步语句块

用关键字synchronized生命的方法在某些情况下是具有弊端的,例如:A线程调用同步方法执行一个长时间的任务,那么B线程等待的时间就比较长,这种情况可以使用synchronized同步语句块来解决,以提高运行效率。

synchronized方法是将当前对象作为锁,而synchronized代码块是将任意对象作为锁。可以将锁看成一个标识,那个程序持有这个标识,就可以执行同步方法。

1.synchronized方法的弊端

会加大执行时间。
解决:同步代码块

2.synchronized同步代码块的使用

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

3.用同步代码块解决同步方法的弊端

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

4.一半异步,一半同步

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

5.synchronized代码块间的同步性

在使用同步synchronized(this)代码块时需要注意,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同步代码块的访问将被阻塞,这说明synchronized使用的对象监视器是同一个,即使用的锁是同一个。

6.println()方法也是同步的。

7.验证同步synchronized(this)代码块是锁定当前对象的

8.将任意对象作为锁

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

synchronized同步方法或synchronized(this)同步代码块分别有两种作用
synchronized同步方法的作用:
1)对其他synchronized同步方法或synchronized(this)同步代码块调用呈同步效果。
2)同一时间只有一个线程可以执行synchronized同步方法中的代码。
synchronized(this)同步代码块的作用:
1)对其他synchronized同步方法或synchronized(this)同步代码块调用呈同步效果。
2)同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码。

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

synchronized(非this对象x)同步代码块的作用:当多个线程争抢相同的”非this对象x“的锁时,同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块中的代码。

锁非this对象具有一定的优点:如果一个类中有很多个synchronized方法,则这是虽然能实现同步,但影响运行效率。如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,因为有两把锁,不与其他锁this同步方法争抢this锁,可大大提高运行效率。

9.多个锁就是异步执行

10.方法调用时随机的

同步代码块放在非同步synchronized方法中进行声明,并不能保证调用方法的线程执行同步(顺序性),也就是线程调用方法的顺序是无序的,虽然在同步块中执行的顺序是同步的。

11.不同步导致的逻辑错误及其解决方法

如果方法不被同步化,则会出现逻辑上的错误。需要进行同步处理。

12.细化验证3个结论

synchronized(非this对象x)格式的写法是将x对象本身作为”对象监视器“这样就可以分析出3个结论:
1)当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。
2)当其他线程执行x对象中synchronized同步方法时呈同步效果
3)当其他线程执行x对象方法里面的synchronized(this)代码块时呈现同步效果。

13.类Class的单例性

每一个*java文件对应的Class类的实例都是一个,在内存中是单例的。

Class类用于描述类的基本信息,包括有多少个字段,有多少个构造方法,有多少个普通方法等,为了减少对内存的高占用率,在内存中只需要存在一份Class类对象就可以了,所以被设计成是单例的。

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

关键字synchronized还可以应用在static静态方法上,如果这样写,那是对当前的*java文件对应的Class类对象进行持锁,Class类的对象是单例的,更具体地说,在静态static方法上使用synchronized关键字声明同步方法时,使用当前静态方法所在类对应Class类的单例对象作为锁。

synchronized关键字加到static静态方法上的方式是将Class类对象作为锁,而synchronized关键字加到非static静态方法上的方式是将方法所在类的对象作为锁。二者的结果是一样的。

15.同步syn static方法可以对类的所有对象实例起作用

Class锁可以对类的所有对象实例起作用。

16.同步syn(class)代码块可以对类的所有对象实例起作用

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

17.String常量池特性与同步相关的问题与解决方案

结论:new Object()实例化一个新的Object对象,它并不放入缓存池中,或者执行new String()创建不同的字符串对象,形成不同的锁。

18.同步synchronized方法无限等待问题与解决方案

使用同步方法会导致资源被长期占用,得不到运行的机会。
解决:可以使用同步块来解决。

19.多线程的死锁

Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成。

20.内置类与静态内置类

21.内置类与同步

在内置类中有两个同步方法,但使用的是不同的锁,输出结果也是异步的。

同步代码块synchronized(lock)对lock上锁后,其他线程只能以同步的方式调用lock中的同步方法。

22.锁对象改变导致异步执行

在将任何数据类型作为同步锁时,需要注意是否有多个线程同时争抢对象。如果多个线程同时争抢相同的锁对象,则这些线程之间就是同步的;如果多个线程分别获得自己的锁,则这些线程之间就是异步的。

通常情况下,一旦持有锁后就不再对锁对象进行更改,因为一旦更改就有可能出现一些错误。

23.锁对象不改变依然同步执行

只要对象不变,运行的结果即为同步。

24.同步写法案例比较

3.volatile关键字

volatile在使用上具有以下特征:
1)可见性 2)原子性 3)禁止代码重排序。

1.可见性的测试

关键字volatile具有可见性,可见性是指A线程更改变量的值后,B线程马上就能看到更改后的变量的值,提高了软件的灵敏度。

1.单线程出现死循环
2.使用多线程解决死循环
3.使用多线程有可能出现死循环
4.使用volatile关键字解决多线程出现的死循环

在这里插入图片描述如上:私有堆栈中和公共堆栈中的值不同步造成的。
如下:使用volition关键字的主要作用是当线程访问isRunning这个变量时,强制从公共堆栈中进行取值。

在这里插入图片描述#### 5。synchronized代码块具有增加可见性的作用
关键字synchronized可以使多个线程访问同一个资源具有同步性,而且具有使线程工作内存中的私有变量与公共内存中的变量同步的特性,即可见性。

2.原子性的测试

1.在32位系统中long或double数据类型写操作作为非原子的。
2.关键字volatile int i++;非原子的特性

关键字volatile使用的主要场合是在多个线程中可以感知实例变量被更改了,并且可以获得最新的值时,也就是可用于增加可见性/可视性。

关键字volatile提示线程每次从共享内存中去读取变量,而不是从私有内存中去读取,这样就保证了同步数据的可见性。

表达式i++的操作步骤分解:
1)从内存中取出i的值;
2)计算i的值;
3)将i的值写到内存中;

3.使用Atomic原子类进行i++操作实现原子性

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

4.出现逻辑混乱与解决

在有逻辑性的情况下,原子类的输出结果具有随机性。

3.禁止代码重排序的测试

代码重排序是为了追求更高的程序运行效率。

1.关键字volatile之前的代码可以重排
2.关键字volatile之后的代码可以重排
3.关键字volatile之前的代码不可以重排到volatile之后
4.关键字volatile之后的代码不可以重排到volatile之前
5.关键字synchronized之前的代码不可以重排到sychronized之后
5.关键字synchronized之后的代码不可以重排到synchronized之前

总结

关键字synchronized的主要作用是保证同一时刻,只有一个线程可以执行某一个方法,或是某一个代码块,synchronized可以修饰方法及代码块。
三大特征:
1)可见性:synchronized具有可见性。
2)原子性:使用synchronized实现了同步,同步实现了原子性,保证被同步的代码段在同一时间只有一个线程在执行。
3)禁止代码重排序:synchronized禁止代码重排序。

关键字volition的主要作用是让其他线程可以看到最新的值,volition只能修饰变量。
三大特征:
1)可见性:B线程能马上看到A线程更改的数据。
2)原子性:在32位系统中,针对未使用volatile声明的long或double数据类型没有实现原子性,如果想实现,则声明变量时添加volition,而在64位系统中,原子性取决于具体的实现,在X86架构64位JDK版本中,写double或long是原子的。另外,针对用volition声明的int i变量进行i++操作时是非原子的。
3)禁止代码重排序。

关键字synchronized和volition的使用场景总结如下:
1)当想实现一个变量的值被更改时,让其他线程能取到最新的值时,就要对变量使用volition。
2)当多个线程对同一个对象中的同一个实例变量进行操作时,为了避免出现非线程安全问题,就要使用synchronized.

3.线程间通信

线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理是不能成为一个整体的,线程间的通信是使这些独立的个体成为整体的必用方案之一,可以说,线程间进行通信后系统之间的交互性会更强大,在大大提高CPU利用率的同时也会使程序员对个线程任务的处理过程进行有效把控与监督。

1.wait/notify机制

线程与线程之间不是独立的个体。他们彼此是可以互相通信和协作的。

1.wait/notify机制的原理

拥有相同锁的线程才可以实现wait/notify机制。
wait()方法是Object类的方法,它的作用是使当前执行的wait()方法的线程等待,在wait()所在的代码行处暂停执行,并释放锁,直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方式或同步块中调用wait()方法。通过通知机制使某个线程继续执行wait()方法后面的代码时,对线程的选择是按照执行wait()方法的顺序确定的,并需要重新获得锁。如果调用wait()时没有持有适当的锁,则抛出IllegaMonitorStateException,它是RuntimeException的一个子类,因此不需要try-catch语句捕捉异常。

notify()方法要在同步方法或同步块中调用,即在调用前,线程必须获得锁,如果调用notify(()时没有持有适当的锁,则会抛出IllegalMonitorStateException.该方法用来通知那些可能等待该锁的其他线程,如果有多个线程等待,则按照执行wait()方法的顺序对处于wait状态的线程发出一次通知,并使该线程重新获取锁。

执行notify()方法后,当前线程不会马上释放该锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized同步区域后,当前线程才会释放锁,而呈wait状态的线程才可以获取该对象锁。

当第一个获得了该对象锁的wait程序运行完毕后,它会释放该对象锁,此时如果没有再次使用notify语句,那么其他呈wait状态的线程因为没有得到通知,会继续处于wait状态。

总结:wait()方法使线程暂时运行,而notify()方法通知暂停的线程继续运行。

2.wait()方法的基本使用

wait()方法的作用是使当前线程暂时运行,并释放锁。

关键字synchronized可以将任何一个Object对象作为锁来看待,而Java为每个Object都实现了wait()和notify()方法,他们必须用在被synchronized同步的Object的临界区内。通过调用wait()方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁,而notify操作可以唤醒一个因调用了wait操作而处于wait状态中的线程,使其进入就绪状态,被重新唤醒的线程会试图重新获得临界区的控制权,也就是锁,并继续执行临界区内wait之后的代码。如果发出notify操作时没有处于wait状态中的线程,那么改命令会被忽略。

wait()方法可以使调用方法的线程释放锁,然后从运行状态转换成wait状态,等待被唤醒。

notify()方法按照执行wait()方法的顺序唤醒等待同一个锁的”一个“线程,使其进入可运行状态,即notify()方法仅通知一个“线程”。

notifyAll()方法执行后,会按照执行wait()方法相反的顺序唤醒全部的线程。

3.线程状态的切换

在这里插入图片描述
1)创建一个新的线程对象后,调用它的start()方法,系统会为此线程分配CPU资源,此时线程处于runnable(可运行)状态,这是一个准备运行的阶段。如果线程抢占到CPU资源,则此线程就处于running(运行)状态。
2)runnable状态和running状态可相互切换,因为有可能程序运行一段时间后,其他高优先级的线程抢占了CPU资源,这时次线程就从running状态变成runnable状态。
线程进入runnable状态的大体分为如下4种情况。
a)调用sleep()方法后经过的时间超过了指定的休眠时间;
b)线程成功获得了试图同步的监视器;
c)线程正在等待某个通知,其他线程发出通知;
d)处于挂起状态的线程调用了resume恢复方法。
3)blocked是阻塞的意思。例如:如果遇到了一个I/O操作,此时当前线程由runnable运行状态转成blocked阻塞状态,等待I/O操作的结果。这时操作系统会把宝贵的CPU时间片转成blocked阻塞状态,等待I/O操作的结果。这时操作系统会把宝贵的CPU时间片分配给其他线程,当I/O操作结束后,线程由blocked状态结束,进入runnable状态,线程会继续运行后面的任务。
出现阻塞的情况大体分为如下5种。
a)线程调用sleep()方法,主动放弃占用的处理器资源。
b)线程调用了阻塞式I/O方法,在该方法返回前,该线程被阻塞。
c)线程试图获得一个同步监视器,但该同步监视器正被其它线程所持有。
d)线程等待某个通知(notify).
e)程序调用了suspend()方法将该线程挂起。此方法容易导致死锁,应尽量避免使用该方法。
4)run()方法运行结束后进入销毁阶段,整个线程执行完毕。

4.wait()方法:立即释放锁

5.sleep()方法:不释放锁

将wait()方法改成sleep()方法,就获得了同步的效果,因为sleep()方法不释放锁。

6.notify()方法:不立即释放锁

必须执行完notify()方法所在的同步synchronized代码块后才释放锁。

7.interrupt()方法遇到wait()方法

当线程调用wait()方法后,在对该线程对象执行interrupt()方法会出现InterruptedException异常。

1)执行完notify()方法后,按照执行wait()方法的顺序唤醒其他线程。notify()所在的同步代码块执行完才会释放对象的锁,其他线程执行wait()之后的代码。
2)在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。
3)在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,等待被唤醒。

8.notify()方法:只通知一个线程

唤醒的顺序与执行的wait()方法的顺序一致。

9.notifyAll()方法:通知所有线程

会按照执行wait()方法的倒叙一次对其他线程进行唤醒。

环形的顺序是正序,倒叙,随机,取决于具体的jvm实现。

10.wait()方法的基本使用

带一个参数的wait(long)方法的功能是等待某一个时间内是否有线程对锁进行notify()通知唤醒,如果超过这个时间则线程自动唤醒,能继续向下运行的前提是再次持有锁。

11.wait(long)方法自动向下运行需要重新持有锁

wait(long)方法想要自动向下运行也要持有锁,如果没有锁,则一直在等待,直到持有锁为止。

12.通知过早问题与解决方法

如果通知过早,则会打乱程序正常的运行逻辑。

13.wait条件发生变化与使用while的必要性

14.通过管道进行线程间通信——字节流

管道流(pipe stream)是一种特殊的流,用于在不同线程之间直接传送数据。一个线程发送数据到输出管道,另一个线程从输出管道中读取数据。无需借助临时文件。
Java JDK提供了4个类来使线程间可以进行通信,即PipedInputStream和PipedOutputStream,PiedReader和PipedWriter.

inputStream.connect(outputStram)或outputStream.connect(inputStraem)的作用是使两个管道之间建立通信连接,这样才可以对数据进行输出与输入。

15.通过管道进行线程间通信——字符流

2.join()方法的使用

多线程创建并启动子线程,如果子线程要进行大量的耗时运算,主线程往往将早于子线程结束之前结束,这时如果主线程想等待子线程执行完成之后在结束,就是用join()方法。

方法join()的作用是等待线程对象销毁。

1.学习join()方法前的铺垫

join()方法的作用是使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无限期的阻塞,等待线程x销毁后再继续执行z后面的代码,具有串联执行的效果。

join()方法具有使线程排队运行的效果,有些类似同步的运行效果,但是join()方法与synchronized的区别是join()方法在内部使用wait()方法进行等待,而synchronized关键字使用锁作为同步。

2.join()方法和interrupt()方法出现异常

在使用join()方法的过程中,如果当前线程对象被中断,则当前线程出现异常。

3.join(long)方法的使用

x.join(long)方法中的参数用于设定等待的时间,不管x线程是否执行完毕,时间到了并且重新获得了锁,则当前线程会继续向后运行。如果没有重新获得锁,则一直在尝试,知道获得锁为止。

4.join(long)方法与sleep(long)方法的区别

join(long)方法的功能在内部是使用wait(long)方法来进行实现的,所以join(long)方法具有释放锁。

5.join()方法后面的代码提前运行——出现意外

6.join(long millis,int nanos)方法的使用

作用是等待该线程终止的时间最长为millis毫秒+nanos纳秒。

3.类ThreadLocal的使用

变量值的共享可以使用public static变量的形式实现,所有的线程都使用同一个public static变量那如何实现每一个线程都有自己的变量?JDK提供的ThreadLocal可用于解决这样的问题。

类ThreadLocal的主要作用是将数据放入当前线程对象中的Map中,这个Map是Thread类的是变量。类ThreadLocal自己不管理,不存储任何数据,它只是数据和Map之间的桥梁,用于数据放入Map中,执行流程如下:数据->ThreadLocal->currentThread()->Map.

执行后每个线程中的Map存有自己的数据,Map中的key存储的是TheadLocal对象,value就是存储的值。每个Thread中的Map值只对当前线程可见,其他线程不可以访问当前线程对象中Map的值。当前线程销毁,Map随之销毁,Map中的数据如果没有被引用,没有被使用,则随时GC回收。
在这里插入图片描述

1.get()方法与null

如果从未在Thread中的Map存储ThreadLocal对象对应的value值,则get()方法返回null.

类ThreadLocal解决的是变量在不同线程隔离性,也就是不同线程拥有自己的值,不同线程的值是可以通过ThreadLocal类进行保存的。

2.解决get()方法返回null的问题

覆盖initialValue()方法具有初始值,因为·ThreadLocal.java中的initialValue()方法默认返回值是null,所以要在子类中进行重写。

3.重写initialValue()方法的隔离性

子线程和父线程各有自己所拥有的值

4.类ThreadLocal不能实现值继承

5.使用InheritableThreadLocal体现值继承特性

该类可以让子线程从父线程继承值。

6.父线程有最新值,子线程仍是旧值

7.子线程有最新的值,父线程仍是旧值

8.子线程可以感应对象属性值的变化

9.重写childValue()方法实现对继承的值

进行加工子线程可以在任意的时间执行InheritableThreadLocalExt.set()方法任意次,使自身具有最新的值,而重写childValue()方法实现子线程具有新的值是只有在创建子线程时才会发生,但是仅仅是一次。

4.Lock对象的使用

1.使用ReentrantLock类

ReentrantLock类在扩展功能上比synchronized更强大,具有嗅探锁定,多路分支通知等功能。

1.使用ReentrantLock实现同步

调用ReentrantLock对象的lock()方法获取锁,调用unlock()方法释放锁,这两个方法成对使用。想要实现同步某些代码,把这些代码放在lock()和unlock()之间即可。

2.多代码块间的同步性

线程之间的顺序是随机的。

3.await()方法的错误用法与更正

关键字synchronized与wait(),notify()/notifyAll()方法相结合可以实现wait/notify模式,ReentrantLock类也可以实现同样的功能,但需要借助于Condition对象。

在使用notify()/notifyAll()方法进行通知时,被通知的线程由JVM进行选择,而方法notifyAll()会通知所有的waiting线程,没有选择权,会出现相当大的效率问题,但使用ReentrantLock结合Condition类可以实现“选择性通知”,这个功能是Condition类默认提供的。

Condition对象的作用是控制并处理线程的状态,它可以使线程呈wait状态,也可以让线程继续运行。

await()方法的作用使当前线程在接到通知或被中断之前一直处于等待wait状态。它和wait()方法的作用一样。

await()方法必需要再之前调用lock()方法

4.使用await()和signal()实现wait/notify机制

Object类中的wait()方法相当于Condition类中的await()方法。
Object类中的wait(long timeout)方法相当于Condition类中的await(long time,TimeUnit unit)
Object类中的notify()方法相当于Condition类中的signal()方法
Object类中的notifyAll()方法相当于Condition类中的signalAll()方法。

5.await()方法暂停线程运行的原理

并发包源代码内部执行了Unsafe类中的public native void park(boolean isAbsolute,long time)方法,让当前线程呈暂停状态,方法参数isAbsolute代表是否为绝对时间,方法参数time代表时间值。如果对参数isAbsolute传入true,则第二个参数time时间单位为毫秒,如果传入false,则第2个参数时间单位为纳秒。

6.通知部分线程

使用Condition对象可以唤醒指定种类的数据,通过唤醒的对象锁.signalAll().

7.公平锁与非公平锁

公平锁:采用先到先得的策略,每次获取锁之前都会检查队列里面有没有排队等待的线程,没有才会尝试获取锁,如果有就将当前线程追加到队列中。

非公平锁:采用“有机会插队”的策略,一个线程获取锁之前要先尝试获取锁而不是在队列中等待,如果获取锁成功,则索命线程虽然是后台启动的,但先获得了锁,这就是“作弊插队”的效果。如果获取锁没有成功,那么才将自身追加到队列中进行等待。

A线程持有锁后,B线程不能执行的原理是在内部执行了unsafe.park(false,OL)代码;A线程释放后B线程可以运行的原理是当A线程执行unlock()方法时在内部执行了unsafe.unpark(bThread),B线程得以继续运行。

8.public int getHoldCount()方法的使用

作用是查询“当前线程”保持此锁定的个数,即调用lock()方法的次数。

9.public final int getQueueLength()方法的使用

作用是返回正等待获取此锁的线程估计数。

9.public int getWaitQueueLength(Condition condition)方法的使用

作用是返回等待与此锁相关的给定条件Condition的线程估计数。

10.public final boolean hasQueuedThread(Thread thread)方法的使用

作用是查询指定的线程是否正在等待获取此锁,也就是判断参数中的线程是否在等待队列中。

11.public final boolean hasQueuedThreads()方法的使用

作用是查询是否有线程正在等待获取此锁,也就是等待队列中是否有等待的线程。

12.public boolean hasWaiters(Condition condition)方法的使用

作用是查询是否有线程正在等待与此锁有关的condition条件,也就是是否有线程执行了condition对象中的await()方法而呈等待状态。而public ine getWaitQueueLength(Condition condition)方法的作用是返回有多少个线程执行了condition对象中的await()方法而呈等待状态。

13.public final boolean isFair()方法的使用

判断是不是公平锁

14.public boolean isHeldByCurrentThread()方法的使用

作用是查询当前线程是否保持此锁。

15.public boolean isLocked()方法的使用

作用是查询此锁是否由任意线程保持,并没有释放。

16.public void lockInterruptibly()方法的使用

作用是当某个线程尝试获得锁并且阻塞在lockInterruptibly()方法时,该线程可以被中断。

17.public boolean tryLock()方法的使用

作用是嗅探拿锁,如果当前线程发现被其他线程持有了,则返回false,程序执行后面的代码,而不是呈阻塞等待锁的状态。

18.public boolean tryLock(long timeout,TimeUnit unit)方法的使用

作用是嗅探拿锁,如果当前线程发生锁被其他线程持有了,则返回false,程序继续执行后面的代码,而不是呈阻塞等待的状态。如果当前线程在指定的timeout内持有了锁,则返回值是true,超过时间则返回false。参数timeout代表当前线程抢锁的时间。

19.public booleanawait(long time,TimeUnit unit)方法的使用

作用和public final native void wait(long timeout)方法一样,都具有自动唤醒线程的功能

20.public long awaitNanos(long nanosTimeout)方法的使用

public long awaitNanous(long nanosTimeout)方法的作用和public final native void wait(long timeout)方法一样,都具有自动唤醒线程的功能,时间单位是纳秒(ns).

21.public boolean awaitUntil(Date deadline)

方法作用是在指定的Date结束等待

22.public void awaitUninterruptibly()方法的使用

作用是实现线程在等待的过程中,不允许被中断。

2.使用ReentrantReadWriteLock类

该类具有完全排他的效果,同一时间只有一个线程在执行ReentrantLock.lock()方法后面的任务,这样做虽然保证了同时写实例变量的线程安全性,但效率是非常低下的,所以JDK提供了一种读写锁——ReentreadWritrLock类,使用它可以在进行读操作时不需要同步执行。提升运行速度,加快运行效率。

读写锁有两个锁:一个是读操作相关的锁,也称共享锁;另一个是写操作相关的锁,也称排他锁。

读读是异步 ,读写,写读,写写都是互斥。

1.ReentrantLock类的缺点

使用ReentrantLock对象时,所有的操作都同步。哪怕只是对实例变量进行读取操作,这样会耗费大量的时间,降低运行效率。

1.ReentrantReadWriteLock类的使用——读读共享

2.ReentrantReadWriteLock类的使用——写写互斥

3.ReentrantReadWriteLock类的使用——读写互斥

4.ReentrantReadWriteLock类的使用——写读互斥

5.定时器Timer

Timer类的主要作用是设置计划任务,即在指定时间开始执行某一个任务。
TimerTask类的主要作用是封装任务。
执行任务计划的代码要放入TimerTask的子类中,因为TimerTask是一个抽象类。

1.schedule(TimerTask task,Date time)方法的测试

该方法的作用是在指定日起执行一次某一任务。

1.执行任务的时间晚于当前时间——在未来执行的效果

2.TimerThread线程不销毁的原因

进程不销毁的的原因是在创建Timer对象时启动一个新的非守护线程。
只有满足if(queue.isEmpty())条件,才执行break退出while(true)死循环。

基本的源代码逻辑:

  1. 使用while循环queue.isEmpty()&&new TasksMayBeScheduled条件进行判断。
  2. 当&&两端运算结果都为true时,执行wait()方法使当前线程暂停运行,等待被唤醒。
  3. 唤醒线程的时机是执行了public void schedule(TimerTask task,Data time)方法。
  4. 唤醒线程后while继续判断queue.isEmpty()&&newTasksMayBeScheduled条件。
  5. if(queue.isEmpty())中的queue.isEmpty()结果为true,说明队列为空,那么就执行break语句退出while(true)死循环。
  6. 执行public void cancel()方法会使布尔变量newTasksMayBeScheduled的值由true变成false.
  7. 不执行public void cancel(),则变量newTasksMayBeScheduled的值就不会是false,进程一直呈死循环的状态,进程不销毁就是这个原因。

3.使用public void cancel()方法实现线程TimerThread销毁

该方法的作用是终止此计时器,丢弃所有以前安排的任务。这不会干扰当前正在执行的任务(如果存在)。一旦终止了计时器,那么它的执行线程也会终止,并且无法根据它安排更多的任务。(正在调用的计时器任务的run()方法内调用此方法,可以确保正在执行的任务是此计时器所执行的最后一个任务。只有效一次)。

4.计划时间早于当前时间——立即运行的效果

如果执行任务的时间早于当前时间,则立即执行task任务。

5.在Timer中执行多个TimerTask任务

6.延时执行TimerTask的测试

TimerTask以队列的方式逐一按顺序执行,所以执行的时间有可能和预期的时间不一致,因为前面任务有可能消耗的时间较长,而后面任务的运行时间也可能会被延后。

2.schedule(TimerTask task,Data firstTime,long period)方法的测试

作用是在指定日期之后按指定的间隔周期无限循环地执行某一任务。

1.计划时间晚于当前时间——在未来执行的效果

2.计划时间早于当前时间——立即运行的效果

3.延时执行TimerTask的测试

4.TimerTask类中的cancel()方法

该方法的作用是将自身从任务队列中清除。

5.Timer类中的cancel()方法

该方法是将任务队列中全部任务清空。并且进程被销毁。

6.间隔执行Task任务的算法

将末尾移到最前面
ABC
CAB
BCA

7.Timer类中的cancel()方法的使用注意事项

调用Timer类中的cancel()方法有时并不一定会停止计划任务,即计划任务正常执行。
Timer类中的cancel()方法有时并没有争抢到queue锁,所以TimerTask类中的任务正常运行。

3.schedule(TimerTask task,long delay)方法的测试

作用是以schedule(TimerTask task,long delay)方法当前的时间为参考时间,在此时间基础上延迟指定的毫秒数后执行一次TimerTask任务。

4.schedule(TimerTask task,long delay,long period)方法的测试

作用是以执行schedule(TimerTask task,long delay,long period)方法当前的时间为参考时间,再此时间基础上延迟指定的毫秒数再以某一间隔时间无限次数的执行某一任务。

凡是代有period参数的方法,均无限循环执行TimerTask中的任务。

5.schedule(TimerTask task,long delay,long period)方法的测试

schedule()和scheduleAtFixedRate()方法的主要区别在于用没有追赶特性。

1.测试schedule()方法任务不延时——Date类型

在不延时的情况下,如果在任务没有被延时执行,则下一次执行任务的开始时间是上一次任务的开始时间加上period时间。
“不延时”是指执行任务的时间小于period间隔时间

2.测试schedule()方法任务不延时——long类型

在不延时的情况下,如果任务没有被延时执行,则第一次执行任务的时间是任务开始时间加上delay时间,接下来执行任务的时间是上一次任务的开始时间加上period时间。

3.测试schedule()方法任务延时——Date类型

在延时的情况下,如果任务被延时执行,那么下一次任务的执行时间参考的是上一次“结束”时的时间来开始的。

4.测试schedule()方法任务延时——long类型

在延时的情况下,如果任务被延时执行,那么下一次任务的执行时间参考的是上一次“结束”时的时间来开始的。

5.scheduleAtFixedRate()方法任务不延时——Data类型

在不延时的情况下,如果在任务没有被延时执行,则下一次执行任务的开始时间是上一次任务的开始时间加上period时间。

6.scheduleAtFixedRate()方法任务不延时——long类型

在不延时的情况下,如果任务没有被延时执行,则第一次执行任务的时间是任务开始时间加上delay时间,接下来执行任务的时间是上一次任务的开始时间加上period时间。

7.scheduleAtFixedRate()方法任务延时——Data类型

在延时的情况下,如果任务被延时执行,那么下一次任务的执行时间参考的是上一次“结束”时的时间来开始的。

8.scheduleAtFixedRate()方法任务延时——long类型

在延时的情况下,如果任务被延时执行,那么下一次任务的执行时间参考的是上一次“结束”时的时间来开始的。

9.验证schedule()和scheduleAtFixedRate()方法的追赶执行性

在两个时间段内的时间所对应的Task任务被“弥补”地执行,也就是在指定时间段内的运行次数必须运行完整,这就是Task任务的追赶特性。
scheduleAtFixedRate()具有追赶执行性

6.单例模式与多线程

1.立即加载/饿汉模式

使用类的时候已经将对象创建完毕。
可以完成加载的对象是同一个
缺点是:不能有其他实例变量,因为getInstance()方法没有同步,有可能出现线程安全问题。

2.延迟加载/懒模式

调用方法时实例才会被加载
缺点:在多线程环境中会取出多个实例

3.延迟加载/懒汉模式的解决方案

  1. 声明synchronized关键字
    效率低
  2. 尝试同步代码块
    还是比较低
  3. 针对某些重要代码进行单独同步
    此方法使synchronized语句块只对实例化对象的关键代码进行同步,从语句的结构来讲,运行效率的确提高了,但多线程的情况下还是无法解决得到同一个实例对象的结果。
  4. 使用DCL机制
    DCL(双检查)机制来实现多线程环境中的延迟加载单例模式。
    使用volition修改变量,让变量在多个线程间达到可见性,另外也禁止new对象时的代码重排

4.使用静态内置类实现单例模式

5.序列化与反序列化的单例模式实现

protected Object readResolve()方法的作用是在反序列化时不创建新的MyObject对象,而是复用JVM内存中原有的MyObject对象被复用,也就是实现了对MyObject序列化与反序列化时保持单例性的效果。

注意:如果序列化和反序列化操作分别放入两个Clss中,则反序列化时会产生新的MyObject对象,放在两个class类中分别执行其实相当于创建了两个JVM虚拟机,每个虚拟机里面的确只有一个MyObject对象,我们想要实现的是在一个JVM虚拟机中进行序列化与反序列化时保持MyObject单例性的效果,而不是创建两个JVM虚拟机。

6.使用enum枚举数据类型实现单例模式

enum枚举数据类型的特性和静态代码块的特征相似,在使用枚举时,构造方法会被自动调用,可以应用这个特性实现单例模式。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值