多线程之普通

1. Java多线程面试问题:
2.多线程实现方式及并发与同步
3.java多线程面试题整理及答案(2018年)

-----

11.线程各种同步的含义?
重入锁同步:
在 JavaSE5.0中 新增了一个 java.util.concurrent 包来支持同步。 
ReentrantLock类是 可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例 
lock() : 获得锁 ;        unlock() : 释放锁 ;
注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用 。例如: 
        class Bank {
            private int account = 100;   
            private Lock lock = new ReentrantLock(); //需要声明这个锁
            public int getAccount() {
                return account;
            }
            public void save(int money) { //这里不再需要synchronized 
                lock.lock();
                try{
                    account += money;
                }finally{
                    lock.unlock();
                }
            }
        }
    注:关于Lock对象和synchronized关键字的选择: 
        a.最好两个都不用,使用一种java.util.concurrent包提供的机制,能够帮助用户处理所有与锁相关的代码。 
        b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 
        c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 
Volatile 同步:
    a. volatile关键字为域变量的访问,提供了一种免锁机制。
    b.使用volatile修饰域相当于告诉虚拟机,该域可能会被其他线程更新
    c.因此每次使用该域就要重新计算,而不是使用寄存器中的值。
    d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量 。
    例如: 在上面的例子当中,只需在account前面加上volatile修饰,即可实现线程同步。 
    代码实例: 
        //只给出要修改的代码,其余代码与上同
        class Bank {
            //需要同步的变量加上volatile
            private volatile int account = 100;
            public int getAccount() {
                return account;
            }
            //这里不再需要synchronized 
            public void save(int money) {
                account += money;
            }
        }
    注:多线程中的非同步问题,主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。
用final域,有锁保护的域 和volatile域 可以避免非同步的问题。 
原子变量同步:
  需要使用线程同步的根本原因在于对普通变量的操作不是原子的。那么什么是原子操作呢?
原子操作就是指将 读取变量值、修改变量值、保存变量值看成一个整体来操作即-这几种行为要么同时完成,要么都不完成。
在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。
其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer;
可扩展Number,允许那些  处理--机遇数字类的工具和实用工具 进行统一访问。
AtomicInteger类常用方法:
AtomicInteger(int initialValue) : 创建具有给定初始值的新的
AtomicInteger.addAddGet(int dalta) : 以原子方式 将给定值与当前值相加
get() : 获取当前值
class Bank {
    private AtomicInteger account = new AtomicInteger(100);
    public AtomicInteger getAccount() {
        return account; 
    } 
    public void save(int money) {
        account.addAndGet(money);
    }
}
补充--原子操作主要有:  
对于引用变量和大多数原始变量(long和double除外)的读写操作;  
对于所有使用volatile修饰的变量(包括long和double)的读写操作。另外,可以使用线程池进行管理及优化。
我的相关文章推荐链接地址点击跳转:线程优化及线程池管理。
阻塞队列同步:
    前面同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。
使用javaSE5.0版本中新增的java.util.concurrent包,将有助于简化开发。本小节主要是使用LinkedBlockingQueue<E>来实现线程的同步。
LinkedBlockingQueue<E>是一个基于已连接节点的,范围任意的blocking queue。 队列是先进先出的顺序(FIFO)。
LinkedBlockingQueue 类常用方法 LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue 。
put(E e) : 在队尾添加一个元素,如果队列满则阻塞;
size() : 返回队列中的元素个数  ;    take() : 移除并返回队头元素,如果队列空则阻塞;
代码实例: 实现商家生产商品和买卖商品的同步。
注:BlockingQueue<E>定义了阻塞队列的常用方法,尤其是三种添加元素的方法,我们要多加注意,当队列满时:
  add()方法会抛出异常
  offer()方法返回false
  put()方法会阻塞。
局部变量同步:
    如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,
而不会对其他线程产生影响。
ThreadLocal 类的常用方法:
ThreadLocal() : 创建一个线程本地变量 
get() : 返回此线程局部变量的当前线程副本中的值 
initialValue() : 返回此线程局部变量的当前线程的"初始值" 
set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
public class Bank{
            //使用ThreadLocal类管理共享变量account
            private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                @Override
                protected Integer initialValue(){
                    return 100;
                }
            };
            public void save(int money){
                account.set(account.get()+money);
            }
            public int getAccount(){
                return account.get();
            }
        }
    注:ThreadLocal与同步机制;
        a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。
        b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式

12.线程同步问题 (java多线程面试题整理及答案(2018年))

1)Java中synchronized 和 ReentrantLock 有什么不同?
synchronized来实现互斥,它有一些缺点。比如你不能扩展锁之外的方法或者块边界, 尝试获取锁时不能中途取消等。
Java 5通过Lock接口提供更复杂控制来解决这些问题。ReentrantLock类实现了Lock,它拥有与synchronized 相同的并发性和内存语义,且它还有可扩展性。
2)ReadWriteLock是什么?
读写锁是用来提升并发程序性能的锁分离技术的成果。
Java中的ReadWriteLock是Java 5 中新增的一个接口,一个ReadWriteLock维护一对关联的锁,一个用于只读操作一个用于写。
在没有写线程的情况下一个读锁可能会同时被多个读线程持有。写锁是独占的,你可以使用JDK中的ReentrantReadWriteLock来实现这个规则,它最多支持65535个写锁和65535个读锁。

3)Semaphore是什么?
Semaphore是一个计数信号,信号量常常用于多线程代码中,如数据库连接池。
信号量维护了一个许可集合。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release()添加一个许可,从而可能释放一个正在阻塞的获取者。
但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采取相应的行动。

4) 什么是ThreadLocal变量?

ThreadLocal是Java里一种特殊的变量。每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了。

它是为创建代价高昂的对象获取线程安全的好方法,

比如你可以用ThreadLocal 让SimpleDateFormat变成线程安全的,因为那个类创建代价高昂 且每次调用都需要创建不同的实例,所以不值得在局部范围使用它,如果为每个线程提供一个自己独有的变量拷贝,将大大提高效率。

首先,通过复用减少了代价高昂的对象的创建个数。 其次,你在没有使用高代价的同步 或者 不变性的情况下获得了线程安全。

线程局部变量的另一个不错的例子是 ThreadLocalRandom类,它在多线程环境中,减少了创建代价高昂的Random对象的个数。

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

13 什么是线程安全? Vector是一个线程安全类吗?

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量 的值也和预期的是一样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可以将集合类分 成两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它相似的ArrayList不是线程安全的。

1) 如果同步块内的线程 抛出异常会发生什么?

这个问题坑了很多Java程序员,若你能想到锁是否释放这条线索来回答还有点希望答对。无论你的同步块是正常还是异常退出的,里面的线程都会释放锁,

所以对比锁接口,我更喜欢同步块,因为它不用我花费精力去释放锁,该功能可以在finally block里释放锁实现。

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

你可以通过共享对象来实现这个目的,或者 是使用 像阻塞队列 这样并发的数据结构。

这篇教程《Java线程间通信》(涉及到在两个线程间共享对象)用wait和notify方法实现了生产者消费者模型。

3) 如何写代码来解决生产者消费者问题?

在现实中你解决的许多线程问题都属于生产者消费者模型,就是一个线程生产任务供其它线程进行消费,你必须知道怎么进行线程间通信来解决这个问题。

比较低级的办法是用wait和notify来解决这个问题,

比较赞的办法是   用Semaphore 或者 BlockingQueue来实现生产者消费者模型,这篇教程有实现它。

14 Java中同步集合与并发集合有什么区别?

同步集合与并发集合都为多线程和并发提供了 合适的线程安全的集合,不过并发集合的可扩展性更高。

在Java1.5之前程序员们只有同步集合来用 且在 多线程并发的时候会导致争用,阻碍了系统的扩展性。

Java5介绍了并发集合像ConcurrentHashMap,不仅提供线程安全,还用 锁分离和 内部分区 等现代技术提高了可扩展性。

  Java中ConcurrentHashMap的并发度是什么?

ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是 ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这样在多线程情况下就能避免争用。

多线程实现方式及并发与同步:

概述:
    进程是系统的执行单位,  一般一个应用程序 即是一个进程,程序启动时系统默认有一个主线程,即是UI线程,我们知道不能做耗时任务,否则ANR程序无响应。
   这时需要借助子线程实现,即多线程。 由于线程是系统CPU的最小单位,用多线程其实就是为了更好的利用cpu的资源。
11. 线程状态

1、wait()。 使一个线程处于等待状态,并且释放所有持有对象的lock锁,直到notify()/notifyAll()被唤醒后放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable)。
2、notify()。使一个等待状态的线程唤醒, 注意并不能确切 唤醒等待状态线程,是由JVM决定且不按优先级。
3、notifyAllAll()。使所有等待状态的线程唤醒,注意并不是给所有线程上锁,而是让它们竞争。
4、join()。 使一个线程中断,IO完成会回到Runnable状态,等待JVM的调度。
5、sleep()。 使一个线程处于睡眠状态,是一个静态方法,调用此方法要捕捉Interrupted异常,醒来后进入runnable状态,等待JVM调度。
6、synchronized()。 使Running状态的线程加同步锁使其进入(lock blocked pool ),同步锁被释放 进入可运行状态(Runnable)。
注意:当线程在runnable状态时是处于被调度的线程,此时的调度顺序是不一定的。
Thread类中的yield方法可以让一个running状态的线程转入runnable。

基础概念
1、 并行。多个cpu实例或多台机器同时执行一段代码,是真正的同时。
2、并发。通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。
3、线程安全。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。
     线程不安全就意味着线程的调度顺序会影响最终结果,比如某段代码不加事务去并发访问。
4、线程同步。指的是通过人为的控制和调度,保证共享资源的多线程访问成为 线程安全,来保证结果的准确。
     如某段代码加入@synchronized关键字。线程安全的优先级高于性能优化。
5、原子性。一个操作或者一系列操作,要么全部执行要么全部不执行。  数据库中的“事物”就是个典型的院子操作。
6、可见性。当一个线程修改了共享属性的值,其它线程能立刻看到共享属性值的更改。
      比如 JMM分为 主存和工作内存,  共享属性的修改过程是在主存中读取并复制到工作内存中,在工作内存中修改完成之后,再刷新主存中的值。
     若线程A在工作内存中修改完成但还来得及刷新主存中的值,这时线程B访问该属性的值仍是旧值。这样可见性就没法保证。
7、有序性。 程序运行时代码逻辑的顺序,在实际执行中不一定有序,为了提高性能,编译器和处理器 都会对代码进行重新排序。
前提是,重新排序的结果要 和单线程执行程序顺序一致。

12.什么是Java Cuncurrency API中Lock 接口?相对同步(synchronization)有什么优势?

首先我们看下Lock接口的定义:
Lock是一个控制多线程访问共享资源的工具类,比使用synchronized方法或者语句有了更加扩展性的操作,结构灵活,可以有完全不同的属性,

也可以支持多个相关类的条件对象。使用方法:

  {
     Lock l = ...;
     l.lock();
   try {
     // 访问锁保护的共享资源
    } finally {
       l.unlock();
  }}
  • 相对于synchronization 有如下优点:
  • 可以使锁更公平
  • 可以使线程在等待锁的时候响应中断;
  • 可以让线程尝试获取锁,并在无法获取锁的时候立即返回 或者等待一段时间
  • 可以在不同的作用域,以不同的顺序获取和释放锁

13. 什么是原子操作? Java Concurrency API中有哪些原子操作类?

原子操作是执行单个任务单元的操作,这个操作不需要干扰其他操作,可以理解为当前情况下不可再分的操作,远在操作是多线程环境下避免数据不一致而存在的必需品。
int++就不是原子操作,如果一个线程读取它的值并行+1操作,而另外一个线程读取了旧的值,则会导致错误的结果。
为了解决这个问题,我们需要确保递增操作是原子的,可以使用同步原语(synchronization),也可以使用Java5 包装的AtomicInteger直接完成原子操作。
在包java.util.concurrent.atomic下面的一Atomic开头的类都是原子类。例如AtomicInteger,AtomicReference等

21.什么是阻塞队列(BlockingQueue)?  如何使用阻塞队列实现生产者—消费者问题?

BlockingQueue是一个阻塞队列,顾名思义,当进行检索和删除的时候,如果队列为空,则阻塞等待直至队列非空,当添加元素到队列的时候,如果没有空间会阻塞等待到队列有空间为止。接口定义如下图所示:

BlockingQueue不接受Null值,如果存储Null会抛出空指针异常。BlocingQueue的实现都是线程安全的,使用内部锁或者其他的并发控制方法。

BlockingQueue定义的常用方法如下:

  • add(anObject):把anObject加到BlockingQueue里,如果BlockingQueue可以容纳,则返回true,否则抛出异常。
  • offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false。
  • put(anObject):把anObject加到BlockingQueue里,如果BlockingQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里有空间再继续。
  • poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null。
  • take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的对象被加入为止。

BlockingQueue有四个具体的实现类,根据不同需求,选择不同的实现类:

  • ArrayBlockingQueue:规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小。其所含的对象是以FIFO(先入先出)顺序排序的。
  • LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定。其所含的对象是以FIFO顺序排序的。
  • PriorityBlockingQueue:类似于LinkedBlockingQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数所带的Comparator决定的顺序。
  • SynchronousQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成的。

LinkedBlockingQueueArrayBlockingQueue比较起来,它们背后所用的数据结构不一样,导致LinkedBlockingQueue的数据吞吐量要大于ArrayBlockingQueue,但在线程数量很大时其性能的可预见性低于ArrayBlockingQueue。实现生产者—消费者代码如下:Message.java

public class Message {
    private String msg ;
    public String getMsg() {
        return msg;
    }
    public void setMsg(final String msg) {
        this.msg = msg;
    }
    public Message(final String msg) {
        this.msg = msg;
    }
}

Producer.java

public class Producer implements Runnable {
    private BlockingQueue<Message> messageBlockingDeque;
    public Producer(final BlockingQueue<Message> messageBlockingDeque) {
        this.messageBlockingDeque = messageBlockingDeque;
    }
    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
           Message msg = new Message(" "+i);
            try {
                Thread.sleep(i);
                messageBlockingDeque.put(msg);
                System.out.println("Produced:"+msg.getMsg());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        Message msg = new Message("exit");
        try {
            messageBlockingDeque.put(msg);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Consumer.java

public class Consumer implements Runnable {
    private BlockingQueue<Message> blockingDeque;
    public Consumer(final BlockingQueue<Message> blockingDeque) {
        this.blockingDeque = blockingDeque;
    }
    @Override
    public void run() {
         Message message;
        try {
            while((message= blockingDeque.take()).getMsg()!="exit"){
                Thread.sleep(10);
                System.out.println("Consumed:"+message.getMsg());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ProductConsumerService.java

public class ProductConsumerService {
    public static void main(String[] args) {
        BlockingQueue<Message> queue = new ArrayBlockingQueue<Message>(10);
        Producer producer = new Producer(queue);
        Consumer consumer = new Consumer((queue));
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.submit(producer);
        executorService.submit(consumer);
        try {
            Thread.sleep(10000);
        } 
        executorService.shutdown();
    }
}

22.什么是Executors类?

Executors为Executor,ExecutorService,ScheduledExecutorService,ThreadFactory和Callable类提供了一些工具方法。

Executors可以用于方便的创建线程池。

什么是Executors框架?

在Java5中, Executor框架随同java.util.concurrent.Executor接口一同被引入,Executor框架标准化了线程的调用,调度,执行以及异步任务的控制。
无节制的线程创建会引起内存溢出,创建有限线程数的线程池(ThreadPool)是一个好的选择,线程可以预先创建,回收再利用。

Executor框架促进了Java中线程池的创建,代码如下:

public class SimpleThreadPool {

    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            Runnable worker = new WorkerThread("" + i);
            executor.execute(worker);
          }
        executor.shutdown();
        while (!executor.isTerminated()) {
        }
        System.out.println("Finished all threads");
    }
}

23.什么是并发集合类?

Java集合类都是快速失效的,这就意味着当集合被改变且一个线程在使用迭代器遍历集合的时候,迭代器的next()方法
将抛出ConcurrentModificationException异常。
并发容器支持并发的遍历和并发的更新。主要的类有ConcurrentHashMap, CopyOnWriteArrayList 和CopyOnWriteArraySet

Java 8中,Concurrency API有哪些改进?
一些改进如下:
ConcurrentHashMap 改进了compute(), forEach(), forEachEntry(), forEachKey(), forEachValue(), merge(), reduce() 和 search() 方法.
CompletableFuture that may be explicitly completed (setting its value and status).
Executors 的newWorkStealingPool() 方法创建一个 work-stealing 线程池,使用目前机器上可用的处理器作为它的并行级别。
什么是FutureTask 类?
FutureTask是一个可取消的异步计算类,它实现了Future接口,因为可以在Executors中使用,执行异步处理操作,大多数情况下,我们不需要FutureTask类。
仅仅当我们打算重写Future接口的一些方法并保持原来基础的实现是,它就变得非常有用。我们可以仅仅继承于它并重写我们需要的方法。
什么是Callable和Future?
Java 5在concurrency包中引入了java.util.concurrent.Callable 接口,它和Runnable接口很相似,但它可以返回任何一个对象或者抛出一个异常。
Callable接口使用泛型去定义它的返回类型。Executors类提供了一些有用的方法去在线程池中执行Callable内的任务。由于Callable任务是并行的,我们必须等待它返回的结果,java.util.concurrent.Future应运而生。 在线程池提交Callable任务后返回了一个Future对象,使用它我们可以知道Callable任务的状态和得到Callable返回的执行结果。Future提供了get()方法让我们可以等待Callable结束并获取它的执行结果,示例代码如下:

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        Thread.sleep(1000);
        return Thread.currentThread().getName();
    }
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        List<Future<String>> list = new ArrayList<Future<String>>();
        Callable<String> callable = new MyCallable();
        for (int i = 0; i < 10; i++) {
            Future<String> future = service.submit(callable);
            list.add(future);
        }
        for (Future<String> fut : list) {
            try {
                System.out.println(new Date() + "::" + fut.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        service.shutdown();
    }
}

运行结果:

Mon Oct 06 17:24:52 CST 2014::pool-1-thread-1
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-2
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-3
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-4
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-5
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-6
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-7
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-8
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-9
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-10 

3. 如何避免死

Java多线程中的死锁
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:

  • 互斥条件:一个资源每次只能被一个进程使用。

  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

  • 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。

3.1) Java中活锁和死锁有什么区别?

这是上题的扩展,活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,活锁可以认为是一种特殊的饥饿。一个现实的活锁例子是两个 人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不能通过走廊。简单的说就是,活锁和死锁的主要区别是前者 进程的状态可以改变但是却不能继续执行。

3.2)怎么检测一个线程是否拥有锁?

我一直不知道我们竟然可以检测一个线程是否拥有锁,直到我参加了面试。在java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁。 

二. 常见的线程问题

21、AsyncTask:
HandlerThread只开一条线程,任务都被阻塞在一个队列中,那么就会使阻塞的任务延迟了。
而AsyncTask开启线程方法asyncTask.execute()默认也是开启一个线程和一个队列的,不过也可以通过asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 0)开启一个含有5个新线程的线程池,也就是说有5个队列了。
假如你执行第6个耗时任务时,除非前面5个都还没执行完,否则任务是不会阻塞的,这样就可以大大减少耗时任务延迟的可能性,这也是它的优点所在。
当你想多个耗时任务并发的执行,那你更应该选择AsyncTask。
21. 常见多线程方式?
1、继承Thread类,重写run函数方法:
  class xx extends Thread{
    public void run(){
        Thread.sleep(1000);   //线程休眠1000毫秒,sleep使线程进入Block状态,并释放资源
    }
}
xx.start();   //启动线程,run函数运行
2、实现Runnable接口,重写run函数方法:
Runnable run =new Runnable() {
    @Override
    public void run() {}
}
3、实现Callable接口,重写call函数方法:
Callable call =new Callable() {
    @Override
    public Object call() throws Exception {
        return null;}
}
小结:Callable 与 Runnable 对比。
相同:都是可被其它线程执行的任务。
不同:
     ①Callable规定的方法是call(), 而Runnable规定的方法是run().
     ②Callable的任务执行后可返回值,而Runnable的任务是不能返回值的
     ③call()方法可抛出异常,而run()方法是不能抛出异常的。

     ④运行Callable任务可拿到一个 Future对象,Future表示异步计算的结果。 通过Future对象可了解任务执行情况, 可取消任务的执行。 

22、HandlerThread:
       handlerThread = new HandlerThread("MyNewThread");//自定义线程名称  
        handlerThread.start();  
        mOtherHandler = new Handler(handlerThread.getLooper()){  
            @Override  
            public void handleMessage(Message msg){  
                if (msg.what == 0x124){  

                     try {  

                        Log.d("HandlerThread", Thread.currentThread().getName());  
                        Thread.sleep(5000);//模拟耗时任务  
                    }
                }  
            }  
        };  
HandlerThread的好处是代码看起来没前面的版本那么乱,相对简洁一点。
还有一个好处就是通过handlerThread.quit() 或者 quitSafely(),使线程结束自己的生命周期。
23、IntentService:
最后是IntentService,相信很多人也不陌生,它是Service的子类,用法跟Service也差不多,就是实现的方法名字不一样,
耗时逻辑应放在onHandleIntent(Intent intent)的方法体里,它同样有着退出启动它的Activity后不会被系统杀死的特点,
而且当任务执行完后会自动停止,无须手动去终止它。
例如在APP里我们要实现一个下载功能,当退出页面后下载不会被中断,那么这时候IntentService就是一个不错的选择了。
 

1、多线程和线程同步
线程概念:指进程中的一个按顺序执行的流程,一个进程中可以运行多个线程。线程总是属于某个进程,进程中多个线程共享进程的内存
1、启动一个线程方式
1)使用Thread类,重写Thread的run
Thread thread = new Thread() {
@Override
public void run() { } };
thread.start(); }
2)使用Runnable来定义工作
原理:runnable传到Thread构造方法里,当Thread的run执行时,该方法内部执行runnable的run。作用:便于runnable的重用
static void runnable() {
Runnable runnable = new Runnable() {
@Override
public void run() { } };
Thread thread = new Thread(runnable);
thread.start(); }
3)通过线程池来定义
static void executor() {
Executor executor = Executors.newCachedThreadPool();
executor.execute(runnable); }
4)有返回值的Runnable(很少用)。
static void callable() {
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(5000);//等待5秒模,拟耗时操作
return "Done!";
} };
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> future = executorService.submit(callable);
while (true) { //该循环的逻辑可理解成加载网络转菊花情况
//xx1() 处理其他事情
//xx2()
if (future.isDone()) { //判断任务是否执行完
try {
//卡主线程,阻塞式的方法,需要等5秒后(sleep的时长)取到值。用来获取任务执行完的时机
String result = future.get();
System.out.println("result:" + result);
}
break;
}} }

posted on 2019-04-26 15:05  左手指月  阅读(282)  评论(0编辑  收藏  举报