一些概念:
|--进程:就是一个正在执行中的程序;
|--线程:就是进程中的一个独立的控制单元;线程在控制着进程的执行;
|--一个进程中至少有一个线程;
|--java虚拟机启动的时候会有一个进程java,exe 该进程中至少有一个线程负责java程序的执行,而且这个线程运行的代码存在与main方法中,该线程称为主线程;
|--jvm启动的时候不止一个线程,还有一个垃圾回收的线程,另一个是主线程同时子啊执行;
|--多线程的出现:可以是多部分代码同事进行;-->提高了效率;多条路径同时执行;
创建线程:
|--自己自定义的去创建一些线程,让某些代码可以同时执行
|--如何在自定义一个线程呢?
|--Thread:线程 是程序中的执行线程,Java 虚拟机允许应用程序并发地运行多个执行线程。
|--创建新执行线程有两种方法。一种方法是将类声明为
Thread
的子类。该子类应重写Thread
类的run
方法。接下来可以分配并启动该子类的实例。-->第一种方式就是继承Thread类,还要覆盖run方法;|--步骤:
定义类继承Thread;
复写Thread类中的run方法;目的是就将自定义的代码存储在run方法,让线程运行;
调用线程的start方法;-->该方法有两个作用:启动线程,然后调用run方法;
|--d.run:仅仅是对象调用方法,而线程创建了,但是并没有运行 -->如果是d.run()的话,那么运行的是run里面覆盖的方法,然后在运行主函数里面的方法;
|--d.start:开启线程并执行该线程的run方法;如果是d.start()的话,那么就是多线程的运行状态;
|--另一种方法是声明实现
Runnable
接口的类。该类然后实现run
方法。然后可以分配该类的实例,在创建Thread
时作为一个参数来传递并启动。|--发现每一次运行的结果都是不同的,cpu执行到谁谁就运行,在某一个时刻只能有一个程序运行;cpu做着快速的切换,已达到同时运行的效果;
|--多线程的特性:随机性-->谁抢到谁就执行,执行多长时间,是cpu说了算
为什么要覆盖run方法呢?
|--Thread类是用来描述线程的;
|--该类就定义了一个功能,用于存储线程要执行的代码,该存储功能就是run方法;也就是说Thread类中的run方法是用来存储线程要运行的代码;
线程的状态
|--被创建也是一种状态
|--运行状态
|--冻结状态
|--消亡状态
获取线程名称和对象
|--线程也有自己的名称:线程都有自己默认的名称:Thread-编号,该编号是从0开始
|--currentThread():获取当前线程对象。该方法是静态的,直接用类名调用;
卖票程序
|--卖票是要多个窗口卖票,要同时出票,多个窗口要同时卖票;
|--
创建线程的第二种方法:
|--接口Runnable:可运行的;里面就只有一个方法;
|--实现Runnable接口;
步骤:
1、定义类实现Runnable接口
2、覆盖Runnable接口中的run方法;-->将线程要运行的代码存放在该run方法中
3、通过Thread类建立线程对象;
4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法;
|--原因:自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定指定对象的run方法;
5、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
实现方式和继承方式有什么区别
|--由于java只支持单继承;当你有了自己的父类,但是这时候就不能再继承Thread了,但是你的里面有些代码是多线程锁执行的,那么这个时候,java工程师就对外提供了一个规则,你得符合我的规则我才能帮你办,那么学生就实现了一个接口,
|--通过以上的分析就是学生是人当中的一种,学生像Runnable,学生既继承了人这个类,有实现了Runnable,也就是实现了多线程;对外提供了功能性的扩展
|--实现方式和继承方式有什么区别:实现的方式避免了单继承的局限性,在定义线程时,建议使用实现方式;
|--还有一个就是线程要运行的代码存放的位子是不同的:继承Thread类:线程代码存放代码Thread子类run方法中
实现Runnable接口,线程代码存放在接口子类的run方法中
多线程的安全问题
|--多线程的安全隐患:
|--问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来,导致了共享数据的错误;
|--解决的办法:对多条操作共享数据的语句,只能让一个线程都执行完,再执行过程中其他的线程不可以参与执行;
|--java工程师提供了专业的解决方式:就是同步代码块
|--哪些代码需要同步,就看哪些代码是共享数据;
|--同步的经典例子:火车上的卫生间;
|--对象如同锁,持有锁的线程,可以在同步中执行,没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有锁;
|--同步的前提:必须要有两个或者两个以上的线程,才能要用到同步;
必须是多个线程使用同一个锁;要保证同步中只能有一个线程在运行;要保证你进去了,我就不能进,
|--同步的好处:解决了多线程的安全问题;
|--同步的弊端:多个线程需要判断锁,较为消耗资源;
同步函数用的锁
|--同步函数用的锁是this锁;
静态同步函数的锁
|--同步函数被静态修饰后,使用的锁是哪一个呢?通过验证,发现不是this了;因为静态方法中也不可以是this;
|--静态进内存的时候是没有对象的,是有类的,要封装成class对象,也就是字节码文件对象;
|--静态进内存时,是没有本类对象的,有该类对应的字节码文件对象;类名.class 该对象的类型是Class
|--同步函数被静态修饰后,用的锁是该类所属的字节码文件对象;也就是类名.class;
多线程访问懒汉式
|--懒汉式在进行多线程访问时,会出现安全隐患;
|--如果是同步函数的话,懒汉式如果加了同步会比较低效,每次都要判断锁,
面试懒汉式
面试的时候要问的;问:懒汉式和饿汉式有什么不同?
答:懒汉式的特点在于实例的延迟加载。
问:懒汉式的延迟加载有没有什么问题?
答:懒汉式如果在多线程的访问时会出现安全隐患问题,
问:怎么解决?
答:可以加同步来解决,而加同步的方式,用同步函数和同步代码块都是可以的,但是同步加在同步函数上面会稍微有些低效,这时候用双重判断的形式,可以提高效率;
问:加同步的时候用的锁是哪一个?
答:该类所属的字节码文件对象;
请写一个延迟加载的单例设计示例;
同步还有一个小小的弊端
同步出现以后会出现的一个现象:
|--死锁现象:就是你持有一个锁,我也持有一个锁,我要到你的你面去运行,就要问你要锁,而你也要我的里面来运行,你也要问我要锁,我不放我的锁要进你的里面去,你不放你的锁要进我的里面来,谁都不放,就出现了死锁的现象,
|--中国人用筷子吃饭的例子:你不放你的筷子,我不放我的筷子,咱们谁也不要吃饭;
|--死锁的出现通常是同步中嵌套同步,而锁却是不同的;-->同步函数里面有同步代码快,同步代码快里面有同步函数;她们用的锁是不相同的;
|--一个线程拿个a锁想要到b里面来,而b锁想要到a里面来;两者都相持不下,就是进不来的;
面试的时候要求写一个死锁的程序:就写这个locka里面有lockb,lockb里面有locka
多线程间通信
wait()与金额sleep()有什么区别?
多线程的优先级wait():释放资源,释放锁
sleep():释放资源,不释放锁;
线程间通讯:其实就是多个线程在操作同一个资源;但是操作的动作不同;因此要放到两个不同的run方法中;
|--to String();覆盖了Object里面的toString的方法,建立了自己的方法,
|--返回的是 该线程的字符串的表现形式,包括线程名称,优先级和线程组;
|--优先级代表着强资源的频率
|--所有的线程,包括主线程的优先级默认都是5;
|--优先级最高是10;
|--最高优先级10,最低优先级1,线程的默认优先级5;
线程的生命周期:
|--有5个状态:被创建,运行,冻结,消亡,阻塞状态或者是临时状态
|--具备执行资格但是没有执行权:临时状态;
|--放弃执行资格:冻结状态
|--既有执行资格,又有执行权的是运行状态