多线程 001
其实java虚拟机jvm不止一个线程,想想执行java程序的是主线程,但垃圾回收机制肯定有一个子线程负责垃圾回收
如何自定义一个线程呢?
1.创建一个类继承Thread
2.重写Thread的run方法
目的:将自定义的代码存储在run方法中,让线程运行
3.实例化一个线程,调用start方法
start方法的作用:启动线程;调用run方法
直接调用t.run方法不行吗,结果是执行完run方法才执行main的,不会出现上面交替的情况
start和run的区别:start运行(启动)了线程,并执行run方法;而调用run方法,仅仅执行run里的代码
Demo d1 = new Demo();---->创建一个线程
d1.start();---->启动线程
-------------------------------------------------------------------
体会一下。。。。。
电脑CPU在快速切换多个进程,而每个进程里面又在不停地切换多个线程。
run方法用于存储线程运行的代码
------------------------------------------------------------------------------
Thread的五种状态
也可以具体到6种状态,将冻结状态分为:睡眠状态、等待状态
当多个线程运行,同一时间只有一个线程能抢到cpu,此时,其它线程就暂时处于阻塞状态;
调用sleep(时间)方法,可以让一个线程进入冻结状态,当冻结时间结束时,他会进入阻塞状态,抢cpu;
当调用wait方法时,线程进入冻结状态(等待),调用notify方法,线程重新启动;
当一个线程被stop(),线程就消亡了,或者程序结束运行,也消亡;
---------------------------------------------------------------------------------------
获取线程对象和名称
static Thread currentThread(): 获取当前线程的对象(由于是Thread类中的静态方法,所以可以不用实例化直接调用)
getName():获取线程的名称
设置线程名称setName()或者构造函数
-----------------------------------------------------------------------------
获取线程名称currentThread()
Thread.currentThread().getName();
--------------------------------------------------------------------------------
------------------------------------------------------------
设置线程名称
super(name)-->重写父类的方法,父类有一个改名的构造方法
Test t = new Test("线程名称");
-------------------------------------------------------------------------
卖票的小程序
我一共100张票,3个窗口去卖票,结果卖了300张!每张票都被卖了3次。。。- -!
解决办法:将票定义为静态的private int ticket = 100;
但是静态方法生命周期太长了,于是,引出线程的第二种创建方式
----------------------------------------------------------------------
线程的第二种创建方式:实现Runnable接口
创建一个类实现Runnable接口,然后实现run方法
这次,有四个窗口,只卖了100张票,不会出现重复卖票的情况了
首先要说明,Runnable不是一个线程,开的四个线程是通过main函数中的实例化t1\t2\t3\t4实现的
然后,看Ticket类中,输出语句Thread.currentThread().getName()方法,咦,刚开始不是可以省略Thread吗,这次怎么不能了?因为上次类继承了Thread类,所以可以直接调用
而这次Runnable不是线程,但是currentThread方法是静态的,所以可以通过类名调用
----------------------------------------------------------------------
第二种实现步骤:
1.定义类实现Runnable接口
2.覆盖Runnable接口中的run方法
3.创建Thread对象
4.强Runnable接口的子类对象作为参数传到Thread类的构造函数中
5.调用Thread的start方法开启线程,运行Runnable接口子类的run方法
-----------------------------------------------------------------------
继承Thread方法和实现Runnable接口的区别
先说说Runnable是怎么来的
看,第一个student继承Thread,重写了run方法
但是,第二个学生继承了Person类,java只支持单继承,那么我就没有办法在继承Thread类了,
那怎么办呢,java工程师就引入Runnable接口,解决这个问题
我继承了Person类的同时,可以实现Runnable接口,然后实现接口的run方法
然后,我把这个实现Runnable的子类对象传给Thread(Runnable target)
调用start方法就可以了
-----------------------------------------------------------------------------------------------
多线程的安全问题
还是那个买票的例子
想象一下这样的情景:只剩下最后一张票了,当我窗口1判断了if(tick>0),窗口1休眠10毫秒;此时,窗口2开始判断if(tick>0),也休眠10毫秒;窗口3、窗口4都是这样,那么,这四个都符合条件了,等休眠结束后,窗口1取出1号票,此时已经没票了;但是...由于之前窗口2、3、4都符合if语句,那么,他们也会继续执行,结果,就会取出0号票、-1、-2号票,这不就出问题了吗 - -!
错误的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程就参与进来,导致共享数据出现错误。
------------------------------------------------------------
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完。执行过程中,其他线程不可以参与进来。
java的解决方式:同步代码块
synchronized(对象){
需要被同步的代码
}
由于同步代码块需要一个实例对象作为参数,所以我用obj作为参数
看下结果
这个同步代码块是怎么玩的呢:
其实静态代码块就相当于一个开关,默认是开着的,当0线程进去后,它会立马关闭,然后才执行里面的代码;
执行完毕后,同步代码块才重新打开。
结合上面代码,里面有个休眠10毫秒,0线程就处于休眠状态;此时,1、2、3线程就开始执行,但执行到同步代码块时,发现它是关闭的,只好阻塞在那里,等0休眠结束后,执行卖票,然后跳出代码块。1、2、3线程就可以进去了,但是此时,进去后判断ticket已经==0了,不成立了,就卖不出-1号票了。
举个生活实例:火车上的厕所,无人才能进去,进去就上锁,此时第二个人不能进去
----------------------------------------------------------------------------------------------
同步的前提:
1.必须两个或者两个以上的线程
2.必须是多个线程使用同一个锁
3.必须保证同步中只能有一个线程在执行
好处:解决了多线程的安全问题
弊端:多个线程都判断这个锁,较为消耗资源
如何找问题:
1.明确那些代码是多线程运行的代码
2.明确共享数据
3.明确多线程运行中哪些语句是操作共享数据的
---------------------------------------------------------------
同步函数
think:函数也具备封装代码的功能,同步代码块也是封装代码,多出来一条就是同步性,那么,能不能搞个同步函数呢
synchronized用来修饰函数就行了
public synchronized void add(int n){
}
那么,就可以把同步代码块中的数据封装成同步函数了
那么,同步函数到底是谁的锁呢,答案是this的
我定义一个boolean型deal,注意,同步代码块中的synchronized参数是this,而交替执行同步方法和同步代码块,并没有出现安全问题,也就是说,这两个操作的是同一个锁,即,同步函数的是this的锁。
--------------------------------------------------------------
如果同步函数被静态修饰,使用的锁不再是this的了,
因为,静态函数在方法区,不可能定义this,
静态进内存的时候内存中没有本类对象对象,但是有该类的字节码文件.class
所以,静态同步函数的对象时字节码文件对象---->类名.class
------------------------------------------------------------------
同步的问题
死锁
同步代码块中调用同步函数,而同步函数中也调用同步代码块,就可能产生死锁