对应《实战java高并发程序设计》第二章内容。
1.线程与进程
1.概念
进程简单的理解就是一个运行的程序,线程是程序执行的最小单位,是轻量级的进程。
2.线程状态
新建,运行,阻塞,等待(有时限,无时限),终止
阻塞:比如遇到synchronized同步块,直到获得锁之后才继续运行。
有时限等待:sleep(1000),睡一秒钟
无时限等待:wait(),join()…等到了期望的事件之后,继续运行。
2.线程的基本操作
1.新建线程
2.终止线程
为什么不建议用Thread.stop()?
直接终止线程,并且会立即释放这个线程所拥有的锁,而这些锁用来维持对象的一致性的,可能会造成悲剧。
如何退出呢?
private volatile boolean stop = false;
@Override
public void run() {
while (true){
if (stop){
break;
}
System.out.println("一直运行。。。。。");
}
}
3.线程中断
线程中断不是让线程停止,而是告诉线程,有人希望你退出,怎么处理,由线程自身决定,如果线程什么都不管的话,中断不会用什么作用(当线程sleep或wait时,中断线程会唤醒它们,如下图:)。
//中断线程
public void interrupt() {
}
//判断是否被中断
public boolean isInterrupted() {
}
//判断当前线程是否被中断
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
通过判断中断来退出循环体,效果更为强劲,wait,sleep中都可以接收到中断。
public void run() {
while (true){
if (Thread.interrupted()){
break;
}
System.out.println("一直运行。。。。。");
}
}
4.等待(wati)和通知(notify)
这两个方法是在Object上的。当线程调用了对象的wait后,会等待,直到另外一个线程调用了该对象的notify(notifyAll),才会唤醒。
在调用这两方法前,要先获得该Object的监视器。线程调用wait后,会自动释放锁,notify之后,要手动释放。
5.挂起(suspend)和继续执行(resume)
与stop 相反,挂起不会释放任何资源。若resume在挂起前调用了,可能持有的资源永远都不会释放了。
注意:挂起的线程,状态为Runnable
6.等待线程结束(join)
主线程会一直等待直到t1线程执行完毕。
public static void main(String[] args) {
Thread t1 = new Thread(()->{
for (int i = 0;i < 100000;i ++){
System.out.println(i);
}
});
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
还有一个可以指定等待时长:
public final synchronized void join(long millis) throws InterruptedException {
join的本质:
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
可以看到是让调用线程wait在当前线程对象实例上,当线程执行完毕后,会调用notifyAll通知所有等待的线程继续执行。
在Thread类上有个方法,当线程结束时会调用:
/**
* This method is called by the system to give a Thread
* a chance to clean up before it actually exits.
*/
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
进入:threadTerminated可以看到在这里调用了notifyAll
void threadTerminated(Thread t) {
synchronized (this) {
remove(t);
if (nthreads == 0) {
notifyAll();
}
if (daemon && (nthreads == 0) &&
(nUnstartedThreads == 0) && (ngroups == 0))
{
destroy();
}
}
}
因此:不要在Thread对象实例上使用wait或notify等方法,以被系统api影响。
7.yield
让出CPU时间片
3.volatile关键字
如果变量没有使用该关键字修饰,当修改该变量后,其他线程不一定能看到变化。
4.线程组
ThreadGroup类,可以将多个相同功能线程放入一个线程组。可以看到它里面有许多方法。
在源码中能看到:The thread groups form a tree in which every thread group except the initial thread group has a parent.
可以看出是一个树形的结构。
5.守护线程
在一个java应用内,当只有守护线程时,程序退出。主线程不是守护线程。
设置守护线程:
Thread t1 = new Thread();
t1.setDaemon(true);
6.线程优先级
Thread t1 = new Thread();
t1.setPriority(Integer.MAX_VALUE);
7.线程安全的概念与synchronized
volatile只保证一个线程修改了数据后,其他线程能够看到变化,但多个线程同时修改时,仍然会产生冲突。
synchronized可以对代码进行加锁。
- 指定加锁对象
- 直接作用于实例方法:当前对象
- 直接作用于静态方法:当前类