------- android培训、java培训、期待与您交流! ----------
多线程:
1、 进程:就是正在运行的程序,也就是代表程序所占用的内存区域。
线程:线程代表程序的执行路径,执行单元。
如果一个应用程序的执行只有一个执行路径,就称为单线程程序;
如果一个应用程序的执行有多条窒息那个路径,就称为多线程程序。
例如:迅雷下载,360安全卫士等。
思考:Jvm的启动是多线程的,还是单线程的?
Jvm在启动时,最少启动了main线程和垃圾回收线程。因为如果只启动main线程的话,那么随着程序的执行,可能会引发内存溢出,所以垃圾回收线程必须一直在运行,所以它是一个多线程的。
查API:java.lang.Thread线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。 其实在同一个时刻,只能有一个应用程序在执行。CPU在每一个应用程序间进行着高效的切换。
2、 实现多线程的方法:
实现多线程可以通过继承Thread类和实现Runnable接口。
(1)继承Thread类
1.创建一个类继承Thread类;
2.子类复写父类中的run方法,将线程运行的代码存放在run中;
因为run方法里面封装了多线程要执行的代码.
3.通过调用start方法开启线程而不是调用run方法。
4.常见方法:getName( ),Thread.currentThread( );
(2)实现Runnable接口;
1.创建Runnable类的实现类,子类复写接口中的run方法;
2.通过Thread类创建线程,并将事先了Runnable接口的子类对象作为参数传递给Thread类的构造函数。
3.Thread类对象调用start方法开启线程,因为Runnable类以及其实现类没有start方法。
两种方法区别:
(1)实现Runnable接口避免了单继承的局限性
(2)继承Thread类线程代码存放在Thread子类的run方法中
实现Runnable接口线程代码存放在接口的子类的run方法中;
在定义线程时,建议使用实现Runnable接口,因为它可以避免单继承,能实现该接口的同时还能继承或实现其他类或接口。
思考1:为什么要给Thread类的构造函数传递Runnable的子类对象?
Thread类中有一个Runnable类型的成员变量来接收Runnable的子类对象,并且在Thread方法开启线程是会运行该对象里面的run方法,这样就相等于把需要多线程运行的任务作为参数进行传递了。
思考2:start()和run()方法的区别?
start()方法是Thread类中的方法,它的作用是启动线程;而run( )方法是Runnable接口中的抽象方法,Thread类实现了该接口并复写了该方法,它用于封装需要被多线程执行的代码。
3、线程的几种状态:
新建:new一个Thread对象或者其子类对象就是创建一个线程,当一个线程对象被创建,但是没有开启,这个时候,是对象线程对象开辟了内存空间和初始化数据。
就绪:新建的对象调用start方法,就开启了线程,线程就到了就绪状态。 在这个状态的线程对象,具有执行资格,没有执行权。
运行:当线程对象获取到了CPU的资源。在这个状态的线程对象,既有执行资格,也有执行权。
冻结:运行过程中的线程由于某些原因(比如wait,sleep),释放了执行资格和执行权。当然,他们可以回到运行状态。只不过,不是直接回到运行状态而是先回到就绪状态。
死亡:当线程对象调用的run方法结束,或者直接调用stop方法,就让线程对象死亡,在内存中变成了垃圾。
4、线程安全问题
(1)导致安全问题出现的原因:
A.多个线程访问出现延迟;
B.线程随机性。
注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。
如何判断一个程序是否有线程安全问题?
A:是否有是操作共享的数据;
B:操作数据的语句是否是多条;
C.是否在多线程程序中;
(2)解决方法:对多条操作共享数据的语句进行同步,一个线程在执行过程中其他线程不可以参与进来。
5、同步(synchronized)
(1)格式:
Synchronize(对象){需要同步的代码},它可以解决安全问题的根本原因就在那个对象上,该对象如同锁的功能。
(2)前提:
A.同步需要两个或两个以上的线程;
B.多个线程使用的是同一个锁
(3)弊端:
当线程相当多时,每个线程都会取判断同步上的锁,比较耗资源,降低程序的运行效率。
(4)同步的两种表现形式:
A.同步代码块:
定义在方法内,需要使用同一个锁对象。
注意:
虽然同步代码快的锁可以使任何对象,但是在进行多线程通信使用同步代码快时,必须保证同步代码块锁的唯一性,否则会报错。
同步函数的锁是this,也要保证同步函数的锁的对象和调用wait、notify和notifyAll的对象是同一个对象,也就是都是this锁代表的对象。
格式:
synchronized(对象)
{
需同步的代码;
}
(2)同步函数
定义在方法上用synchronized修饰符修饰。
注:非静态同步函数的锁为this,而静态同步函数的锁是该方法所在的类的字节码文件对象,即类名.class文件
格式:
修饰词 synchronized 返回值类型 函数名(参数列表)
{
需同步的代码;
}
注意:如果对多个程序加锁了,但是还有问题,那么需要考虑多线程的锁对象是不是同一个。
在jdk1.5后,提供了可重入锁对象,用lock锁取代了synchronized。
6、sleep()和wait()的区别:
(1)这两个方法来自不同的类,sleep()来自Thread类,和wait()来自Object类。
(2)sleep是Thread的静态类方法,而wait()是Object类的非静态方法
(3)sleep()释放资源不释放锁,而wait()释放资源释放锁;
(4)使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用,但当一个线程正在使用或者更新同一个共享数据,这样容易导致程序出现错误的结果。
使用等待唤醒机制做一个接力跑的游戏!
6、使用synchronized同步来解决懒汉式单例设计模式的安全问题
7、死锁
两个线程对两个同步对象具有循环依赖时,就会发生死锁。即同步嵌套同步,而锁却不同。
8、wait()、sleep()、notify()、notifyAll()
wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程(一般是最先开始等待的线程),而且不是按优先级。
notityAll():唤醒所有处入等待状态的所有线程.
9、为什么wait()、notify()、notifyAll()这些用来操作线程的方法定义在Object类中?
(1)这些方法只存在于同步中;
(2)使用这些方法时必须要指定所属的锁;
(3)而锁可以是任意对象,所以任意对象调用的方法就定义在Object中。
10、线程间通信(生产者与消费者)
A.问题:数据错乱。
原因分析:因为等待线程被唤醒后没有判断标记!
解决方法:该if语句为while循环语句,要让被唤醒的线程再一次判断标记;
B.问题:用while()语句后,导致程序挂起?
原因分析:因为只用notify,容易出现只唤醒本方线程的情况,导致程序中所有线程等待
解决方案:改notify()为notifyAll(),唤醒对方线程。
10、Lock和Condition
JDK1.5版本提供了显示的锁机制!!以及显示的锁对象上的等待唤醒操作机制。将同步synchonized替换成了显示的Lock操作,将Object中的wait、notify、notifyAll替换成了Condition对象。“它实现提供比synchronized方法和语句可获得的更广泛的锁的操作,此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象”。该对象可以Lock锁进行获取。
Lock的方法摘要:
void lock() 获取锁。
Condition newCondition() 返回绑定到此 Lock 实例的新 Condition 实例。
void unlock() 释放锁。
Condition方法摘要:
void await() 造成当前线程在接到信号或被中断之前一直处于等待状态。
void signal() 唤醒一个等待线程。
void signalAll() 唤醒所有等待线程。
步骤:
通过Lock接口的实现类获取lock对象,然后调用该接口中的方法:Lock lock = new ReentrantLock();
通过lock对象中已经定义好的创建Condition对象的方法获取其对象,然后调用它的await,signal等方法。
Condition condition = lock.newCondition();
如何只唤醒对方线程?
Condition condition_pro = lock.newCondition()
Condition condition_con = lock.newCondition()
11、停止线程:
stop方法已经过时,如何停止线程?
停止线程的方法只有一种,就是run方法结束。如何让run方法结束呢?
开启多线程运行,运行代码通常是循环体,只要控制住循环,就可以让run方法结束,也就是结束线程。
特殊情况:当线程属于冻结状态,就不会读取循环控制标记,则线程就不会结束。
为解决该特殊情况,可引入Thread类中的Interrupt方法结束线程的冻结状态;
当没有指定的方式让冻结线程恢复到运行状态时,需要对冻结进行清除,强制让线程恢复到运行状态
12、守护线程(后台线程)
setDaemon(boolean on):将该线程标记为守护线程或者用户线程。当主线程结束,守护线程自动结束,比如圣斗士星矢里面的守护雅典娜,在多线程里面主线程就是雅典娜,守护线程就是圣斗士,主线程结束了,守护线程则自动结束。
当正在运行的线程都是守护线程时,java虚拟机jvm退出;所以该方法必须在启动线程前调用;
守护线程的特点:
守护线程开启后和前台线程共同抢夺cpu的执行权,开启、运行两者都没区别,
但结束时有区别,当所有前台线程都结束后,守护线程会自动结束。
13、多线程join方法:
void join() 等待该线程终止。
void join(long millis) 等待该线程终止的时间最长为 millis 毫秒。
throws InterruptedException
特点:当A线程执行到B线程的join方法时,A就会等待B线程都执行完,A才会执行
作用: join可以用来临时加入线程执行;
示例: main();
t1.start();
t2.start();
t1.join();//读到该句时,主线程把cpu执行权释放掉(注意不是转给t1线程),并等待t1执行完毕.
t2.start();
然后t2与主线程抢夺cpu的执行权。
又如:
main();
t1.start();
t2.start();
t1.join();此时,主线程释放执行权,等t1执行完毕才可去抢夺cpu的执行权。此时,t1与t2在抢夺cpu执行权,而不是只有t1在执行!!
也即:主线程遇到谁的join,就等待谁的线程执行完毕。
14、多线程优先级:yield()方法
yield():暂停当前正在执行的线程对象,并执行其他线程
setPriority(int newPriority):更改线程优先级
int getPriority() 返回线程的优先级。
String toString() 返回该线程的字符串表示形式,包括线程名称、优先级和线程组
(1)MAX_PRIORITY:最高优先级(10级)
(1)Min_PRIORITY:最低优先级(1级)
(1)Morm_PRIORITY:默认优先级(5级)
15、在静态方法上使用同步时会发生什么事?
同步静态方法时会获取该类的“Class”对象,所以当一个线程进入同步的静态方法中时,
线程监视器获取类本身的对象锁,其它线程不能进入这个类的任何静态同步方法。
它不像实例方法,因为多个线程可以同时访问不同实例同步实例方法。