搜公众号
推荐 原创 视频 Java开发 开发工具 Python开发 Kotlin开发 Ruby开发 .NET开发 服务器运维 开放平台 架构师 大数据 云计算 人工智能 开发语言 其它开发 iOS开发 前端开发 JavaScript开发 Android开发 PHP开发 数据库
Lambda在线 > 乐哉码农 > 多线程开发编程总结

多线程开发编程总结

乐哉码农 2019-02-08
举报


乐哉码农

摘要

这段时间,抽时间学习了下多线程编程,对线程的基本使用,到线程的同步、线程之间进行通信、JDK线程工具类的使用、单例模式与线程的结合进行了一个总结与思考

一、线程的基本使用

学习java基础的时候,我们都知道创建一个线程是可以通过两种方式,一是通过继承Thread类,重写run方法,二是通过实现Runnable接口,实现run方法,这两种方式实现的目的都是一样的,后者的产生是由于java类只能进行单继承,而接口确可以进行多继承,所以才引用了Runnable的接口

1.通过继承Thread类,创建线程


多线程开发编程总结

使用Thread

注意:这里启动线程使用了两种方式,两者的区别在于,后者并没有开启新线程,还是在主线程中进行操作,前者调用stsrt方法才是真正的在主线程中启动一个子线程运行

2.通过继承Runnable接口实现多线程

多线程开发编程总结

使用Runnable方式1


多线程开发编程总结

使用Runnable方式2

通过Runnable创建线程有两种方式,第一种是定义一个类去继承Runnable接口,第二个就是以成员变量的方式去创建线程,第二种特别适合一个类中有多个操作需要开启线程,但是业务逻辑又比较简单,就不需要为每个线程都去创建一个类

实现Runnable接口的方式创建的线程,启动的方式和第一种有点不一样,他需要先实例化一个Thread的实例,然后将自己作为参数传递给Thread,启动方式也是通过调用start方法

多线程开发编程总结

3.线程安全与非线程安全概念的定义

在学习多线程之前,就了解过线程安全,当时以为有线程安全,肯定就有线程不安全的一说,学习本书才知道,那是叫非线程安全

线程安全指的是在多线程情况下,数据不会受多线程所影响,数据不需要考虑线程在交替切换的时候带来的影响并且不需要任何额外的同步操作。方法内部的变量属于线程安全的,实例变量属于非线程安全

非线程安全与线程安全的定义刚好相反,在多线程情况下,它会因为线程的交替切换,而导致出现脏数据,并且需要借助外部工具进行同步操作,才能避免这些情况

4.线程的停止

线程的停止与暂停,在多线程刚开发中,经常使用interrupt方法进行线程的停止,但是这个方法并不能有效的终止正在运行中的线程,在线程中调用interrupt之后,只会给此线程贴上一个终止标记,停止操作需要我们自己操作,例如可以通过抛出异常,我们在捕获这个异常的时候进行优雅的线程停止操作;

如何判断线程是否被贴上线程终止的标记,jdk中为我们提供了两种方法

interrupted()、isInterrupted()

interrupted()方法是测试当前运行这个方法的线程的终止状态,并且在调用之后会清除当前线程的终止标记

多线程开发编程总结

终止当前线程,并调用interrupted进行探测

多线程开发编程总结

测试结果

分析测试结果可以看出,第一调用此方法后,返回的是true,我们可以理解,因为线程在之前已经调用过interrupt方法,给线程打上了终止标记,但是第二次看到的却是false,这是为什么呢,因为在调用过interrupted之后,会将终止标记进行清除。

isInterrupted()方法返回的是当前所属线程的终止状态,与interrupted()不同的是,它不会清除终止标记,测试结果如下

多线程开发编程总结

测试用例

多线程开发编程总结

测试结果

停止线程的方法前面已经介绍了一种,通过判断线程终止标记,通过抛异常进行线程的终止。另外一种是调用stop方法,暴力停止,但这种方法官方已经不推荐使用,因为使用这种方法停止县城后,我们不能对线程终止后进行清理动作。 

还有几种方法:return;

5.线程的暂停

suspend-resume

缺点:造成数据不同步、锁独占,不能释放

6.yield方法

释放当前CPU

7.守护线程

系统中定义了两种线程:守护线程、用户线程

守护线程随着用户线程消失而消失,当前如果没有了用户线程,则守护线程则自动消失。例如我们运行main方法的时候,创建的就是守护线程,我们在创建线程的时候默认创建的就是用户线程,可以通过在线程start之前设置thread1.setDaemon(true);,来表明这是一个守护线程,

jvm中垃圾收集器就是守护线程,当系统中没有了用户线程的时候,他就会退出,这样设计的意义在于当前如果没有用户线程在运行,他也就没有进行垃圾收集的意义了。

二、线程的同步

线程间的同步可以借助于jdk为我们提供的synchronized

1.同步方法

通过在方法前面加上synchronized关键字,实现同步,如果该方法是使用static方法修饰的,则对这个class进行同步,也就是当前的锁对象指的是这个类,如果是非static修饰的方法,则当前的锁对象是当前类的实例,如果不是同一个实例,那么他们的锁就不一样,那么这两个线程就会异步执行,不会进行同步操作。接下来通过一个测试案例进行测试:

多线程开发编程总结

创建一个线程,调用传入实例的方法

多线程开发编程总结

synchronized修饰的static方法

多线程开发编程总结

通过创建两个实例,去启动线程

多线程开发编程总结

测试结果,呈现同步效果

结论:synchronized修饰的static方法,传入两个不同的实例是可以实现同步,因为现在的所对象是这个类

我们将static去掉看看

多线程开发编程总结

synchronized修饰的非static方法

多线程开发编程总结

测试结果程不同步效果


结论:synchronized修饰非static方法,如果传入的实例不同,则不会呈现同步效果,因为当前锁的对象是类的实例,所以传入不同的实例,锁就会不同,当然实现不了同步的效果

我们现在传入相同的实例查看效果

多线程开发编程总结

传入相同的实例

多线程开发编程总结

测试结果呈同步效果

结论:synchronized当前的锁是类的实例,当前实例是相同的,所以锁自然相同,当然可以实现同步

2.同步代码块

同步代码块可以只对我们需要关心的同步模块进行同步,不需要锁住整个方法,通过代码块所持的锁对象可以是任何一个对象,如果想实现同步,只要他们所持的锁对象相同即可,锁对象又被称作监视器,获得了锁又称获得监视器

多线程开发编程总结

需要注意的是锁对象值得改变并不会影响同步

3.volatile关键字

volatile能够使所修饰的变量,能够在使用到这个变量的时候,会让他强行从公有区中取值,因为我们在开启了java虚拟机的-server模式的时候,虚拟机为了提升效率,会为每个线程创建一个线程私有变量,线程每次读取变量的时候都会从私有区进行读取,而我们更新的变量却更新的是公有区的,并没有同步到私有区,所以需要强制让线程从公有区进行取值

另外,通过synchronized也能达成类似的功能

三、线程间的通信

线程间虽然是单独的个体,但是他们之间也需要进行通信和数据的传输,这样我们才能灵活的利用线程帮助我们解决业务问题

1.wait、notify、notifyall进行等待 通知

使用wait方法,可以让当前线程进入等待状态,并且会释放当前锁,需要被调用notify或者notifyall之后,进行换新,重新竞争获得执行权,

我们使用notify进行线程唤醒之后,并不会立即释放锁,它会执行剩下的操作,线程结束后才会释放锁。

wait和notify方法都是Object提供,所以任何对象都可以进行调用这个方法,但是有个前提是,在使用wait和notify之前必须获得此对象的监事权。

否则会抛出异常,接下来进行代码测试

多线程开发编程总结

执行如上代码之后,发现抛出了IllegalMonitorStateException异常,未获得锁的监事权,因为直接调用的是wait方法,默认是当前对象,但是在调用对象之前并未获得监视,所以抛出异常;

现在改动代码如下

多线程开发编程总结

在调用之前通过同步代码块或得this监视,程序无抛出异常


现在对notify不释放锁,wait释放锁进行测试

多线程开发编程总结

测试用例



测试结果

通过对测试结果分析:首先t1拿到了锁,然后调用wait进行等待,如果它此时不释放锁,notify是不会进行打印的,所以说明调用wait之后,线程释放了锁,接着再看天进行notify之后,睡了3秒之后,接连打印了后面的语句,如果此时notify释放了锁,又睡了3秒,锁肯定被他抢占到,t1的打印肯定在t2前面,可是结果是t2先打印,T1在其后打印,与我们分析相反,所以得出结论:wait释放锁,notify不释放锁

notifyall与notify除了通知范围不同,还有一个就是notify只能通知同类锁,也就是锁对象是同一个,而notifyall则没有这个限制

2.join方法

t1.join方法是等待这个线程结束后在运行后面的代码,也可以传入时间参数,等待指定时间如果还没结束,就直接执行后续语句,另外join方法内部也是通过使t1进行wait,实现,所以他自身也会释放锁

3.threadlocal

又称线程私有变量,为每个线程存储自己的变量,实现线程间变量的隔离

四、工具类LOCK的使用

在java5中,通过创建lock对象也能够使用同步,并且控制更加的灵活

1.ReentrantLock的基本使用

private static ReentrantLock lock=new ReentrantLock();

通过在需要同步的代码前后添加lock.lock()和lock,unlock()进行锁的占用和解锁

2.通过condition实现wait,notify同样的功能,并且更加强大

通过private static Conditioncondition2=lock.newCondition();可以实例化一个condition,可以实例化多个,再调用condition.await和.singal进行线程的等待与通知,也有类似notifyall的signalall方法,另外condition还可以对自己感兴趣的线程进行通知,通过实例化多个condition对象,在进行通知的时候,使用对应的conditon对象进行通知,凡是使用这个对象进行等待的线程,都会被唤醒

3.ReentrantReadLock和ReentrantWriteLock的使用

有时候我们不应对所有的代码都要进行控制,我们需要进行同步控制的是对那些更新数据或产生数据的模块进行控制,所以ReentrantReadLock和ReentrantWriteLock可以为我们所用,通过名字就能看出,readlock是可以进行异步执行的,也就是不会独占锁,而writelock,会进行同步操作,因为需要对数据进行更新操作。

到这里,已经写完了我已经了解到的多线程相关的知识。

个人站点:http://www.lebaol.cn:8080(网站在备案中,备案成功后会将8080端口去掉)


乐哉码农


版权声明:本站内容全部来自于腾讯微信公众号,属第三方自助推荐收录。《多线程开发编程总结》的版权归原作者「乐哉码农」所有,文章言论观点不代表Lambda在线的观点, Lambda在线不承担任何法律责任。如需删除可联系QQ:516101458

文章来源: 阅读原文

相关阅读

举报