CPU同一个时刻执行一个程序,一个进程中可能出现多条执行路径,也就是线程。
线程是进程的控制单元,是进程中真正执行的部分。线程控制进程的执行。一个进程至少有一个线程负责程序的执行,而且这个线程运行的代码存在于main中,称为主线程。
其实jvm启动不止一个线程,还有负责垃圾回收机制的线程,是多线程的。
Thread类:程序中的执行线程。允许程序并发运行多个线程
创建线程:1.继承Thread类,覆盖run方法,创立新对象等于创建好一个线程,启动线程则调用start方法(启动线程,调用run方法)
在一个进程中CPU也要进行线程的切换,抢到CPU执行权就先执行。运行结果每一次都不同,因为多个线程都获取cpu的执行权,在某一时刻只能有一个程序在运行(单核)。
run方法定义了一个功能,用于储存线程要运行的代码。虚拟机定义主线程要调用的代码在main方法中。
复写run的目的是:将自定义代码存储在run方法中,让线程运行。而start的功能是启动线程并调用run。仅仅使用run是封装线程执行的代码,即使线程创建了却没有运行线程。
static Thread currentThread():获取当前线程对象
getName():获取线程名称
线程名称:setName或用构造函数传入
局部变量在每一个线程内存中都有独立的一份。
多线程例子:售票,多窗口卖票
runnable接口:创建线程的第二种方式
1.定义类实现runnable接口
2.覆盖runnable中的run方法:线程要运行的代码
3.通过Thread类建立线程对象
4.将runnable接口的子类对象作为Thread的参数传递其构造函数(因为run方法所属的对象是Runnable类,让线程指定对象的run方法,就必须明确run方法的对象)
5.调用Thread类的start方法并开启线程、调用runnable接口子类的run方法
实现方式和继承方式的区别:实现方式使多线程成为扩展功能,避免了单继承局限性。而继承之后该类就不能成为其他类的子类。继承thread:线程代码存放在Thread的子类中,而runnable代码存放在接口的子类的run方法中。
安全隐患:多线程可能会跳过判断而出现不合理的结果,需要停一下。可能测试不出问题,但是实际运行出现问题。
try{Thread.sleep(10);} catch(Exception e){ }
注意:实现接口的方法的异常不能throw,只能try。
问题原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行一部分,但是另一个线程参与进来执行,导致共享数据的错误。
解决办法:对多条操作共享数据的语句,只能一个线程执行完,再执行过程中其他线程。
同步代码块:
synchronized(对象)
{需要被同步的代码,也就是操作共享数据的语句}
锁:对象有两个标志位0,1,当0线程获得执行权就判断标志位。如果是1,就进入代码,将1变成0,执行代码块。执行完后又把标志位改1。对象如同锁,持有锁的线程可以在同步中执行。没有锁的线程即使获取cpu执行权也进不去,因为没有锁。
同步的前提:必须要有多个线程;必须的多个线程使用同一个锁;必须保证同步中只能有一个线程进行。
好处:解决多线程安全问题;弊端:判断消耗资源
找问题:明确哪些代码是多线程运行代码;明确共享数据;明确多线程中哪些语句操作共享数据;
同步函数:修饰符synchronized修饰函数起到同步效果
可以将有需要的代码变成函数封装成同步函数。
函数需要被对象调用,同步函数的锁是this
多线程中还有主线程,可能会启动其他线程并操作共享数据,从而影响了一开始启动的线程。可以让主线程先sleep一下。实例中使用了两种不同的同步:同步函数和同步代码块。因为两个方式的锁不一致,所以出现了错误。
静态同步函数:静态同步函数所用的锁不是this,类要先封装成对象(字节码文件对象)该对象的类型是class,锁是该方法所在类对应的字节码文件对象。
死锁
同步中嵌套同步,144,一个线程同步中拿着A对象想要调用B对象,而另一个线程同步中拿着B对象想要调用A对象