线程中run()和start()的区别:
对于Thread对象来说,当你调用的是start()
,线程会被放到等待队列,等待CPU调度,不一定马上执行;无需等待run()
方法执行完毕,可以直接执行下面的代码;
而调用的是run()的话,就是当做普通的方法调用,程序还是要顺序执行的;
新建线程的几种方式:
实现Runnable接口;里面实现run()方法;
然后把这个实现了Runnable接口的类就新建为一个Thread t = new Thread(new (实现Runnable接口的类)),调用start()方法即可开始一个线程了。记住,start()只是开启,然后就会返回,继续执行start()下面的语句了。
线程执行器:
我们可以通过不同的线程执行器来实现多线程的执行,有以下几种执行器:
ExecutorService exec = Executors.newCachedThreadPool();
ExecutorService exec = Executors.newFixedThreadPool(5);
ExecutorService exec = Executors.newSingleThreadExecutor();
我们可以对比一下这三者的区别:第一个执行会为每一个任务都创建一个线程,
而第二个则是可以一次性指定要分配多少线程,而第三个则是属于单线程,会一个线程一个线程的依次执行;
休眠:
会使得任务中断一段时间,相当于变相的阻塞了,可以给其他线程制造机会去执行;
但是我们不能通过sleep()
来试图控制线程的顺序执行,而是要考虑用同步控制来实现;
让步:
通过使用yield()
方法来给线程调度机制一个暗示:你的工作已经完成的差不多了,可以让别的线程使用CPU了,其功能上跟sleep()
其实是差不多的。
后台线程:
指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序总共不可或缺的部分,当所有非后台线程结束时,程序终止;由后台线程创建的线程也是后台线程;
在线程调用start()
之前,调用setDaemon(true);
实现多线程的另一种方式:
通过继承Thread的方式来实现:而且run()
方法是放在构造函数里面的,也就是说,当初始化一个线程的时候,就自动的开启了线程,记得run()方法里面一般都是一个while()
循环;
加入一个线程:
一个线程可以在其他线程之上调用join()
方法,如果某个线程在另一个线程t上调用t.join();
此线程将被挂起,知道目标线程t结束才恢复;
join()方法,你在一个线程中join()了一个线程进来,你就要等待这个线程结束了,才可以把自己这个线程给结束掉;join()的底层实现是wait()
方法;
同步:
Synchronzied;可以用在方法上,也可以用到类上面;
显式地使用lock对象
先用Lock lock = new ReentrantLock();
建出一个锁对象出来,然后在方法里面,先调用lock.lock();
然后try语句里面是方法体,最后记得要在finally里面加上lock.unlock();这样就就相当于解锁了。
区别:
可以看到synchronized和 lock相比起来,lock似乎要加上一些try/catch语句才可以,但是,这也是好处之一,比起synchronized,可以多出来处理的过程,让用户出现错误的可能性降低;
使用原子类也可以实现资源共享的问题,但是原子类一般很少在常规编程中用到,用于性能调优,然后AtomicInteger,AtomicLong等原子类,使用这些的时候,不需要用到synchronized和lock,但是原子类很少用到,所以我们还是用synchronized和lock。
线程的状态:
新建;就绪;阻塞;死亡;
导致阻塞的几个原因:
1,通过调用sleep()
使任务进入休眠状态,
2,通过调用wait()
使线程挂起,知道线程得到notify()
或notifyAll()
消息,
3,任务再等待某个输入/输出完成;
4,任务视图在某个对象上调用其同步控制方法,但是对象锁不可用,因为另一个任务已经获取了这个锁;
中断:
这是一个大学问呀。一般的话,我们中断都是用interrupted(),
但是,我们现在说了,用Executor执行器可以更好地执行了,所以我们如如何在执行器中中断线程呢?这也很好办,用Executor的shutdownNow(),但是,这又是一个问题了,这只是用来中断所有的线程的,但是我们是想要中断某一个线层那该怎么办呢?这就用到了返回式了,通过submit()
来启动任务的时候,我们就能够得到返回的类型Future<?>通过这个去调用calcel()来中断某个线程。具体等一下码,现在还要讨论的还有一个问题,中断的线程是否有一些是无法中断的,判定如下:如果是在sleep()中的线程,那么显然是可以中断的,但是对于正在读取I/O的线程和正在试图获取锁的线程,我们是无法中断的,而中断线程就相当于抛出了一个异常,方便我们关闭掉资源。
线程之间的协作:
当线程同时运行多个任务时,我们可以用锁来同步两个任务的行为,同时也可以用wait()
和notifyAll()
来实现对线程的控制;
wait()就是一种挂起的状态,当你挂起了之后,锁将被释放,把空出来的线程,给别人执行,而等到被调用notify()唤醒之后,又会重新获得之前wait()锁,如果这个时候锁正在被使用的话,就要陷入等待了。
wait()跟sleep()之间的区别:
1,在synchronized中,wait()期间对象锁是释放的;而sleep()锁是不会释放的;
2,可以通过notify(),notifyAll(),或者令时间到期,从wait()中恢复执行;
唤醒的区别:
notify()方法保证的唤醒是指唤醒的是恰当的任务,另外,为了使用notify(),你必须等待相同的条件,而对于notifyAll()来说,是否所有的线程都会被唤醒呢?只有当notifyAll()因某个特定锁被调用时,只有等待这个锁的任务才能被唤醒;
除了wait()和notify(),我们还可以显式地使用lock和Condition对象;
使用互斥并允许任务挂起的基本类是Condition;可以通过在Condition上调用await来挂起一个任务,通过signal()
来通知这个任务,唤醒这个任务,或者调用signalAll()来唤醒所有在这个Condition上被其自身挂起的任务;与使用notify()相比,signlAll()是更安全的方式;
1 2 3 4 5 6 7 8 9 |
|
但是我们要知道显式的lock对象,相比起wait(),notify()来说,更加复杂,所以还是建议用回原来的那个wait(),Lock和Condition只有在更加困难的多线程问题才是必需的;
死锁:
当某一个任务在等待另一个任务的锁释放,而下一个任务又在等待上一个锁的释放,在这样呈链式的循环里面,直到这个链条上的任务又在等待第一个任务释放锁,得到了一个任务之间的相互等待的连续循环;称为死锁;
哲学家就餐问题;经典的死锁问题;
发生死锁的四个满足条件:
1,互斥条件,任务使用的资源中国至少有一个是不能共享的。
2,至少有一个任务它必须持有一个资源把那个正在等待获取另一个被别的任务持有的资源;
3,资源不能被任务抢占;
4,必须要有等待循环;
防止死锁的最容易的方式是破坏第四个条件。