39 Java中的多线程.docx
- 文档编号:24450869
- 上传时间:2023-05-27
- 格式:DOCX
- 页数:14
- 大小:153.91KB
39 Java中的多线程.docx
《39 Java中的多线程.docx》由会员分享,可在线阅读,更多相关《39 Java中的多线程.docx(14页珍藏版)》请在冰豆网上搜索。
39Java中的多线程
Java中的多线程
1.
(1)进程是正在进行的程序,用来开辟内存空间的;
(2)线程:
就是进程中一个负责程序执行的控制单元(执行路径)。
2.
(1)一个进程中至少有一个线程。
(2)一个进程中允许有多个执行路径,即多线程。
(3)开启多个线程是为了同时运行多部分代码;
(4)任务:
每个线程都具有自己运行的内容,这个内容就可以称为线程要执行的任务。
(5)多线程的好处:
解决了多部分代码同时运行的目的;
(6)多线程的弊端:
多线程过多,使效率降低。
因为程序的执行都是在CPU中做着快速的切换完成的,且这个切换是随机的。
(7)JVM启动时就已经启动了至少两个线程:
1主线程:
执行main方法的线程:
该线程的任务代码都定义在main方法里8;
2负责垃圾回收的线程。
(8)创建一个线程的目的是:
为了开启一条执行路径,能够达到执行此代码和其他代码的同时运行,而运行此代码就是这个执行路径的任务。
(9)JVM创建的主线程的任务都定义在了主方法中;而自定义的线程的任务在哪里呢?
为什么要重写run方法呢?
Thread用于描述线程,线程是需要任务的,所以Thread类也对任务进行描述;这个任务就是通过Thread类中的run方法来体现,也就是说,run方法就是封装自定义线程运行任务的方法。
run方法中定义的就是线程需要执行的任务代码。
(10)开启线程是为了执行指定的代码,所以只有继承Thread类,并重写run方法,将运行的代码定义在run方法中即可。
3.创建线程的两个方法:
∙
(1)方法一:
继承Thread类。
∙创建步骤:
①定义一个类并继承Thread类,②重写Thread类中的run方法;③直接创建此类的一个对象,即创建线程成功,④调用此对象的start方法,启动此线程。
∙(调用run与调用start方法有什么区别?
)
∙run方法只是个普通的方法,调用run方法并不能启动此线程,只有调用start方法才会启动一个线程,启动后自动执行run方法中的任务。
∙classPrimeThreadextendsThread{
∙longminPrime;
∙PrimeThread(longminPrime){
∙this.minPrime=minPrime;
∙}
∙
∙publicvoidrun(){
∙//computeprimeslargerthanminPrime
∙ . . .
∙}
∙}
∙启动:
PrimeThreadp=newPrimeThread(143);
∙p.start();
(2)方法二:
①接口Runnable:
里面只有一个抽象方法run()方法,实现此接口就是实现任务的封装,然后通过生成对象,实现任务的对象封装,作为参数传递给Thread,创建Thread对象(线程对象)。
Thread类也是实现了Runnable接口的。
②如果一个类继承了其他的父类,此时就不能继承Thread了,所以只能继承接口了,此时就可以继承Runnable接口。
3创建线程:
步骤:
i.定义类并实现接口Runnable;
ii.重写接口中的run方法,将线程的任务代码封装到run方法中去;
iii.对新创建的类new出一个对象,并作为Thread类构造方法的参数传递进去;目的是创建一个Thread类的线程对象;为什么?
因为线程的任务都封装在创建的类(此类继承接口Runnable)中的run方法中,所以要在创建线程对象(Thread对象)时,必须明确此线程要执行的任务(run方法),即需要把创建类的对象作为参数传进去。
iiii.调用线程对象(Thread类的对象)的start方法启动线程。
注意:
Thread构造方法中如果参数是个类的对象,则会调用传进去的对象的run方法,实现方式如下面代码:
classPrimeRunimplementsRunnable{
∙longminPrime;
∙PrimeRun(longminPrime){
∙this.minPrime=minPrime;
∙}
∙
∙publicvoidrun(){
∙//computeprimeslargerthanminPrime
∙ . . .
∙}
∙}
∙启动线程:
PrimeRunp=newPrimeRun(143);
∙newThread(p).start();
解析:
Thread的有多个构造方法,其中一个是
Thread(Runnable target)
∙AllocatesanewThreadobject.
4.两个方法的区别:
(1)第一个是继承类Thread,重写Thread类中的run方法,第二个是实现接口Runnable,实现接口的同时可以继承其他的一个类;
(2)实现Runnable接口的方法有好处:
①将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想,就是把线程的任务封装成对象;②避免了Java单继承的局限性;
因此,利用实现Runnable接口的方法创建线程比较常用。
5.对于一个线程,不能多次启动start;特别是当线程结束后不能再次启动。
否则会抛出异常Throws:
IllegalThreadStateException-ifthethreadwasalreadystarted.
Itisneverlegaltostartathreadmorethanonce.Inparticular,athreadmaynotberestartedonceithascompletedexecution.
6.抛出异常要看四部分信息:
①异常所属的线程,②异常的名称,③异常的提示信息,④异常发生的位置。
7.一个线程开启多次产生异常,这个异常属于多次开启语句所在的线程,如线面这个是在main方法中产生异常IllegalThreadStateException:
publicstaticvoidmain(String[]args){
Tickett1=newTicket();
Tickett2=newTicket();
Tickett3=newTicket();
Tickett4=newTicket();
t1.start();
t2.start();
t3.start();
t3.start();//此处产生异常,使main线程停止,后面的语句不在进行,即线程t4便不会启动,前面三个进行仍然照常进行,只不过是main进程停止,并不影响t3进程。
t4.start();//main进程异常,此语句不会执行
}
8.买票问题:
实现多个窗口同时售票
(1)利用继承Thread类的子类创建多个线程对象,把创建的子类中的票数num定义为static,则多个对象就操作共享的这一个变量了,解决了买票问题;实例见代码。
publicclassSaleTickets{
publicstaticvoidmain(String[]args){
Tickett1=newTicket();
Tickett2=newTicket();
Tickett3=newTicket();
Tickett4=newTicket();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
classTicketextendsThread{
privatestaticintnum=100;
publicvoidrun(){
while(num>0){
System.out.print(num--+""+Thread.currentThread().getName()+"");
}
}
}
(2)利用实现接口Runnable的方法:
利用一个类去实现接口Runnable,然后利用此类产生一个对象,然后把这个对象作为Thread类的构造方法的参数,由于参数是同一个对象,显然产生的多个线程就是操作同一个对象中的数据。
classTicket22implementsRunnable{
privateintnum=0;
publicvoidrun(){
for(;num<100;){
System.out.println(num+++""+Thread.currentThread().getName());
}
}
}
publicclassTickets{
publicstaticvoidmain(String[]args){
Ticket22t=newTicket22();
Threadt0=newThread(t);
Threadt1=newThread(t);
Threadt2=newThread(t);
Threadt3=newThread(t);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
9.多线程的安全隐患:
原因:
当多个线程是在操作共享的数据,且操作共享数据的线程代码有多条时,当其中一个线程在处理共享数据的多条代码过程中,其他线程参与了运算,就会导致多线程安全问题的出现。
10.上述安全问题的解决方法:
将操作共享数据的多条代码封装起来,当有线程执行这块代码时,其他的线程不准执行这块代码,只有当此线程把这块代码都执行完毕后,其他线程才可以执行这块代码。
在java中利用同步代码块就可以实现上述需求。
同步代码块的定义格式:
Synchronized(对象){需要被同步的代码块}
对象称为同步锁;如同火车上的卫生间,“有人”,“无人”。
作用:
对同步进行监视。
同步的前提:
同步中必须有多个线程使用同一个锁(同步锁),此同步锁(对象)必须定义在run方法之外,否则就是一个线程一把锁了。
11.
(1)同步的好处:
解决了多线程的安全问题;
(2)同步的弊端:
降低了效率,因为同步外的线程都会判断同步锁,是一种没有必要的判断。
12.同步方法:
利用关键字synchronized修饰的方法,称为同步方法。
权限修饰符synchronized方法名(){方法体}
把需要同步的代码块封装成同步方法。
但是此时的同步锁呢?
答:
同步方法使用的同步锁是调用run方法的对象,即this。
13.同步方法与同步代码块的区别:
同步方法的同步锁是固定的this;而同步代码块的同步锁可以任意指定(任意对象),也可以利用this。
在实际开发中,建议使用同步代码块,因为可以任意指定对象作为同步锁,同步方法可以看做是同步代码块的简写形式。
14.静态同步方法的同步锁:
静态方法不能使用this,那静态同步方法的同步锁是什么对象呢?
学习过Object类中有个getClass()方法,可以获取一个Class类的对象(字节码类对象),静态同步方法的同步锁就是默认getClass()返回的Class类的字节码文件对象。
(注意Class是一个类名,首字母大写,class首字母小写表示关键字定义一个类。
)如果同步代码块同步锁定义为this.getClass(),就可以实现与静态同步方法拥有同一个同步锁对象,synchronized(this.getClass()){}相当于Classclazz=this.getClass();synchronized(clazz){}。
另外,对象名.getClass()等同于此类名.class;即Ticket是个类名,则Tickett=newTicket();有Classclazz=t.getClass();等价于Classclazz=Ticket.class;这是获取一个类的字节码文件对象的两个等价方法。
getClass()方法是非静态的。
15.多线程内存解析:
不同的线程有独自的内存区,每个线程有自己独自的路径,相互不影响。
只有当所有的线程都结束了,jvm才会结束。
任何一个线程发生异常与其他的线程没有影响。
16.线程的状态:
(1)当run方法执行完后,线程会自动消亡,当然也可以利用stop方法使线程强制消亡;
(2)sleep(time)方法必须指定时间,时间一到自动会进入运行状态;
(3)wait()方法既可以指定时间,也可以不指定时间,如果不指定时间,则必须由notify()方法唤醒线程,否则一直等待。
(4)CPU的执行资格:
在CPU待处理的队列中排队;
CPU的执行权:
正在被CPU处理。
(5)冻结状态:
释放CPU执行权的同时,同时释放CPU执行资格。
(6)临时阻塞状态:
具备CPU执行资格,但不具有CPU执行权,正在等待获取执行权。
(7)如果划分更细的话,可以把sleep和wait分别化为一种状态。
17.可以通过Thread类中getName()方法获取线程的名称:
Thread–编号(从0开始)。
18.在创建一个类对象时,就已经创建了一个线程,同时就已经给此线程编号了。
没有开启线程之前,便已经对线程编号了。
主线程的名字就是main。
19.Thread类中有个static方法,可以获取当前运行的线程,方法名currentThread();获取Thread.currentThread();。
获取当前运行线程的名字Thread.currentThread().getName();
20.单例模式的多线程问题:
(1)饿汉式:
(单例模式):
不需要同步:
classSigleEhan{
privatefinalstaticSigleEhans=newSigleEhan();
privateSigleEhan(){
}
publicstaticSigleEhangetInstance(){
returns;
}
}
(2)懒汉式:
(延迟加载的单例模式):
(面试的时候经常考到这种情况)
1第一种同步代码块形式
∙classSigleLanhan{//懒汉式(延迟加载单例模式)
∙privatestaticSigleLanhans=null;
∙privateSigleLanhan(){
∙}
∙publicstaticSigleLanhangetInstance(){
∙if(s==null){//这是为了在以后s赋予对象后,直接判断,不需要再次进入同步,从而提高了效率
∙synchronized(SigleLanhan.class){
∙//由于此方法是静态的,因此只能使用类名.class;因为静态方法中不可使用this,不可以使用this.getClass()
∙if(s==null)
∙s=newSigleLanhan();
∙}
∙}
∙returns;
∙}
∙}
②第二种静态同步方法:
classSigleLanhan2{
privatestaticSigleLanhan2s=null;
privateSigleLanhan2(){}
publicstaticsynchronizedSigleLanhan2getInstance(){
if(s==null){
s=newSigleLanhan2();
}
returns;
}
}
问题:
(1)②中的同步方法的同步锁是什么?
答:
Siglelanhan2.class。
类的字节码文件对象。
(2)①和②那个效率会更高些?
答:
①形式会更好,因为只要s赋予对象之后,就不再进行同步锁的判断,不会再进入同步代码块中;然而②同步方法,每次都要进行同步锁的判断,而且如果是同一个同步锁,就会进入同步方法中,使效率降低。
21.多线程死锁示例:
常见情景之一:
同步的嵌套
publicclassDeadlockDemo{
publicstaticvoidmain(String[]args){
Threadst1=newThreads(true);
Threadst2=newThreads(false);
Threadtt1=newThread(t1);
Threadtt2=newThread(t2);
tt1.start();//开启run方法中的true部分
tt2.start();//开启run方法中的false部分
}
}
classThreadsimplementsRunnable{
privatebooleanflag;
Threads(booleanflag){
this.flag=flag;
}
publicvoidrun(){
if(flag){
for(inti=10;i>0;i--)
synchronized(MyLock.locka){
System.out.println("if语句locka"
+Thread.currentThread().getName());
synchronized(MyLock.lockb){
System.out.println("if语句lockb"
+Thread.currentThread().getName());
}
}
}else{
for(intj=10;j>0;j--)
synchronized(MyLock.lockb){
System.out.println("else语句lockb"
+Thread.currentThread().getName());
synchronized(MyLock.locka){
System.out.println("else语句locka"
+Thread.currentThread().getName());
}
}
}
}
}
classMyLock{
publicstaticfinalObjectlocka=newObject();
publicstaticfinalObjectlockb=newObject();
}
解析:
开启了两个子线程(run方法中分别是两个同步的嵌套使用):
对于t1线程而言,首先是A锁的钥匙,然后是B锁的钥匙,;t2线程正好相反,首先是B的钥匙,然后是A的钥匙。
首先,线程t1利用A钥匙打开,紧接着t2利用B钥匙打开,这样T1
拥有A钥匙,需要B钥匙才可进入第二层同步,T2拥有B钥匙需要A钥匙才可以进入第二层同步,就这样,各自都不能得到自己的东西,又不释放已有的东西,产生死锁现象。
22.
1.finalize方法:
是根目录Object类中的方法。
2.System.gc();方法可以启动垃圾回收器。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 39 Java中的多线程 Java 中的 多线程