多个线程是共享内存的,所以一个线程完全有可能破坏另一个线程使用的变量和数据结构 ,
所以带来了安全和生存周期的问题;
run()方法结束,线程就结束;
如果你的程序中还有一个线程还没有结束,那么整个程序就不会结束,
main执行完了只是main线程执行完了,子线程对于main来说不是主函数与子函数的关系。
从线程返回信息
正确结果的获得依赖于不同线程之间的相对速度,这就是竞争条件,一个错误的做法;
轮询是一个基本的方法:直到要求副线程返回的数据有了符合条件的值,处理线程才去处理;
回掉是一个改进的方法:副线程通知主线程:我已经将所需要返回的数据返回了;
很显然,轮询总是招人烦,一个老师老是问每个学生:你他吗的作业写完了没有?
回掉就很好接受,当一个学生完成作业的时候去主动告诉老师:您可以批阅我的作业了;
回掉的一个思想就是:主线程中的方法不一定要主线程来调用,回掉就是副线程调用主线程的一个
方法完成处理,所以回掉的方法甚至不需要老师批阅,只是学生借用了老师的红色钢笔自己批阅
完事;
同步
同步仅仅是对象上的局部锁定,如果其他方法比较隐蔽地使用对象,而不企图在此对象上同步,则也可以使用同步对象;也就是说Java没有提供方法来阻止所有其他线程使用一个
共享资源,它只能阻止在同一对象上同步的其他线程使用这个共享资源;这就是我们以前得出的结论:“如果你的多个线程使用了不同的同步块或者同步方法来达到临界区的
同步,那你必须保证这些同步块或同步方法都使用了同一个对象的监视器做
加锁用,不然的话,还是不同步----当然如果多个线程是同一个Thread类的实例的话,一般情况下,他们都使用同一个同步块,所以也就用了同一个对象做为监视器,是同步的;而如果并不是这样的情况,比如多个线程是完全不同的类的实例,那除非他们都用了同一个监视器来同步,不然是不同步的;重要的是这些线程共享了什么资源,而不是这些线程是什么类。
如果多个线程都有一个指向同一个对象的引用,则会产生问题,因为一个线程在另一个线程写数据的过程中可能打断它的写过程
对于在哪个对象上同步有两个很好的选择:
1, 在方法所使用到的对象上同步。
2, 在方法所在的对象上同步。
通常我们使用第二个方法,即在对象自身上使整个方法同步。但我说不出以上所说的两个选择有什么区别;
而同步方法自动采用它所在的对象本身来同步,所以同步方法实际上是第二个选择的一个快捷方式;
有一下三个原因使得我们对同步方法并不看好:
1, 它会在许多虚拟机上造成性能降低;
2, 它提高了死锁的可能;
3, 通常不是对象本身需要保护以避免同时改动,而且在方法的类的实例上同步可能不是对象本身需要保护的对象;
需要总结的是:我们只需要观看可能拥有需要被保护以免被同时改动的同一个对象的引用所能出现的线程是否用了同一个对象监听器来同步了,而不需要观看同一个类的不同对象的引用所能出现的线程是否用了同一个对象监听器; 所以我们通常把被保护的对象设置为私有成员;
同步的替代方案
注意:本地变量没有同步问题,因为每个线程有自己单独的本地变量集合;
基本类型的方法参数也是线程安全的;
引用参数不是线程安全的;
String参数是安全的,因为他们是不变的;
死锁
当两个线程都需要对相同的资源集合进行独有访问,但每个线程都占有这些资源的一个不同的子集时,就会产生死锁;一个俗一点的解释是:两个线程互相等待对方
锁定的资源,而在未得到对方锁定的资源的时候自己不会释放自己锁定
的资源,就发生了死锁;
死锁的时候程序暂停,暂停虽然不通于挂起,但是对用户来说是一样的:程序仍然在运行,而且工作正常;
所以同步块要尽量小,同时被同步的对象要少;
线程的时序安排
优先权
优先权指定为1到10;
默认每个线程都是5;
10的优先权最高;
static int
MAX_PRIORITY =10
The maximum priority that a thread can have.
static int
MIN_PRIORITY =1
The minimum priority that a thread can have.
static int
The default priority that is assigned to a thread.
更改优先权的方法:
Public final void setPriority(int newPriority)
抢先
所有的Java虚拟机都保证在优先权间使用抢先类型的线程时序安排;也就是说,低优先级的线程可能在没有主动控制权的情况下被暂停;
而协作线程调度程序是非常尊重线程;
线程方法可以暂停线程或者该线程将要暂停的途径:
1, 线程在I/O上阻塞,block
2, 线程在一个同步的对象上阻塞;这种阻塞容易引发死锁:一个线程等待第二个线程占据的锁定,而第二个线程等待第一个线程占据的锁定;
3, 线程让步yield public static void yield() 让步后线程并不释放任何锁定,所以如果其他线程与该线程是用对象同步的,那都会阻塞,最终还是执行该线程;
4, 线程休眠sleep 进入休眠的线程无论任何其他的线程是否准备执行还是不执行都将暂停;public void interrupt()方法可以在预期的时间未到来之前唤醒睡眠的线程;
区分Thread对象和线程非常重要:这意味着,其他未休眠的线程可以通过休眠线程的方法和字段使用相应的Thread对象;特别的,其他线程可以调用休眠Thread对象的interrupt()方法,休眠线程将把调用该方法的请求作为InterruptedException.
5, 线程加入到另一个线程 public final void join() : 加入的线程等待被加入的线程的结束;
当一个线程需要另一个线程的结果时这个方法相当有效,也许你认为可以用sleep()来睡眠需要另一个线程的结果的线程直到结果出来,但是你TM怎么知道需要睡眠多长时间;当一个主线程需要副线程给出的结果来利用,但是主线程有超过给出结果的副线程的趋势时;
6, 线程在一个对象上等待 线程可以在它锁定的对象上等待。等待状态比较前面的让步和休眠而言,最大的好处是线程释放在该对象上的锁并暂停,直到某个其他线程通知它为止,这就可以让别的线程进入同步块;
Public final void wait() throws InterruptedException
注意,这个方法是Object的,而不是Thread的;也就是说不同于前面的让
休眠,等待是对象的决定而不是线程的主动决定;为了在一个特殊对象上等
待,希望暂停的对象必须首先使用synchronized获得该对象的锁定,然后调
用对象的3个wait()方法中的一个;
Wait()方法的方式是:调用这个方法的线程将释放它在对象上的锁定来在这个对象
等待,并进入睡眠状态;***/////你应该消除这样一个错误的顾虑:方法是谁的,调
用者就是谁!其实这也对,方法当然是由它的对象调用的,但是调用者对象的调
用容器是谁呢?还不是最终有个线程在调用这一切吗!
当如下的事情发生的时候,在对象上等待的线程将不再继续睡眠:
超时;
中断线程;interrupt()
对象接到通知;public final void notify() / public final void notifyAll()
Notify()方法的调用线程必须拥有对对象的锁定;notify()方法从在对象上等待的线 程中随机选择一个激活;notify()方法激活在一个对象上等待的所有线程;
不存在特意选择某个等待线程的方式,所以如果有多个线程在一个对象上等待的
你就应该调用notify()激活所有线程;
7, 线程结束
8, 优先权高的线程抢先于该线程 :
9, 线程挂起
10, 线程停止
应该保证run()方法中有上述的一个情况以合理的频率发生;
线程库
线程提高了性能,但是线程有自己的开销;启动和退出线程还是需要耗费虚拟机大量精力的;线程的切换也是cpu的一个烦恼的事情;总之,你可以想像线程是为了充分利用cpu
的空余时间,但是,如果成千上百的线程被制造出来的时候,线程不再是提高性能而是增加系统的负担;
所以聪明的人们就想到了既能提高资源的利用率,又可以避免过多的线程浪费资源的方法:
让线程在完成任务后并不退出;(一个线程退出后就不可以再次启动);
线程库的思想是:将需要完成的所有任务放置到一个队列或其他数据结构中,并让每个线程在完成一个任务后从队列里取得下一个任务,用于保存任务的数据结构称为库:pool