java学习笔记13

8、线程的创建不代表运行,必须开启了start()才算数。线程的几种状态如下:



线程run()代码执行完可以结束,线程也可以直接调用stop()强制结束掉自己。


线程就像学生,CPU就像老师,甲乙丙三个线程,三个学生,都要问老师问题(等候CPU处理),但是老师一次只能回答一个同学。也即甲乙丙都有执行资格,但是只有某一个具有执行权,如果甲同学去睡觉了(冻结),它不排队问老师问题,老师自然也先不理它,在剩下仍要问题的乙丙中来回切换,也就是说甲已经释放了执行权同时也释放了执行资格。

临时阻塞状态:还没有没执行到,等待被执行的线程就处于这个状态,甲乙丙都要问问题,老师回答甲,那么乙丙都处于临时阻塞状态。想要处于冻结状态,你首先要处于运行状态,这样才能调用sleep方法,变成冻结状态。也就是轮到你问问题时,你说我要睡觉,才能去睡觉。没到你问问题时,没人鸟你,你没经过批准就不能去睡觉。

9、创建线程的第二种方法:实现Runnable接口

如果一个类想成为线程类,而且它已经继承了别的类,由于java中不支持多继承,所以就必须借助接口来帮助这个类实现线程的功能。

如果你有一个类,想让线程去执行你类中的一部分内容,你这个类就要去实现Runnable接口,要扩展其功能,让其具有封装任务的能力run( 要运行的)的能力。


run方法是Runnable接口中的方法,实现Runnable接口后可以覆盖重写,把要执行的代码(如show()方法)写在run()方法中就可以开辟出一个线程来,单独执行这个show()方法。但注意:实现Runnable接口后,new出来的对象并不是线程,只是有可被运行能力(实现了Runnnable接口),但是还是需要一个线程来运行这个对象。Runnable接口定义了接口规则(其中一项规则是可以被线程执行,因为Runnable接口中有run()方法,而线程的run()方法里正是封装的要执行的任务代码),线程就像带USB功能的笔记本,对于实现Runnable接口的子类就像U盘,子类覆盖重写了Runnable接口的run方法后,就相当于要把执行的代码封装成了线程的任务。Thread t  =  new Thread(d)就相当于把U盘插到了笔记本上(对象d实现了Runnable接口,并重写了Runnable接口的run()方法)。




10、至于为什么按照上面的思路执行,还可参见下面的内部代码:如果是子类继承了线程,那么new出一个子类对象时,调用start(),其实是调用的线程(父类)的start()方法,start()里面是r.run(),但是在运行时被子类的run()方法覆盖了,所以执行的还是子类的run(),也就是线程所要执行的任务代码。


下面介绍继承接口时的运行代码:


t.start( )由于没有传进去一个Runnable类型的对象r,所以由run方法里面的if判断句就可以知道,run方法什么都不执行。


11、有时候我们继承线程类就是为了能多线程执行一些任务代码,只是为了多线程的功能而使子类继承Thread变成线程类,有点没必要,这就是第二种方法用Runnable接口实现的好处。只是用了线程的run方法来实现多线程就ok,不需要使类变成线程类。把任务封装成对象,再传给线程。

继承Thread是我们是线程的子类,但任务只是我们对象中的一部分,而实现Runnable则是把任务整个封装成对象(对象就是任务)


继承Thread是子类覆盖Thread类中的run方法,实现Runnable接口,是子类覆盖Runnable接口中的run方法。



12、卖票实例:

异常有4部分信息:1、异常所属的线程。2、异常的名称。3、异常的信息。4、异常的位置。

所以上图代码的运行结果就是每张票都被4个窗口(线程)卖了一次,即每个座位都被卖了4次。有4个num这是不对的。

可以考虑吧num设为静态共享的,这样只有一个num,大家都来修改这个num值,就不会出现上面的问题了。(但是这种方法有局限性,譬如有普通票和动车票多种不同的票,这时把num设为静态就没什么用了,因为num只能代表一种票,如果再来一个静态变量num2来代表第二种票,里面的判断语句也要更改,但是如果用Runnable的接口的方法就会变得很简单。)

下面将新的思路:

用实现Runnable的方法,而不是继承Thread


一个Ticket对象就是一种票(因为对象内部有成员变量num,代表这种票的数目),4个线程执行一个Ticket对象(卖票的任务被封装到了对象中),那么由于堆中只有一个Ticket对象,所以只有一个num,所有线程都是改变的一个num。当然了,堆中还有4个Thread对象。



准确的讲,t1这个变量名字是在栈中,线程对象在堆中,t1变量指向堆中的线程对象。调用start()方法就调用了run()方法,就在栈中开辟出一条执行路径。

卖不同的票只需在new出一个Ticket对象;


线程的安全问题:

当num=1时,由于num>0进入四个线程都过了num>0这句,刚打印完这时刚好切换到了别的线程,每个线程都打印了num值,每一次num--,就有可能num出现0,-1这些值。



sleep语句可能会抛出InterrupedException异常,但是由于Ticket实现的Runnable接口没有抛异常,父类没有抛异常,所以子类也不能抛异常,所以只能try{ } catch{ },注意这里虽然catch了,但是没有处理。catch里面的内容为空。

14、线程安全问题产生的原因


15、在java中用同步代码块就可以解决这个问题



关键字synchronized里面所需的对象随便造一个就ok,例如下面就用了object对象。保证了同步代码块内部的代码在执行时不会被别的线程所切入


关键字synchronized里的对象就相当于一个锁,有线程进去,就锁上变为1,别的线程就进不去,等进去的线程执行完代码后,锁打开为0,别的线程就可以进去执行了,道理类似火车上的卫生间。但是当线程0进去后获得执行权,也不是一直执行代码,CPU仍然会切换到1,2,3线程上去,但是别的线程会判断同步所,里面还有线程没出来,只能干等着继续切换到线程0.


17、同步的前提

new出来的object对象如果放在run方法外面就是成员变量,由于只有一个Ticket对象,所以只有一个object对象成员,相当于就只有一把锁,但是如果object对象下载run方法内部,那就是局部变量,由于每个线程都会执行run方法,就相当于有4个object对象,4把锁,没起到一个时间内只有一个人能上厕所的作用。因为有4个厕所,每个线程各进一个,他们都能进去执行任务代码,就没起到锁的作用(一个时间内只有一个人能上厕所,即执行任务代码)

18、注意唯一的数据,例如银行,锁都是唯一的,所以要写到函数的外面,也就是写成成员变量的形式。下图中的synchronized(new Object())相当于就是每个线程造一把锁。还有Bank b = new Bank();也要写成成员变量,不能写在run函数内部,那样就是2个银行了。线程安全隐患的原因,有多个线程在操作共享数据Bank,add()代码有多行,可能一个线程在执行的时候,另一个切进来,所以要加上同步锁。


 同步代码块有同步锁,有同步的效果,也可以把synchronized关键字写成函数的权限修饰符。这时函数就是同步函数,也就具有了同步效果。
19、同步函数


在前面的卖票例子中也可以修改成同步函数的功能

但是不能直接加载run()函数的外面,否则的话线程0进入run()函数之后,由于里面有一句while(true)所以就算num<=0,线程0也出不来同步函数还是要一直循环判断,别的线程就一直无法进去,就相当于一直只有一个窗口在卖票。


while是不需要同步的,改成下图的形式就ok


19、同步函数也是需要锁的,锁也是对象,和同步代码块类似,不过同步函数的锁是this即当前对象,如果同步代码块用的锁是this,那就可以把同步代码块写成同步函数的形式,比较简洁直观。但平时还是建议用同步代码块,锁灵活。不像同步函数,锁只能是当前对象this。


20、类一加载还没new出来此类对象前,就在堆中生成了字节码类对象,因为类也是对象,这一类就只有一个字节码类对象,前面讲过可以用this.getClass()来得到那个唯一的字节码对象,如果同步函数时静态static的,由于静态函数中没有this,所以静态的同步函数用的锁是字节码对象,所以同步代码块要用的锁也是this.getClass() 


上面划线的两句都是为了得到字节码对象,作用是一样的,下面的好处就在于不用new对象就可以通过调用类的class属性得到字节码对象的引用。


21、单例模式设计的多线程问题

由于多线程会涉及到安全问题,所以在单例模式下也要考虑下这个问题


虽然有多个线程,但是饿汉式由于共享的数据(函数)中只有一句 return s,所以不存在安全问题。而懒汉式则不同,因为共享的数据(函数中有多条语句


由于getInstance()是静态的,所以函数里面没有this,对象要用字节码对象,得到字节码对象有2种方法,但是this.getClass()的方法一没有this,二getClass()是非静态的,所以不能用,必须用Single.class的方式。其次外面也有一句if(s == null )是为了提高效率,一旦某个线程创建了对象,别的线程只需判断一下s==null?不等的话直接return s,而不是再去判断同步锁是否锁着(获得执行权后,锁着也还要等待,等着执行权切换到别的线程,效率不高)

22、死锁示范:

如果线程0拿到locka锁,线程1拿到lockb锁,那线程0就进不去lockb,就等到那里没办法结束,而线程1也因为线程0不释放locka而无法向下执行停在那里,无法释放lockb,所以线程0和线程1就变成了死锁。




22、对象中再有对象,和object放在run()内部和外部的区别:



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值