2018.03.12、Java-Thread学习笔记

375 阅读20分钟

一、Thread 基础知识

1、线程的状态

  • 创建(new)状态:准备好了一个多线程对象。
  • 就绪(runable)状态:调用了start()方法,等待CPU的调度。
  • 运行(running)状态: 执行run()方法.
  • 阻塞(blocked)状态:暂时停止,可能将资源交给其他线程。
  • 终止(dead)状态:线程销毁。

2、线程执行流程

  • 1、线程创建后不会立即进入就绪状态,因为线程运行需要一些条件(比如划分一块内存空间)。只有线程就绪需要的条件都准备好了,才能进入就绪状态。
  • 2、当线程进入就绪状态后,不代表立刻就能获取CPU执行时间,也许此时CPU正在执行其他的事情,因此它要等待。当得到CPU执行时间之后,线程便真正进入运行状态。
  • 3、线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去。进入阻塞状态。time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)
  • 4、当由于突然中断或者子任务执行完毕,线程就会被消亡。

3、sleep和wait的区别:

  • wait是Thread的方法,sleep是Object中的方法。
  • Thread.sleep() 不会改变锁的行为,不会让线程释放锁。
  • Thread.sleep和Object.wait都会暂停当前的线程,调用wait后, 需要别的线程执行notify/notifyAll才能够重新获得CPU执行时间.

4、停止一个线程

  • 1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
  • 2、使用stop方法强行终止线程,但是不推荐使用这个方法,是作废过期的方法,使用他们可能产生不可预料的结果。
  • 3、使用interrupt方法中断线程,但这个不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。

二、Android 面试题

1、开启线程的三种方式?

  • 1)继承thread 类
public class MyThread extends Thread {
    private int i;
    @Override
    public void run() {
    ...
    }
}
    1. 实现Runable接口
//1实现Runnable接口
public class SecondThread implements Runnable{
    public void run() {
     
    }
}

//2使用
    SecondThread s1=new SecondThread();
    Thread t1=new Thread(s1,"线程1");
    t1.start();

2、线程和进程的区别?

  • 1)一个程序至少有一个进程.并且线程共享整个进程的资源(寄存器、堆栈、上下文),一个进行至少包括一个线程。
  • 2)进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
  • 3)进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元
  • 4)线程是轻量级的进程,它的创建和销毁所需要的时间比进程小很多,所以系统中的执行功能通常是创建线程去完成的

3、为什么要有线程,而不是仅仅用进程?

  • 1)进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
  • 2)进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

4、run()和start()方法区别

  • 1)用start方法来启动线程,真正实现了多线程运行。
  • 2)run方法只是thread的一个普通方法调用,还是在主线程里执行。

5、如何控制某个方法允许并发访问线程的个数?

  • 通过samePhore方法
  • semaphore.acquire() 请求一个信号量,这时候的信号量个数-1(一旦没有可使用的信号量,也即信号量个数变为负数时,再次请求的时候就会阻塞,直到其他线程释放了信号量)
  • semaphore.release() 释放一个信号量,此时信号量个数+1
public class SemaphoreTest {  
    private Semaphore mSemaphore = new Semaphore(5);  
    public void run(){  
        for(int i=0; i< 100; i++){  
            new Thread(new Runnable() {  
                @Override  
                public void run() {  
                    test();  
                }  
            }).start();  
        }  
    }  
    private void test(){  
        try {  
            mSemaphore.acquire();  //Thread.interrupted()阻塞线程
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println(Thread.currentThread().getName() + " 进来了");  
        try {  
            Thread.sleep(1000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println(Thread.currentThread().getName() + " 出去了");  
        mSemaphore.release();  
    }  
}  

6、在Java中wait和seelp方法的不同;

    1. Thread类的方法:sleep(),yield()等 ,Object的方法:wait()和notify()等
    1. Thread.sleep不会导致锁行为的改变,如果当前线程是拥有锁的,那么Thread.sleep不会让线程释放锁。调用wait()方法的时候,线程会放弃对象锁,
    1. 而且wait存在notify方法来唤醒调用wait的线程,这个是sleep没有的。

7、谈谈wait/notify关键字的理解

    1. wait( ),notify( ),notifyAll( )都不属于Thread类,而是属于Object基础类,每个对象都有wait( ),notify( ),notifyAll( ) 的功能,因为每个对象都有锁,锁是每个对象的基础,当然操作锁的方法也是最基础了。
    1. notify( ) 方法只会通知等待队列中的第一个相关线程(不会通知优先级比较高的线程)
    1. notifyAll( ) 通知所有等待该竞争资源的线程(也不会按照线程的优先级来执行)
    1. wait( ) 导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法

8、什么导致线程阻塞?

  • 1)特点:该线程放弃CPU的使用,暂停运行,只有等到导致阻塞的原因消除之后才恢复运行。或者是被其他的线程中断,该线程也会退出阻塞状态,同时抛出InterruptedException
  • 2)线程执行了Thread.sleep(intmillsecond);方法,当前线程放弃CPU,睡眠一段时间,然后再恢复执行
  • 3)线程执行了一个对象的wait()方法,直接进入阻塞状态,等待其他线程执行notify()或者notifyAll()方法
  • 4)线程执行某些IO操作,因为等待相关的资源而进入了阻塞状态。比如说监听system.in,但是尚且没有收到键盘的输入,则进入阻塞状态。
  • 5)线程执行一段同步代码,但是尚且无法获得相关的同步锁,只能进入阻塞状态,等到获取了同步锁,才能回复执行。

9、线程如何关闭?

  • 1)关于线程的关闭,可能会用stop() 方法,但是stop是线程不安全的,一般采用interrupt,判断线程是否中止采用isInterrupted,
Thread thread = null;
thread = new Thread(new Runnable() {
    @Override
    public void run() {
        /*
         * 在这里为一个循环,条件是判断线程的中断标志位是否中断
         */
        while (true&&(!Thread.currentThread().isInterrupted())) {
            try {
                Log.i("tag","线程运行中"+Thread.currentThread().getId());
                // 每执行一次暂停40毫秒
                //当sleep方法抛出InterruptedException  中断状态也会被清掉
                Thread.sleep(40);
            } catch (InterruptedException e) {
                e.printStackTrace();
                //如果抛出异常则再次设置中断请求
                Thread.currentThread().interrupt();
            }
        }
    }
});
thread.start();

//触发条件设置中断
thread.interrupt();

10、讲一下java中的同步的方法

    1. 同步方法
即有synchronized关键字修饰的方法。 
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 
内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
    1. 同步代码块
即有synchronized关键字修饰的语句块。 
    被该关键字修饰的语句块会自动被加上内置锁,从而实现同步 
    代码如: 
    synchronized(object){ 
    }
    注:同步是一种高开销的操作,因此应该尽量减少同步的内容。 
    通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。 
  • 3)使用特殊域变量(volatile)实现线程同步

  • 4)使用重入锁实现线程同步
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
     ReenreantLock类的常用方法有:
         ReentrantLock() : 创建一个ReentrantLock实例 
         lock() : 获得锁 
         unlock() : 释放锁 
    注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用 
  • 5)使用局部变量ThreadLocal实现.
  • 总结:volatile和ThreadLoacl都是通过修饰线程共享的变量来实现同步.剩余3种类似个域加锁.

这篇文章分析很到位

11、数据一致性如何保证?

  • 这个没使用过!不会

12、如何保证线程安全?

  • 确保线程安全的方法:竞争与原子操作、同步与锁、可重入、过度优化
  • 1)竞争与原子操作:
    多线程访问和修改一个数据,可能造成严重的后果。原因是很多的操作被操作系统编译为汇编代码后不止一条指令,因此执行的时候可能执行到一半就被调度系统打乱执行别的代码了。一般单指令的操作称为原子操作,不管怎么样,单指令的执行是不会被打乱的。
    因此为了避免出现多线程操作数据的出现异常,Linux系统提供了一些常用操作的原子指令,确保了线程的安全。
  • 2)同步与锁:
    在一个线程访问数据未结束的时候,其他线程不得对同一个数据进行访问,同步的最常用的方法是使用锁(Lock),
    • 1、每个线程访问数据或者资源前首先试图获取锁,
    • 2、访问结束后释放锁 ,
    • 3、在锁已经被占用的时候试图获取锁时,线程会等待,直到锁重新可以用。
  • 3)可重入:
    一个函数被重入,表示这个函数没有执行完成,但由于外部因素或内部因素,又一次进入该函数执行。什么是可重入函数?
    可重入是并发安全的强力保障,一个可重入的函数可以在多线程环境下放心使用。
//只使用非静态局部变量;不调用不可重入的函数。
public class Reentrant1 {
    private int count = 0;
    /**
     * 不可重入
     * 当有多个线程调用同一个Reentrant对象的increament(),输出数值是不可预测的。count可能是1,也可能是任何正整数。
     */
    public void increament(){
        count ++;
        System.out.println(count);
    }
	
    /** 
     * 可重入
     * 无论多少线程调用同一个Reentrant对的象decreament方法,输出数值都是传入参数length的值减1。
     */
    public void decreament(int length){
        length--;
        System.out.println(length);
    }
    
}

  • 4)过度优化
    在很多情况下,即使我们合理地使用了锁,也不一定能够保证线程安全,因此,我们可能对代码进行过度的优化以确保线程安全。 可以使用volatile关键字试图阻止过度优化,它可以做两件事:
    • 第一,阻止编译器为了提高速度将一个变量缓存到寄存器而不写回;
    • 第二,阻止编译器调整操作volatile变量的指令顺序。

13、如何实现线程同步?

同 问题10

14、两个进程同时要求写或者读,能不能实现?如何防止进程的同步?

15、线程间操作List
通过 :Collections.synchronizedList(new ArrayList());

public class MultiThread {
    
    public static final List<Long> list  = Collections.synchronizedList(new ArrayList<Long>());
    
    public static void main(String[] args) {
        for(int i = 1;i<=100;i++){
            list.add(Long.valueOf(i));
        }
        
        MyThread myThread = new MultiThread().new MyThread(); 
        Thread t1 = new Thread(myThread); 
        t1.setName("线程1"); 
        t1.start(); 

        Thread t2 = new Thread(myThread); 
        t2.setName("线程2"); 
        t2.start(); 

        Thread t3 = new Thread(myThread); 
        t3.setName("线程3"); 
        t3.start(); 

        Thread t4 = new Thread(myThread); 
        t4.setName("线程4"); 
        t4.start(); 
    }
    
    
    public class MyThread implements Runnable{

        @Override
        public void run() {
            for(int i = 0;i<list.size();i++){
                // 同步list,打印数据并删除该数据 
                synchronized (list) {
                    try {
                        //当前线程睡眠,让其它线程获得执行机会 
                        Thread.sleep(100);
                    } catch (Exception e) {
                        // TODO: handle exception
                    }
                    System.out.print(Thread.currentThread().getName() + ":" + list.get(i)+"\n");
                    list.remove(i);
                }
            }
        }

16、Java中对象的生命周期

  • 1、创建阶段(Created)

    • 1 为对象分配存储空间
    • 2 开始构造对象
    • 3 从超类到子类对static成员进行初始化
    • 4 超类成员变量按顺序初始化,递归调用超类的构造方法
    • 5 子类成员变量按顺序初始化,子类构造方法调用
  • 2、应用阶段(In Use)

    • 对象至少被一个强引用持有着
  • 3、不可见阶段(Invisible)

    • 说明程序本身不再持有该对象的任何强引用,虽然该这些引用仍然是存在着的。
publec int getNum(){
    int num =0;
    reture num+1;
}
//不可见
Log.e("error--->",num);
  • 4、不可达阶段(Unreachable)

    • 对象处于不可达阶段是指该对象不再被任何强引用所持有。
  • 5、收集阶段(Collected)

    • 当垃圾回收器发现该对象已经处于“不可达阶段”并且垃圾回收器已经对该对象的内存空间重新分配做好准备时,则对象进入了“收集阶段”。
  • 6、终结阶段

    • 当对象执行完finalize()方法后仍然处于不可达状态时,则该对象进入终结阶段。在该阶段是等待垃圾回收器对该对象空间进行回收。
  • 7、对象空间重新分配阶段

    • 垃圾回收器对该对象的所占用的内存空间进行回收或者再分配了,则该对象彻底消失了,称之为“对象空间重新分配阶段”。

17、Synchronized用法

  • 1)synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
    • 1.修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
    • 2.修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
    • 3.修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
    • 4.修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
  • 2)总结:
    • 1.无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
    • 2.每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
    • 3.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

注意:作用于一个类,和作用于一个对象的区别!!!

18、synchronize的原理

  • 1)通过反编译synchronize代码块可以得到 monitorenter 和 monitorexit:
    • 1.monitorenter:每个对象都有一个监视器锁(monitor),当monitor被占用时就会处于锁定状态,线程中执行monitorenter就是尝试获取monitor的所有权。其过程如下:
      • a.如果moniter的进入数为0,则该线程进入monitor。设置monitor的进入数为1,既该线程成为monitor的所有者。
      • b.如果该线程已占有该monitor,则只需重新进入,monitor的进入数加1。
      • c.如果其他线程占有了该monitor,则该线程进入阻塞状态。直到monitor的进入数为0.再尝试获取monitor的所有权
    • 2.monitorexit:
      • a.该指令执行时,monitor的进入数减一,当monitor的进入数为0时,该线程退出monitor,不再是线程的持有者。等着其他线程尝试进入。
  • 2)通过分析:synchronize的底层语义原理是通过monitor对象来完成的。
    其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

19、谈谈对Synchronized关键字,类锁,方法锁,重入锁的理解

  • 1)Synchronized关键字:
  • 2)类锁:当Synchronized修饰某个代码块的时候,那么就程这段代码为同步代码块
Synchronized(Object){  //注意Object被称为锁对象  
........  //这里面的就是同步的代码块  
} 
  • 3)方法锁:当Synchronized修饰某个方法的时候,那么就称这个方法为同步方法。
  • 4)重入锁:所谓重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。所谓重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。可重入锁的意义在于防止死锁

20、static synchronized 方法的多线程访问和作用

  • 1)Synchronized(实例锁):锁在每一个对象上。如果该类是一个单例(比如单例模式),那么它就具有全局锁的概念。
  • 2)Static Synchronized(全局锁):该锁针对的是类的,无论实例化了多少个对象,那么线程都是共享这个锁的。

21、同一个类里面两个synchronized方法,两个线程同时访问的问题

  • 1)在同一个类的一个实例中的两个synchronized方法,并发访问。
    不可以!!!
多个线程访问同一个类的synchronized方法时, 都是串行执行的!
synchronized方法使用了类java的内置锁, 即锁住的是方法所属对象本身.   
同一个锁某个时刻只能被一个执行线程所获取, 因此其他线程都得等待锁的释放.
  • 2)在同一个类的两个实例中的两个synchronized方法,并发访问。

22、volatile的原理

  • 1)volatile作用:
    • 1.防止重排序,实例化一个对象其实可以分为三个步骤:
      • a.分配内存空间
      • b.初始化对象
      • c.将内存空间地址赋值给相应的对象。
      • ※ 操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:
      • a.分配内存空间
      • c.将内存空间地址赋值给相应的对象。
      • b.初始化对象
如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,
从而导致不可预料的结果。
因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。
- 2.实现可见性
可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。
引起可见性问题的主要原因是每个线程拥有自己的一个高速缓存区——线程工作内存。
volatile关键字能有效的解决这个问题

- 3.保证原子性
volatile只能保证对单次读/写的原子性
  • 2)volatile原理:

23、谈谈volatile关键字的用法

  • 同上

24、谈谈volatile关键字的作用

  • Synchronized是一个比较重量级的操作,对系统的性能有比较大的影响,所以,如果有其他解决方案,我们通常都避免使用Synchronized来解决问题。而volatile关键字就是Java中提供的另一种解决可见性和有序性问题的方案。

25、谈谈NIO的理解

nio 是non-blocking的简称,NIO 的创建目的是为了让 Java 程序员可以实现高速 I/O 而无需编写自定义的本机代码。NIO最重要的三个组成部分:

  • 1)通道 Channels
  • 2)缓冲区 Buffers
  • 3)选择器 Selectors

26、synchronized 和volatile 关键字的区别

  • 1)volatile是变量修饰符,而synchronized则作用于一段代码或方法。
  • 2)volatile只是在线程内存和“主”内存间同步某个变量的值;而synchronized通过锁定和解锁某个监视器同步所有变量的值。显然synchronized要比volatile消耗更多资源。

27、synchronized与Lock的区别

  • 1)Lock是一个接口,而synchronized是Java中的关键字。
  • 2)synchronized是在JVM层面上实现的,在代码执行时出现异常,JVM会自动释放锁定,使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将 unLock()放到finally{} 中;
  • 3)Lock可以让等待锁的线程响应中断,线程可以中断去干别的事务,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
  • 4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
  • 5)Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)

28、ReentrantLock 、synchronized和volatile比较

  • 可重入锁。可重入锁是指同一个线程可以多次获取同一把锁。ReentrantLock和synchronized都是可重入锁。

29、ReentrantLock的内部实现

  • 1)ReentrantLock支持两种获取锁的方式,一种是公平模型,一种是非公平模型

ReentrantLock的内部实现

30、lock原理

31、死锁的四个必要条件?

  • 1)死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
  • 2)条件:
    • 1.互斥条件:一个资源每次只能被一个进程使用。
    • 2.占有且等待:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    • 3.不可强行占有:进程已获得的资源,在末使用完之前,不能强行剥夺。
    • 4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

32、怎么避免死锁?

  • 1)避免一个线程获取多个锁。
  • 2)避免一个线程在锁内占用多个资源,尽量保证每个锁只占用一个资源。
  • 3)尝试定时锁,使用 lock.tryLock(timeout) 来替代使用内部索机制。
  • 4)对于数据库锁,加锁解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

33、对象锁和类锁是否会互相影响?

  • 1)对象锁:在代码中的方法上加了synchronized的锁,或者synchronized(this)的代码段,java的所有对象都含有1个对象锁,这个锁由JVM自动获取和释放。

  • 2)类锁:在代码中的方法上加了static和synchronized的锁,或者synchronized(xxx.class)的代码段

34、什么是线程池,如何使用?

  • 1)newSingleThreadExecutor:
    单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务
  • 2)newFixedThreadExecutor(n)
    固定数量的线程池,没提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行
  • 3)newCacheThreadExecutor
    可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。
  • 4)newScheduleThreadExecutor
    大小无限制的线程池,支持定时和周期性的执行线程

35、Java的并发、多线程、线程模型
36、谈谈对多线程的理解
37、多线程有什么要注意的问题?
38、谈谈你对并发编程的理解并举例说明
39、谈谈你对多线程同步机制的理解?
40、如何保证多线程读写文件的安全?
41、多线程断点续传原理
42、断点续传的实现