java多线程与线程间通信
进程与线程
进程:是一个正在执行的程序。
每一个进程执行都有执行顺序,一个执行顺序是一个执行路径,或者叫控制单元;
每一个程序启动时,都会在内存中分配一片空间,进程就用于标识这片空间,并封装一个或若干控制单元。
线程:就是进程中的一个独立的控制单元。
线程控制进程的执行,一个进程至少有一个线程。
Java程序编译时,java编译器启动,对应javac.exe进程启动,编译结束后javac.exe进程退出;java程序运行时,jvm启动,对应java.exe进程启动,java.exe中有一个主线程负责java程序的执行,这个主线程运行的代码就存在于main方法中。其实jvm启动时,不止一个主线程,还有负责垃圾回收机制的线程。
有多条执行路径的程序,称为多线程程序。多线程的好处是可以让程序的多个部分代码产生同时运行的效果,程序的多个功能分支并行执行,优化程序功能结构并提高效率。
自定义创建线程的2种方法
1. 继承Thread类,具体步骤:
a) 自定义类,继承Thread;
b) 复写Thread类的run()方法,run()方法中存储线程要运行的代码;
c) 创建继承Thread的自定义类对象,调用线程的start()方法,start()方法的作用:启动线程,并自动调用run()方法。
2. 实现Runnable接口,具体步骤:
a) 定义类实现Runnable接口;
b) 覆盖Runnable接口中的run()方法,run()中存放线程要运行的代码;
c) 创建Thread类线程对象,并将Runnable接口的子类对象作为实参传递给Thread类构造函数;
d) 调用Thread类对象的start()方法。
2种方式的区别:
实现方式时,线程代码存放在实现Runnable接口的子类的run()方法中,可以使用该子类创建多个Thread类,这样多个线程运行时可以共用Runnable子类中的成员变量,实现资源的独立共享。
继承方式时,线程代码存放在Thread子类的run()方法中,而一个线程不能多次start(),所以达不到Thread子类中资源数据的共享使用。
自定义线程时,建议使用实现Runnable接口的方式,因为这样还可以避免单继承的局限性。
线程的几个零散知识点
多线程运行结果的随机性:单核CPU环境,多个线程并非真正的同时运行,而是互相抢夺CPU的执行权限和资源,谁抢到谁执行,至于执行多长时间,CPU说了算(所以多线程程序的每次运行结果可能都不一样)(后续可以加以控制)。
多核CPU环境,多个线程可以分布运行到多个CPU上,实现真正的同时运行。
多核CPU环境上多个线程同时打印输出信息时,可能打印顺序混乱,这是因为多个CPU核抢占DOS输出屏是随机的,有的打印被临时阻塞。
多核CPU时,程序运行效率就卡在了内在空间上,必须要有足够大的内存存储很多线程,才能让这些线程运行在多个CPU上。
线程状态及状态间切换
已start()过的线程不能再次start(), 否则会报异常java.lang.IllegalThreadStateException。
线程的名称
线程对象都有自己默认的名称:Thread-编号,编号从0开始。
设置自定义线程名称,可以在子类构造函数中调用super(name), 也可以直接创建对象后调用setName()方法。
Thread.currentThread(), 返回当前运行的线程对象,也就是this引用指向的对象。
多线程安全问题
当多条语句在操作多个线程共享数据时,一个线程对多条语句执行了一部分,还没执行完,另一个线程参与进来执行,会导致共享数据的错误。
解决方法:对多条操作共享数据语句,只能让一个线程执行完,在执行过程中,其他线程不可以参与执行。
java对多线程安全问题的专业解决方法就是同步synchronized,具体表现形式有同步代码块和同步函数。
同步代码块
synchronized
- {
- }
同步函数:将synchronized作为修饰符放在函数定义上,函数返回值类型前面。
同步的原理
同步代码块对象如同锁,持有锁的线程可以在同步语句中执行;没有持有锁的线程即使获得了CPU执行权,也进不去,无法执行同步代码。
同步函数使用的锁是this对象;静态同步函数使用的锁是该函数所在类对应的类字节码文件对象,即类名.class,该对象的类型是Class。
synchronized修饰符不属于方法签名的一部分,当子类覆盖父类方法时,synchronized修饰符不会被继承,因此接口中方法不能被声明为synchronized,同样,构造函数也不能被声明为synchronized。
线程进入同步代码块或同步函数前先判断锁标志位,若判断结果为真,则进入同步代码块或同步函数后,修改锁标志位为假,线程退出后,再恢复锁标志位为真。
/*
- */
class Runnable{ - tick=;
- Object obj= Object();
- run(){
- (){
- (obj){
- (tick>){
- {Thread.sleep();}(Exception e){e.printStackTrace();}
- System.out.println(Thread.currentThread().getName()+
public TicketDemo{
- main(String[] args){
- Ticket t= Ticket();
- Thread(t);
- Thread t2= Thread(t);
- Thread(t);
- Thread t4= Thread(t);
- }
同步的前提
1. 必须要有2个或者2个以上的线程
2. 必须是多个线程使用同一个锁,多个线程可以同时操作同一个锁下的代码。
同步的弊端
1. 线程每次进入同步代码块或同步函数都要判断锁,浪费资源,影响效率
2. 可能出现死锁现象,多发生在一个同步代码块或同步函数中嵌套另一个同步函数或同步代码块,且2个同步上使用不同的锁。即同步中嵌套同步而锁不同就容易引发死锁。
下面是一个很直观的死锁的例子,跟毕老师讲得MyLock的例子原理一样,只是形式上有差别:
class
- say(){
- ) ;
- }
- get(){
- System.out.println() ;
- class
- say(){
- ) ;
- }
- get(){
- System.out.println() ;
- public ThreadDeadLock Runnable{
- Zhangsan zs = Zhangsan() ;
- Lisi ls = Lisi() ;
- flag = ;
- run(){
- (flag){
- (zs){
- zs.say() ;
- {
- Thread.sleep() ;
- (InterruptedException e){
- e.printStackTrace() ;
- (ls){
- {
- (ls){
- ls.say() ;
- {
- Thread.sleep() ;
- (InterruptedException e){
- e.printStackTrace() ;
- (zs){
- main(String args[]){
- ThreadDeadLock() ;
- ThreadDeadLock t2 = ThreadDeadLock() ;
- ;
- t2.flag = ;
- Thread(t1) ;
- Thread thB = Thread(t2) ;
- //双方僵持在这,谁都没法继续运行
线程间通讯
Object类方法wait(),notify(),notifyAll()
线程执行wait()后,就放弃了运行资格,处于冻结状态;线程运行时,内存中会建立一个线程池,冻结状态的线程都存在于线程池中,notify()执行时唤醒的也是线程池中的线程,线程池中有多个线程时唤醒第一个被冻结的线程。
notifyall(), 唤醒线程池中所有线程。
wait(), notify(),notifyall()都用在同步里面,因为这3个函数是对持有锁的线程进行操作,而只有同步才有锁,所以要使用在同步中。
wait(),notify(),notifyall(), 在使用时必须标识它们所操作的线程持有的锁,因为等待和唤醒必须是同一锁下的线程;而锁可以是任意对象,所以这3个方法都是Object类中的方法。
wait和sleep区别:从执行权和锁上来分析这2个方法
wait():可以指定时间也可以不指定时间,不指定时间时,只能由对应的notify()或notifyAll()来唤醒。
sleep():必须指定时间,时间到自动从冻结状态转入运行状态或临时阻塞状态。
wait():线程会释放执行权,并释放锁。
sleep():线程会释放执行权,但是并不释放锁。
单个消费者生产者例子:
class
- String name;
- count=;
- flag=;
- set(String name){
- (flag)
- {wait();}(Exception e){}
- .name=name++count++;
- +.name);
- flag=;
- .notify();
- }
- out(){
- (!flag)
- {wait();}(Exception e){}
- System.out.println(Thread.currentThread().getName()++.name);
- ;
- .notify();
- class Runnable{
- Resource res;
- .res=res;
- run(){
- (){
- res.set();
- class Runnable{
- Resource res;
- Consumer(Resource res){
- .res=res;
- }
- run(){
- (){
- public ProducerConsumerDemo{
- main(String[] args){
- Resource();
- Producer pro= Producer(r);
- Consumer(r);
- Thread t1= Thread(pro);
- Thread(con);
- t1.start();
- //运行结果正常,生产者生产一个商品,紧接着消费者消费一个商品。
但是如果有多个生产者和多个消费者,上面的代码是有问题,比如2个生产者,2个消费者,运行结果就可能出现生产的1个商品生产了一次而被消费了2次,或者连续生产2个商品而只有1个被消费,这是因为此时共有4个线程在操作Resource对象r,
而notify()唤醒的是线程池中第1个wait()的线程,所以生产者执行notify()时,唤醒的线程有可能是另1个生产者线程,这个生产者线程从wait()中醒来后不会再判断flag,而是直接向下运行打印出一个新的商品,这样就出现了连续生产2个商品。
为了避免这种情况,修改代码如下:
class
String name;
- count=;
- flag=;
- set(String name){
- (flag)
- {wait();}(Exception e){}
- .name=name++count++;
- +.name);
- flag=;
- .notifyAll();
- }
- out(){
- (!flag)
- {wait();}(Exception e){}
- System.out.println(Thread.currentThread().getName()++.name);
- ;
- .notifyAll();
- public ProducerConsumerDemo{
- main(String[] args){
- Resource();
- Producer pro= Producer(r);
- Consumer(r);
- Thread t1= Thread(pro);
- Thread(con);
- Thread t3= Thread(pro);
- Thread(con);
- t1.start();
- }
jdk1.5中,提供了多线程的升级解决方案:将同步synchronized替换为显式的Lock操作,将Object类中的wait(),
notify(),notifyAll()替换成了Condition对象,该对象可以通过Lock锁对象获取;
一个Lock对象上可以绑定多个Condition对象,这样实现了本方线程只唤醒对方线程,而jdk1.5之前,一个同步只能有一个锁,不同的同步只能用锁来区分,且锁嵌套时容易死锁。
class
String name;
- count=;
- flag=;
- Lock lock = ReentrantLock();
- Condition condition_pro=lock.newCondition();
- Condition condition_con=lock.newCondition();
- set(String name){
- lock.lock();
- {
- (flag)
- .name=name++count++;
- +.name);
- flag=;
- }
- {
- lock.unlock();
- out(){
- lock.lock();
- {
- (!flag)
- System.out.println(Thread.currentThread().getName()++.name);
- ;
- condition_pro.signqlAll();
- {
- }
线程通信的其他几个常用方法:
终止线程
jdk1.5起,stop()方法(非静态)已过时,不能再使用(否则会报错),终止线程的唯一方法是run()方法结束。
开启多线程运行时,运行代码通过是循环结构,只要控制住循环,就可以让run()方法结束。
中断线程
interrupt()方法,如果线程在调用Object类的
wait()、wait(long) 或wait(long,int) 方法,或者该类的
join()、join(long)、join(long,int)、sleep(long) 或sleep(long,int)
方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。
线程的中断状态即冻结状态,interrupt()是将处于冻结状态的线程强制地恢复到运行状态。
守护线程
setDaemon(), 将线程设置为守护线程,当正在运行的所有线程都是守护线程时,jvm自动退出。意思差不多是:前台线程(如main线程)结束后,后台线程(如t1,t2)也自动结束。
setDaemon()方法必须在启动线程前调用。下面是interrupt()和setDeamon()方法的一个示例。
class Runnable{
- flag=;
- run(){
- (flag){
- {
- wait();
- (InterruptedException e){
- );
- flag=;
- );
- changeFlag(){
- flag=;
- public StopTreadDemo {
- main(String[] args) {
- StopThread();
- Thread t1= Thread(st);
- Thread(st);
- t1.start();
- num=;
- (){
- (num++==){
- t1.interrupt();
- ;
- +num);
- );
- }
join()方法
当A线程执行到了B线程的join()方法时,A就放弃运行资格,处于冻结等待状态,等B线程执行完,A才恢复运行资格;如果B线程执行过程中挂掉,那需要用interrupt()方法来清理A线程的冻结状态;join()可以用来临时加入线程执行。
toString()方法
返回线程名称、优先级和线程组字符串。
默认情况下,哪个线程启动了线程t1,
t1就属于哪个线程组,也可创建新的ThreadGroup对象;所有方法,包括main(),线程优先级默认是5;Thread.MAX_PRORITY为10,Thread.MIN_PROTITY为1,NOR_PRORITY为5.
yield()方法
暂时释放执行资格,稍微减缓线程切换的频率,让多个线程得到运行资格的机会均等一些。
java多线程与线程间通信的更多相关文章
- Java多线程基础——线程间通信
在使用多线程的时候,经常需要多个线程进行协作来完成一件事情.在前面两章分析了Java多线程的基本使用以及利用synchronized来实现多个线程同步调用方法或者执行代码块.但上面两章的内容涉及到的例 ...
- Java多线程:线程间通信之volatile与sychronized
由前文Java内存模型我们熟悉了Java的内存工作模式和线程间的交互规范,本篇从应用层面讲解Java线程间通信. Java为线程间通信提供了三个相关的关键字volatile, synchronized ...
- Java多线程:线程间通信之Lock
Java 5 之后,Java在内置关键字sychronized的基础上又增加了一个新的处理锁的方式,Lock类. 由于在Java线程间通信:volatile与sychronized中,我们已经详细的了 ...
- Java——多线程之线程间通信
Java多线系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多线 ...
- java多线程:线程间通信——生产者消费者模型
一.背景 && 定义 多线程环境下,只要有并发问题,就要保证数据的安全性,一般指的是通过 synchronized 来进行同步. 另一个问题是,多个线程之间如何协作呢? 我们看一个仓库 ...
- Java多线程之线程的通信
Java多线程之线程的通信 在总结多线程通信前先介绍一个概念:锁池.线程因为未拿到锁标记而发生的阻塞不同于前面五个基本状态中的阻塞,称为锁池.每个对象都有自己的锁池的空间,用于放置等待运行的线程.这些 ...
- 0038 Java学习笔记-多线程-传统线程间通信、Condition、阻塞队列、《疯狂Java讲义 第三版》进程间通信示例代码存在的一个问题
调用同步锁的wait().notify().notifyAll()进行线程通信 看这个经典的存取款问题,要求两个线程存款,两个线程取款,账户里有余额的时候只能取款,没余额的时候只能存款,存取款金额相同 ...
- 06_Java多线程、线程间通信
1. 线程的概念 1.1多进程与多线程 进程:一个正在执行的程序.每个进程执行都有一个执行顺序,该顺序是一个执行路径,或叫一个控制单元. 一个进程至少有一个线程. 线程:就是进程中的一个独立 ...
- JAVA多线程之线程间的通信方式
(转发) 收藏 记 周日,北京的天阳光明媚,9月,北京的秋格外肃穆透彻,望望窗外的湛蓝的天,心似透过栏杆,沐浴在这透亮清澈的蓝天里,那朵朵白云如同一朵棉絮,心意畅想....思绪外扬, 鱼和熊掌不可兼得 ...
随机推荐
- can总线的示波器检测方法
stm32的can总线是在APB1上的,stm32f10x的主频是72Mhz,can外设时钟是36Mhz,stm32f2xx的主频是120Mhz,can外设时钟是30Mhz... STM32 APB1 ...
- JDK源码分析(4)HashSet
JDK版本 HashSet简介 HashSet特点 非线程安全 允许null值 添加值得时候会先获取对象的hashCode方法,如果hashCode 方法返回的值一致,则再调用equals方法判断是否 ...
- 从C,C++,JAVA和C#来看String库的发展(二)---JAVA和C#篇
http://www.cnblogs.com/wenjiang/p/3272859.html 终于要进入面向对象的世界了,虽然C++也是面向对象,但是它的面向对象程度并不高,因为考虑到要兼容C语言的移 ...
- python备份网站,并删除指定日期文件
#!/usr/bin/python# Filename: backup_ver1.pyimport osimport timeimport datetime# 1. The files and dir ...
- libevent-2.0.so.5 (安装MEMCACHED问题)
今天安装memcache启动服务时出现 error while loading shared libraries: libevent-2.0.so.5: cannot open shared obje ...
- Java——java错误(The Struts dispatcher cannot be found)
这通常是由于使用了struts标签,而没有配置相关联的filter.struts标签只有在http请求通过标签的servlet filter过滤器之后才可用,这些过滤器用来为这些标签初始化struts ...
- W3C规范
连接:https://www.w3cschool.cn/xuexiw3c/xuexiw3c-standards.html W3C 代码标准规范 由 路飞 创建, 最后一次修改 2017-01-03 W ...
- CF875F Royal Questions
传送门 似乎可以按边权排序后二分图匹配 这里给一个复杂度稳定的算法 把一个公主能匹配的两个点连边,然后依次加边,每当加到一个大小为\(n\)的连通块中有\(n\)条边之后,这时形成了基环树,将这些边定 ...
- MySQL中查询行数最多的表并且排序
#切换到schema use information_schema; #查询数据量最大的30张表 并排序 select table_name,table_rows from tables order ...
- [ZJOI2012]波浪弱化版(带技巧的DP)
题面 \(solution:\) 这道确实挺难的,情况特别多,而且考场上都没想到如何设置状态.感觉怎么设状态不能很好的表示当前情况并转移,考后发现是对全排列的构造方式不熟而导致的,而这一题的状态也是根 ...