java 线程中的线程_Java中的多线程(Thread,Runnable)

其次是线程池:

8401d8abbee2b2dbfd6f4f5b9c615ea0.png

1.多线程(原理)

线程和进程各自有什么区别和优劣呢?

进程是资源分配的最小单位,线程是程序执行的最小单位。

进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。

线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。

但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

Thread实现多线程:

eace32fda8e5aa4617b8ca81096eb1b3.png

636d68dd8960effe3e2864877bbeccae.png

只要启用多线程就必须使用thread中的start函数。

7bef4fd2cf36f0ae08309d3556bb7090.png

runnable实现多线程(避免单继承的局限):

runable是接口,可以实现多继承,但是没有start方法,所以无法直接开始线程,但是thread的构造方法中可以把runnable作为参数,所以可以通过构造方法来进行传递。

a85ef0222a7d301dbbefa82fcdfe27d5.png

其中Mythread实现了runnable接口。以后多线程的实现优先考虑runnable。

thread和runnable之间的关系:

查看源码 :

1c869b134bc16dd1293588fce92a59d6.png

发现thread类实现了runnable接口,在之前继承thread类的时候,实际上覆写的还是runnable中的run方法。而执行start之后实际上是调用run方法,一张图表示runnable和thread之间的关系:

3ff3cdfe3dd746c126c77b3fe72529d0.png

在进行thread启动多线程的时候调用的是start方法,而后找到的是run方法。

e1ed3d62be20b722c5319b7d7140b612.png

当通过thread类的构造方法传递了一个runnable的接口对象的时候,那么该接口对象将被thread类中的target属性所保存。在thread中调用start方法时会调用下面的run方法:

78b6698b895f6c3adb30dd1b5e0d7948.png

而这个覆写的run方法会调用runnable接口子类(上面图中的new Mythread对象,这个对象实现了runnable接口)被覆写过的run方法。

当有多个线程时的结构:

743ae96088189b1dc88635d19e2286b5.png

在实际情况下这里的线程对象就可能是各个用户。

模拟多个用户买票的程序:

9a6b903e1544f194793d24bcb9ed47d1.png

内存如下图所示:

257601712c5543dbd39a090745f1be4a.png

Callable实现多线程:

callable调用内存关系图:

23e6d8ba455d2afa89366cec7792929d.png

相关程序演示:

254e5ea36c62c040b1c7319a5fe0b75f.png

runnable和callable的区别:

0880ad3a00be7d58ba6801ef0cca3fa1.png

但是不管用什么,用thread中的start启动线程是不变的定理。

线程的运行:

实际上所有的线程都是通过start开始,但是start仅仅只是代表就绪,真正的开始是run方法,下面给出一张图:

a2e160a07d5eee7d4461ce60de317411.png

面试问题:请问为什么stop方法和suspend方法为什么不推荐使用?

2.线程的操作

线程的命名和取得:

对于程序的开发过程之中,需要通过获得线程来进行一些操作,所以线程的名字至关重要:

5e319e85fb843a1d49f13465996bfc50.png

获取名字的代码操作如下:

728f232d715896fc1ab941bcf20f41e4.png

输出:

f586303e05c89a2bcf1003ee605fb16d.png,如果没有名字,会给一个默认的不重复的名字。

对于名字的不重复,使用到了static关键字:

7eecd24b2a7fe82577ae4cbd0c7f5249.png

其实main函数也是一个线程,查看下面的代码:

fde1feea3a69ee522733e31372949a4f.png

输出main和线程对象,可以发现main也是一个线程,那么进程是什么呢?

每当使用java命令执行程序的时候就表示启动了一个jvm的进程,一台电脑上可以启动若干个jvm进程,jvm进程都会有各自的线程。

47e192ff080a32e63e4b4e091863c257.png

在任何开发之中,主线程可以创建若干子线程,一般开发情况下,主线程负责整体流程,子线程负责处理耗时操作。

13fc1eedb67faeca515edf7cf2ecfd0f.png

线程休眠:

e3fa80d30ffe53fc71f24ed861693940.png

millis是毫秒,nanos是纳秒。

此时产生五个线程对象,5个线程对象执行的方法体相同。

8dbe781623ef1a3b78a5636c1e3d7168.png

执行的时候并不是同时休眠,同时唤醒,中间会有适当的延迟操作。

49ccd1e6039e6d275d955d131c1bc837.png

线程中断:

所有正在执行的线程都是可以被中断的,中断线程必须进行异常处理。

782bbb2fd85d6e6ed9e3afc135ebb243.png

线程强制执行和礼让:

在进行强制执行的时候必须先获得线程对象,然后调用join方法,实现强制执行,强制执行方法中可以写入参数。

线程的礼让方法:yield方法,每次礼让都只会礼让当前当前的资源

e7f5e711cd523076e1061739828e2b51.png

当执行的时候发现有礼让,则礼让当前资源,下次又执行的时候,如果还有礼让则继续礼让当前资源。

线程优先级:

ed944449823d3f34b099739cf357c2bd.png

这些优先级都有对应的常量,分别是10,5,1。但是高优先级的只是有可能先执行,并不是绝对的先执行。其中主线程是中等优先级(5),而默认创建的线程也是中等优先级。

线程的同步和死锁:

线程同步:

问题引入,线程的不同步问题,模拟一个卖票程序,

前一个线程进入run()后开始执行售卖操作,票数减一,操作还没结束,另一个线程也进来了,此时的票数其实已经减过了(负数),后来的线程之后执行到输出语句的时候才知道是负数.

所以,就需要在一个线程进入之后锁住这一方法,等操作结束另一线程才能进入.这一线程操作的时候,其他线程只能等待:

096c9897a152fc08067d4608a9fd4e8c.png

面试题:为什么分布式环境下synchronized失效?如何解决这种情况?(看了上面那篇文章都会了解到)

要实现同步可以由同步代码块和同步方法,其中常用的是同步方法。但是同步会实现性能的下降。

线程死锁和解决方法:

生产者消费者模式:

生产者消费者模式是最基本的,最原始的多线程实现方式。

volatile关键字:

volatile是如何保证可见性的:

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

2)禁止进行指令重排序。

先看一段代码,假如线程1先执行,线程2后执行:

//线程1

boolean stop = false;

while(!stop){

doSomething();

}

//线程2

stop = true;

这段代码是很典型的一段代码,很多人在中断线程时可能都会采用这种标记办法。但是事实上,这段代码会完全运行正确么?即一定会将线程中断么?不一定,也许在大多数时候,这个代码能够把线程中断,但是也有可能会导致无法中断线程(虽然这个可能性很小,但是只要一旦发生这种情况就会造成死循环了)。

下面解释一下这段代码为何有可能导致无法中断线程。在前面已经解释过,每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。

那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。

但是用volatile修饰之后就变得不一样了:

使用volatile关键字会强制将修改的值立即写入主存;

使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);

由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。

那么在线程2修改stop值时(当然这里包括2个操作,修改线程2工作内存中的值,然后将修改后的值写入内存),会使得线程1的工作内存中缓存变量stop的缓存行无效,然后线程1读取时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。

那么线程1读取到的就是最新的正确的值。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值