java线程

并发作用

将多核CPU的计算能力发挥到极致,性能得到提升。

面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分。

并发缺点

  • 线程安全问题。
  • 频繁的上下文切换。

概念

并发与并行

Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once。并发指的是多个任务交替进行,而并行则是指真正意义上的“同时进行”。实际上,如果系统内只有一个CPU,而使用多线程时,那么真实系统环境下不能并行,只能通过切换时间片的方式交替进行,而成为并发执行任务。真正的并行也只能出现在拥有多个CPU的系统中。

同步与异步

主要从消息通知角度来看。

同步是说在调用一个函数后,直到执行完成才返回结果。

异步是在调用一个函数之后,立即返回,等待函数执行完成之后,通过状态、通知和回调来通知调用者。

阻塞与非阻塞

主要从等待返回结果时的状态来看。

阻塞就是在等待返回结果时,当前线程会被挂起,让出CPU,不能执行其他业务。

非阻塞就是在等待返回结果时,当前线程不会阻塞,可以去执行其他的业务。

临界区资源

临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每个线程使用时,一旦临界区资源被一个线程占有,那么其他线程必须等待。

线程

创建线程

  • 继承Thread
  • 实现Runnable
  • 实现Callable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

Thread t1 = new Thread(){

public void () {
System.out.println("t1");
}
};
t1.start();
// implements Runnable
Thread t2 = new Thread(new Runnable() {

public void () {
System.out.println("t2");
}
});
t2.start();

// implements Callable,Callable可以有返回值
ExecutorService service=Executors.newSingleThreadExecutor();
Future<String> future=service.submit(new Callable() {

public String call() throws Exception {
return "thread 3";
}
});
try {
String result=future.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}

线程状态

title

  • 新建(new),创建线程。

  • Runnable。包括Running和Ready两个阶段,Running就是占用CPU运行,Ready是线程还处于等待阶段。

  • 阻塞(Blocked)。等待获取临界区资源,一旦他获得了锁就会结束这个状态。

  • 无限期等待(Waiting)。

    等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。

    进入方法 退出方法
    没有设置 Timeout 参数的 Object.wait() 方法 Object.notify() / Object.notifyAll()
    没有设置 Timeout 参数的 Thread.join() 方法 被调用的线程执行完毕
    LockSupport.park() 方法 LockSupport.unpark(Thread)
  • 限期等待(Timed-waiting)。

    无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。

    进入方法 退出方法
    Thread.sleep() 方法 时间结束
    设置了 Timeout 参数的 Object.wait() 方法 时间结束 / Object.notify() / Object.notifyAll()
    设置了 Timeout 参数的 Thread.join() 方法 时间结束 / 被调用的线程执行完毕
    LockSupport.parkNanos() 方法 LockSupport.unpark(Thread)
    LockSupport.parkUntil() 方法 LockSupport.unpark(Thread)
  • 终止。线程任务结束,或者是产生了异常而终止。

线程基本操作

sleep() and wait()

sleep会休眠当前线程,等到了时间,自动苏醒。

调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程

两者区别:

  • sleep到点自己醒,wait需要其他线程调用notify来唤醒

  • wait()方法必须要在同步方法或者同步块中调用,也就是必须已经获得对象锁。

  • sleep不释放锁,wait释放锁。

join()

线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束

对于以下代码,虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出。

yield()

一旦执行,当前线程会让出cpu,但是,需要注意的是,让出的CPU并不是代表当前线程不再运行了,如果在下一次竞争中,又获得了CPU时间片当前线程依然会继续运行。另外,让出的时间片只会分配给当前线程相同优先级的线程。

sleep()和yield()方法,同样都是当前线程会交出处理器资源,而它们不同的是,sleep()交出来的时间片其他线程都可以去竞争,也就是说都有机会获得当前线程让出的时间片。而yield()方法只允许与当前线程具有相同优先级的线程能够获得释放出来的CPU时间片。

守护线程

守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。

当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。

main() 属于非守护线程。

使用 setDaemon() 方法将一个线程设置为守护线程

线程中断问题

线程可以调用interrupt()来中断别的线程。但这个操作并不一定会使线程中断,更像是给了线程一个通知,但具体是否中断还是要看线程本身。

InterruptedException

通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞。

interrupted()

如果线程不处在等待或者是阻塞状态,那么直接interrupt是不能中断线程的。但是调用 interrupt() 方法会设置线程的中断标记,此时调用 interrupted() 方法会返回 true。

这样,线程就可以对相应的中断请求进行处理了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

public class Test2 {

public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t1=new Thread() {

public void () {
// TODO Auto-generated method stub
while(!isInterrupted()) {
}
System.out.println("end.......");
}
};
t1.start();
t1.interrupt();
System.out.println(t1.isInterrupted());
}