Java多线程知识点总结

来源:互联网 发布:c 图片相似度算法 编辑:程序博客网 时间:2024/05/17 02:48

实现多线程的两种方法:

  1. 实现Runnable接口,然后将它传递给Thread的构造函数,创建一个Thread对象
  2. 直接继承Thread类

sleep()、wait()和suspend()的区别:
区别一:
sleep()是Thread类的方法,是线程用来控制自身流程的。该方法指示当前线程暂停执行指定时间,把执行机会让给其他线程,但是监控状态依然保持,到时间后会自动恢复。调用sleep()方法不会释放对象锁。
wait()是Object类的方法,是用来线程间通信的。该方法会使当前拥有该对象锁的线程等待,直到其他线程调用notify()方法时再醒来,或者给它指定一个时间,自动醒来。这个方法主要是用在不同线程之间的调度。对象调用wait()方法导致本线程放弃对象锁,进入等待此对象的等待池,只有针对此对象发出notify()方法(或notifyAll()方法)后本线程才进入对象的锁定池准备获得对象锁进入运行状态。调用wait()方法会释放对象锁。

区别二:
线程间的通信是靠对象来管理的,所有操作一个对象的线程是这个对象通过自己的wait()方法来管理的。调用sleep()方法不会释放锁,因为sleep()是一个线程用于管理自己的方法,不涉及线程通信。
suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁。此时,其他任何线程都不能访问锁定的资源,除非被”挂起”的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁

在以下情况下,持有锁的线程会释放锁:
1. 执行完同步代码块。
2. 在执行同步代码块的过程中,遇到异常而导致线程终止。
3. 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放锁,进行对象的等待池。

在以下情况下,线程虽然停止执行,但是线程不会释放锁:
1. 在执行同步代码块的过程中,执行了Thread.sleep()方法,当前线程放弃CPU,开始睡眠,在睡眠中不会释放锁。
2. 在执行同步代码块的过程中,执行了Thread.yield()方法,当前线程放弃CPU,但不会释放锁。
3. 在执行同步代码块的过程中,其他线程执行了当前对象的suspend()方法,当前线程被暂停,但不会释放锁。

死锁:
线程之间互相等待对方释放资源对象的锁,而每个线程又持有其他线程需要的锁,造成几个线程在没有必要的永久等待中,程序无法继续进行。

有返回值的线程调用:
实现Callable接口,重写call()方法,Call()方法的返回值为Object。当需要执行实现了Callable接口的类的对象所表示的任务时,需要通过调用ExecutorService接口中的submit方法来完成。该方法入口参数为Callable接口类型引用,返回值为Future接口类型引用,可通过Future的get()方法获取Call()方法的返回值。

ThreadLocal类详解:
ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”。其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

下面是线程局部变量(ThreadLocal variables)的关键点:
一个线程局部变量(ThreadLocal variables)为每个线程方便地提供了一个单独的变量。在多个线程操作该变量时候能够互不影响,因为每个线程操作的实际上是改变量的副本。ThreadLocal实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联线程。当多个线程访问ThreadLocal实例时,每个线程维护ThreadLocal提供的独立的变量副本。

//测试代码:作用于一个对象上面的三个线程来操作同一个ThreadLoacl对象(Integer 类型),看是否会出现脏读等现象:public class Test implements Runnable {private static ThreadLocal<Integer> num = new ThreadLocal<Integer>();    public void run() {        num.set(0);        for (int i = 0; i < 3; i++) {            num.set(num.get() + 1);            System.out.println(Thread.currentThread().getName() + ":num="+ num.get());        }    }    public static void main(String[] args) {    Test test = new Test();    Thread t1 = new Thread(test, "Thread-1");    Thread t2 = new Thread(test, "Thread-2");    Thread t3 = new Thread(test, "Thread-3");    t1.start();    t2.start();    t3.start();    }}运行结果如下:Thread-3:num=1Thread-2:num=1Thread-1:num=1Thread-2:num=2Thread-3:num=2Thread-2:num=3Thread-1:num=2Thread-1:num=3Thread-3:num=3

从上面可以看出,完全没有出现脏读等的现象,因此ThreadLocal线程安全。

ThreadLoacal与同步机制的比较:
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

进程和线程之间有什么不同?
一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一个包含了不同的类和程序的单一进程。线程可以被称为轻量级进程。线程需要较少的资源来创建和驻留在进程中,并且可以共享进程中的资源。

多线程编程的好处是什么?
在多线程程序中,多个线程被并发的执行以提高程序的效率,CPU不会因为某个线程需要等待资源而进入空闲状态。多个线程共享堆内存(heap memory),因此创建多个线程去执行一些任务会比创建多个进程更好。举个例子,Servlets比CGI更好,是因为Servlets支持多线程而CGI不支持。

用户线程和守护线程有什么区别?
当我们在Java程序中创建一个线程,它就被称为用户线程。一个守护线程是在后台执行并且不会阻止JVM终止的线程。当没有用户线程在运行的时候,JVM关闭程序并且退出。一个守护线程创建的子线程依然是守护线程。

你对线程优先级的理解是什么?
每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,但这依赖于线程调度的实现,这个实现是和操作系统相关的(OS dependent)。我们可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个int变量(从1-10),1代表最低优先级,10代表最高优先级。

什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?
线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。

在多线程中,什么是上下文切换(context-switching)?
上下文切换是存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。上下文切换是多任务操作系统和多线程环境的基本特征。

线程之间是如何通信的?
当线程间是可以共享资源时,线程间通信是协调它们的重要的手段。Object类中wait()\notify()\notifyAll()方法可以用于线程间通信关于资源的锁的状态。

为什么线程通信的方法wait(), notify()和notifyAll()被定义在Object类里?
Java的每个对象中都有一个锁(monitor,也可以称为监视器) 并且wait(),notify()等方法用于等待对象的锁或者通知其他线程对象的监视器可用。在Java的线程中并没有可供任何对象使用的锁和同步器。这就是为什么这些方法是Object类的一部分,这样Java的每一个类都有用于线程间通信的基本方法。

为什么wait(), notify()和notifyAll()必须在同步方法或者同步块中被调用?
当一个线程需要调用对象的wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的notify()方法。同样的,当一个线程需要调用对象的notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。

为什么Thread类的sleep()和yield()方法是静态的?
Thread类的sleep()和yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。

如何确保线程安全?
在Java中可以有很多方法来保证线程安全——synchronized关键字(声明同步方法,同步语句块),使用原子类(atomic concurrent classes),实现并发锁,使用volatile关键字,使用不变类和线程安全类。

volatile关键字在Java中有什么作用?
当我们使用volatile关键字去修饰变量的时候,所以线程都会直接读取该变量并且不缓存它。这就确保了线程读取到的变量是同内存中是一致的。

同步方法和同步块,哪个是更好的选择?
同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止执行并需要等待获得这个对象上的锁。

如何创建守护线程?
使用Thread类的setDaemon(true)方法可以将线程设置为守护线程,需要注意的是,需要在调用start()方法前调用这个方法,否则会抛出IllegalThreadStateException异常。

什么是ThreadLocal?
ThreadLocal用于创建线程的本地变量,我们知道一个对象的所有线程会共享它的全部变量,所以这些变量不是线程安全的,我们可以使用同步技术。但是当我们不想使用同步的时候,我们可以选择ThreadLocal变量。
每个线程都会拥有他们自己的Thread变量,它们可以使用get()\set()方法去获取他们的默认值或者在线程内部改变他们的值。ThreadLocal实例通常是希望它们同线程状态关联起来是private static属性。

什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes)?
原子操作(安全操作)是指线程对单个变量的操作是一个整体,不能分割。
原子操作是指一个不受其他操作影响的操作任务单元。原子操作是在多线程环境下避免数据不一致必须的手段。
int++并不是一个原子操作,所以当一个线程读取它的值并加1时,另外一个线程有可能会读到之前的值,这就会引发错误。
为了解决这个问题,必须保证增加操作是原子的,在JDK1.5之前我们可以使用同步技术来做到这一点。到JDK1.5,java.util.concurrent.atomic包提供了int和long类型的工具类,它们可以自动的保证对于他们的操作是原子的并且不需要使用同步。

Java Concurrency API中的Lock接口(Lock interface)是什么?对比同步它有什么优势?
Lock接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。
它的优势有:
可以使锁更公平
可以使线程在等待锁的时候响应中断
可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间
可以在不同的范围,以不同的顺序获取和释放锁

什么是阻塞队列?如何使用阻塞队列来实现生产者-消费者模型?
java.util.concurrent.BlockingQueue的特性是:当队列是空的时,从队列中获取或删除元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。
阻塞队列不接受空值,当你尝试向队列中添加空值的时候,它会抛出NullPointerException。
阻塞队列的实现都是线程安全的,所有的查询方法都是原子的并且使用了内部锁或者其他形式的并发控制。
BlockingQueue 接口是java collections框架的一部分,主要方法有put(Object o)和take(),它主要用于实现生产者-消费者问题。

0 0
原创粉丝点击