0%

Java 线程基础、线程之间的共享和协作 (一)

Java 线程基础、线程之间的共享和协作 (一)

什么是进程和线程

进程是程序运行资源分配的最小单位

线程是 CPU 调度的最小单位,必须依赖于进程而存在

CPU 核心数和线程数的关系

核心数、线程数:目前主流 CPU 都是多核的。增加核心数目就是为了增加线 程数,因为操作系统是通过线程来执行任务的,一般情况下它们是 1:1 对应关系,也 就是说四核 CPU 一般拥有四个线程。但 Intel 引入超线程技术后,使核心数与线程 数形成 1:2 的关系

CPU 时间片轮转机制

时间片轮转调度是一种最古老、最简单、最公平且使用最广的算法,又称 RR 调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。

并行和并发

并发:指应用能够交替执行不同的任务,比如单 CPU 核心下执行多线程并非是 同时执行多个任务,如果你开两个线程执行,就是在你几乎不可能察觉到的速度不 断去切换这两个任务,已达到”同时执行效果”,其实并不是的,只是计算机的速度太 快,我们无法察觉到而已.

并行:指应用能够同时执行不同的任务,例:吃饭的时候可以边吃饭边打电话, 这两件事情可以同时执行 两者区别:一个是交替执行,一个是同时执行.

当谈论并发的时候一定要加个单位时间,也就是说单位时间内并发量是多少? 离开了单位时间其实是没有意义的。

高并发编程的意义、好处和注意事项

  1. 充分利用 CPU 的资源
  2. 加快响应用户的时间
  3. 可以使你的代码模块化,异步化,简单化

多线程程序需要注意事项

  1. 线程之间的安全性
  2. 线程之间的死锁
  3. 线程太多了会将服务器资源耗尽形成死机当机

认识Java里的线程

一个 Java 程序从 main()方法开始执行,然后按照既定的代码逻辑执行,看 似没有其他线程参与,但实际上 Java 程序天生就是多线程程序,因为执行 main() 方法的是一个名称为 main 的线程。
[6] Monitor Ctrl-Break //监控 Ctrl-Break 中断信号的
[5] Attach Listener //内存 dump,线程 dump,类信息统计,获取系统属性等
[4] Signal Dispatcher // 分发处理发送给 JVM 信号的线程
[3] Finalizer // 调用对象 finalize 方法的线程
[2] Reference Handler//清除 Reference 的线程 [1] main //main 线程,用户程序入口

线程的启动与中止

启动线程的方式有:

  1. X extends Thread;,然后 X.start
  2. X implements Runnable;然后交给 Thread 运行

Thread 和 Runnable 的区别 Thread 才是 Java 里对线程的唯一抽象,Runnable 只是对任务(业务逻辑) 的抽象。Thread 可以接受任意一个 Runnable 的实例并执行。

可能有人会说还有Callable,Callable与Runnable的功能大致相似,Callable中有一个call()函数,但是call()函数有返回值,而Runnable的run()函数不能将结果返回给客户程序。

1
2
3
4
5
6
 public interface Runnable {
/*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
1
2
3
4
5
6
7
8
9
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}

可以看到,这是一个泛型接口,call()函数返回的类型就是客户程序传递进来的V类型。

enter image description here

因此FutureTask是Future也是Runnable,又是包装了的Callable( 如果是Runnable最终也会被转换为Callable )。

Callable 和 Future接口的区别:

  • Callable规定的方法是call(),而Runnable规定的方法是run().
  • Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
  • call()方法可抛出异常,而run()方法是不能抛出异常的。
  • 运行Callable任务可拿到一个Future对象, Future表示异步计算的结果。
  • 它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。
  • 通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。
  • Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。
1
2
3
4
5
6
7
FutureTask<Integer> result = new FutureTask<Integer>(new Callable<Integer>() {
public Integer call() throws Exception {
return 1;
}
});
new Thread(result).start();
**最终还是被归类为Runable类**

FutureTask实现Runnable,所以能通过Thread包装执行,
FutureTask实现Runnable,所以能通过提交给ExcecuteService来执行

线程中止

线程自然终止

要么是 run 执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。
暂停、恢复和停止操作对应在线程 Thread 的 API 就是 suspend()、resume() 和 stop()。但是这些 API 是过期的,也就是不建议使用的。

#####Thread中 stop(),suspend()函数不建议使用的原因:

以 suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如 锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方 法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资 源释放工作的机会,因此会导致程序可能工作在不确定状态下.

人为中断

安全的中止则是其他线程通过调用某个线程 A 的 interrupt()方法对其进行中 断操作.不代表线程A会立即停止自己的工作,同样的 A 线程完全可以不理会这种中断请求。 因为 java 里的线程是协作式的,不是抢占式的。线程通过检查自身的中断标志位是否被置为 true 来进行响应.

线程通过方法 isInterrupted()来进行判断是否被中断,也可以调用静态方法 Thread.interrupted()来进行判断当前线程是否被中断,不过 Thread.interrupted() 会同时将中断标识位改写为 false。

如果一个线程处于了阻塞状态(如线程调用了 thread.sleep、thread.join、 thread.wait 等),则在线程在检查中断标示时如果发现中断标示为 true,则会在 这些阻塞方法调用处抛出 InterruptedException 异常,并且在抛出异常后会立即 将线程的中断标示位清除,即重新设置为 false。

不建议自定义一个取消标志位来中止线程的运行。因为 run 方法里有阻塞调 用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取消标志
这种情况下,使用中断会更好,因为

  1. 一般的阻塞方法,如 sleep 等本身就支持中断的检查,
  2. 检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可 以避免声明取消标志位,减少资源的消耗。

注意:处于死锁状态的线程无法被中断

深入理解 run()和 start()

Thread类是Java里对线程概念的抽象,可以这样理解:我们通过new Thread() 其实只是 new 出一个 Thread 的实例,还没有操作系统中真正的线程挂起钩来。 只有执行了 start()方法后,才实现了真正意义上的启动线程。
**start()方法让一个线程进入就绪队列等待分配 cpu,分到 cpu 后才调用实现 的 run()方法,start()方法不能重复调用,如果重复调用会抛出异常。
run()**方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方 法并没有任何区别,可以重复执行,也可以被单独调用。

其他的线程相关方法

**yield()**方法:使当前线程让出 CPU 占有权,但让出的时间是不可设定的。也不会释放锁资源。
注意:并不是每个线程都需要这个锁的,而且执行 yield( )的线 程不一定就会持有锁,我们完全可以在释放锁后再调用 yield 方法。 所有执行 yield()的线程有可能在进入到就绪状态后会被操作系统再次选中 马上又被执行。

**join()**方法 把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。 比如在线程 B 中调用了线程 A 的 Join()方法,直到线程 A 执行完毕后,才会继续 执行线程 B。(此处为常见面试考点)

Thread threadA=new Thread(new Runnable() {
    @Override
    public void run() {
        Log.e("TAG","threadA");

    }
});
Thread threadB=new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            threadA.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.e("TAG","threadB");
    }
});
Thread threadC=new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            threadB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.e("TAG","threadC");
    }
});

  *线程的执行顺序是 A->B->C

线程的优先级

在 Java 线程中,通过一个整型成员变量 priority 来控制优先级,优先级的范 围从 1~10,在线程构建的时候可以通过 setPriority(int)方法来修改优先级,默认 优先级是 5,优先级高的线程分配时间片的数量要多于优先级低的线程。

守护线程

Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调 度以及支持性工作。这意味着,当一个 Java 虚拟机中不存在非 Daemon 线程的 时候,Java 虚拟机将会退出。可以通过调用 Thread.setDaemon(true)将线程设置 为 Daemon 线程。我们一般用不上,比如垃圾回收线程就是 Daemon 线程。