线程、进程、多线程、守护线程、线程池
Peng's Blog 只记录和技术相关的东西

线程、进程、多线程、守护线程、线程池

2017-08-12

写在前面:

跟线程进程有关的东西,初次了解的时候会觉得内容十分杂乱,非常多概念性的东西。这些坑,所有计算机人都是要经历的。多理解多用,就懂了。

线程

定义:有时候被称为轻量级进程,是程序执行流的最小单元。一般我们写的程序都是只有一个线程的,那个线程就是主线程,也就是main()函数开始的那个线程。

一个线程是进程的一个顺序执行流。同类的多个线程共享一块内存空间和一组系统资源,一个进程中可以包含多个线程。所以,线程间的通信基本上没啥好说的,直接通信就是。平常我们说的IPC是进程间的通信。

但有时候需要多线程,比如服务器端处理客户端的请求,当客户很多的时候,这是就需要针对每个客户建立一个线程。多线程可以使得同一时间间隔内,执行多条指令,达到多个逻辑处理并发的运行。

Java中有两种方式定义线程:

1、实现Runnable接口

2、继承Thread类

两种方式最终都必须由Thread类的start()方法来启动线程,而真正的命令在run()方法里面。

线程主要有五种状态:

这里面有几个很重要的点

1、阻塞状态不能直接到运行状态(考得非常多)

2、从运行状态到就绪状态,可能是因为时间片用完

两种实现线程的方式:

1、Runnable接口

/**
 * Created by peng.tan on 17/8/12.
 */
public class RunnableDemo implements Runnable {
    @Override
    public void run() {
        System.out.println("hello");
    }

    public static void main(String[] args) {
        RunnableDemo r = new RunnableDemo();
        Thread t = new Thread(r);
        t.start();
    }
}

2、继承Thread类

/**
 * Created by peng.tan on 17/8/12.
 */
public class ThreadDemo extends Thread {
    @Override
    public void run() {
        System.out.println("hello");
    }

    public static void main(String[] args) {
        ThreadDemo t = new ThreadDemo();
        t.start();
    }
}

一个线程结束的标志是 run() 方法结束

一个锁被释放的标志是 synchronized块或方法结束

线程在被激活之后不一定马上执行,而是进入到可运行线程的队列当中,等待时间片。


进程

我的理解,进程的话其实就是程序的一次执行。每个进程都有独立的数据内存空间,同一进程所产生的线程共享一块内存空间。

进程,是申请内存空间的最小单位。

关于进程间的通信,可以查看我之前写过的一篇文章:http://blog.tanpeng.net/2017/08/12/os/


线程和进程的区别

一个进程至少有一个线程。线程的划分尺度小于进程。线程是进程内部的一个执行序列。线程又叫轻量级进程。


创建线程的两种方式哪种更优

实现Runnable接口这种方式更优。

原因:

1、解决了单继承问题。它不需要继承Thread类,有时候还需要继承别的,但Java没有多继承,这就非常尴尬了。所以这种情况,只能选择Runnable。

2、有利于程序的健壮性,降低了程序的耦合度。因为实现Runnable接口的类,创建的对象.. 是作为Thread(xx)初始化时候的参数,所以,当线程需要改造的时候,只要改那个类就好了。耦合度非常低。


守护线程

最重要的一个特性:如果某个进程到最后只剩下守护线程,那么程序就直接停止了。所以,守护线程是作为辅助线程存在的。主要的作用是提供计数等辅助的功能。

Java如何设置线程为守护线程? 有个方法,是 setDaemon(true/false)。

JVM的垃圾回收器就是一种守护线程。

  1. 例题:GC线程是否为守护线程?()

答案:是

解析:线程分为守护线程和非守护线程(即用户线程)。

只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着 JVM 一同结束工作。 守护线程最典型的应用就是 GC (垃圾回收器)


Runnable和Callable的区别

Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;

Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。


Thread yield和join的区别

Thread.yield():使得线程放弃当前分得的CPU时间,但是不使线程阻塞,线程仍处于可执行状态。

Thread.join():把指定的线程加入到当前的线程,可以将两个交替执行的线程合并为顺序执行的线程。比如说,在线程B中调用了线程A的join()方法,那么直到线程A执行完毕之后,才会继续执行线程B。


如何在两个线程间共享数据

通过在线程之间共享对象就可以了,

然后通过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的。


sleep()和wait()的区别

sleep:不会释放锁

wait:会释放锁


sleep()和yield()的区别

最重要的一点:线程经过sleep()到阻塞状态,线程经过yield()到就绪状态

其次:sleep()方法给其它线程运行机会的时候不会考虑线程的优先级,yield()方法只会给相同优先级或者更高优先级线程以运行的机会。


请说出与线程同步相关的方法

  • wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
  • sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException 异常;
  • notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
  • notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争;


ConcurrentHashMap实现原理

ConcurrentHashMap是由Segment数组结构和HashEntry数据结构组成。

Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,

HashEntry则用于存储键值对数据。

一个ConcurrentHashMap里面包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构。

一个Segment里包含一个HashEntry数组。

每个HashEntry是一个链表结构的元素,每个Segment守护着一个HashEntry数组里的元素。

当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。

详细内容请参考:聊聊并发(四)–深入分析ConcurrentHashMap


volatile关键字的作用

简单的说,就是当你写一个 volatile 变量之前,Java 内存模型会插入一个写屏障(write barrier),

读一个 volatile 变量之前,会插入一个读屏障(read barrier)。

意思就是说,在你写一个 volatile 域时,能保证任何线程都能看到你写的值,

同时,在写之前,也能保证任何数值的更新对所有线程是可见的,因为内存屏障会将其他所有写的值更新到缓存。

也算是一种同步的策略吧,但是没synchronize那么强大。volatile的翻译是异变的,告诉其它线程这个xx对象或者啥的很容易被改变。然后又满足线程安全的原子性、可见性、有序性啥的。


volatile和synchronized的区别

  1. volatile的本质是告诉JVM这个变量是不确定的,需要从主存中读取;synchronized的本质是锁住当前变量,只让一个线程访问;
  2. volatile只能修饰变量,synchronized能修饰变量和类;
  3. volatile最核心的一点是修改时的可见性,原子性支持的比较差;
  4. volatile不会造成线程的阻塞,synchronized会造成线程的阻塞。
  5. 当一个值依赖于它之前的值,那么volatile就无法工作了,比如 n = n+1、i++等;


线程池

线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销


死锁

死锁的必要条件

1)互斥 2)占有等待 3)非剥夺 4)循环等待

死锁定义

死锁是指进程处于等待状态且等待的事件永远不会发生 在一个进程集合中

死锁防止

1)破坏互斥条件; 2)破坏占有等待条件; 3)破坏非剥夺条件; 4)破坏循环等待条件。

死锁避免

银行家算法


关于操作系统的一些概念性的东西可以看我考前总结的一篇博客:操作系统概念性题目总结

参考资料

1、《什么是线程,什么是守护线程》。2011.6。http://uule.iteye.com/blog/1101187

2、《Java 多线程》。极客学院 2017.8。http://wiki.jikexueyuan.com/project/java-interview-bible/multi-thread.html

3、《Java 面试专题 - 多线程&并发编程》。FX_SKY。2017.3。http://www.jianshu.com/p/e0c8d3dced8a

4、《Java线程的五种状态》。苏湘伦。2017.5。https://suxianglun.github.io/2017/05/26/Java%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81%20/


Comments

评论功能暂停使用,如需跟作者讨论请联系底部的GitHub