继承Thread类;
实现Runnable接口;
实现Callable接口经过FutureTask包装器来建立Thread线程;
使用ExecutorService、Callable、Future实现有返回结果的多线程(也就是使用了ExecutorService来管理前面的三种方式)。 java
Thread 类本质上是实现了 Runnable 接口的一个实例,表明一个线程的实例。 启动线程的惟一方法就是经过 Thread 类的 start()实例方法。 start()方法是一个 native 方法,它将启动一个新线程,并执行 run()方法。 面试
若是本身的类已经 extends 另外一个类,就没法直接 extends Thread,此时,能够实现一个Runnable 接口。 数据库
有返回值的任务必须实现 Callable 接口,相似的,无返回值的任务必须 Runnable 接口。执行Callable 任务后,能够获取一个 Future 的对象,在该对象上调用 get 就能够获取到 Callable 任务返回的 Object 了,再结合线程池接口 ExecutorService 就能够实现传说中有返回结果的多线程了。 缓存
线程和数据库链接这些资源都是很是宝贵的资源。那么每次须要的时候建立,不须要的时候销毁,是很是浪费资源的。那么咱们就可使用缓存的策略,也就是使用线程池。 安全
Java 里面线程池的顶级接口是 Executor,可是严格意义上讲 Executor 并非一个线程池,而只是一个执行线程的工具。真正的线程池接口是 ExecutorService。 多线程
newCachedThreadPool
建立一个可根据须要建立新线程的线程池,可是在之前构造的线程可用时将重用它们。对于执行不少短时间异步任务的程序而言,这些线程池一般可提升程序性能。 调用 execute 将重用之前构造的线程(若是线程可用)。若是现有线程没有可用的,则建立一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。 所以,长时间保持空闲的线程池不会使用任何资源。
newFixedThreadPool
建立一个可重用固定线程数的线程池,以共享的***队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。若是在全部线程处于活动状态时提交附加任务,则在有可用线程以前,附加任务将在队列中等待。若是在关闭前的执行期间因为失败而致使任何线程终止,那么一个新线程将代替它执行后续的任务(若是须要)。在某个线程被显式地关闭以前,池中的线程将一直存在。
newScheduledThreadPool
建立一个线程池,它可安排在给定延迟后运行命令或者按期地执行。
newSingleThreadExecutor
Executors.newSingleThreadExecutor()返回一个线程池(这个线程池只有一个线程) ,这个线程池能够在线程死后(或发生异常时)从新启动一个线程来替代原来的线程继续执行下去! 并发
1.使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2.使用stop方法强行终止,可是不推荐这个方法,由于stop和suspend及resume同样都是过时做废的方法。
3.使用interrupt方法中断线程。异步
notify可能会致使死锁,而notifyAll则不会
任什么时候候只有一个线程能够得到锁,也就是说只有一个线程能够运行synchronized 中的代码使用notifyall,能够唤醒全部处于wait状态的线程,使其从新进入锁的争夺队列中,而notify只能唤醒一个。
wait() 应配合while循环使用,不该使用if,务必在wait()调用先后都检查条件,若是不知足,必须调用notify()唤醒另外的线程来处理,本身继续wait()直至条件知足再往下执行。
notify() 是对notifyAll()的一个优化,但它有很精确的应用场景,而且要求正确使用。否则可能致使死锁。正确的场景应该是 WaitSet中等待的是相同的条件,唤醒任一个都能正确处理接下来的事项,若是唤醒的线程没法正确处理,务必确保继续notify()下一个线程,而且自身须要从新回到WaitSet中. jvm
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰以后,那么就具有了两层语义:
1)保证了不一样线程对这个变量进行操做时的可见性,即一个线程修改了某个变量的值,这新值对其余线程来讲是当即可见的,volatile关键字会强制将修改的值当即写入主存。
2)禁止进行指令重排序。
volatile 不是原子性操做
什么叫保证部分有序性?
当程序执行到volatile变量的读操做或者写操做时,在其前面的操做的更改确定所有已经进行,且结果已经对后面的操做可见;在其后面的操做确定尚未进行;
因为flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句一、语句2前面,也不会讲语句3放到语句四、语句5后面。可是要注意语句1和语句2的顺序、语句4和语句5的顺序是不做任何保证的。使用 Volatile 通常用于 状态标记量 和 单例模式的双检锁 ide
start()方法被用来启动新建立的线程,并且start()内部调用了run()方法,这和直接调用run()方法的效果不同。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程 。
明显的缘由是JAVA提供的锁是对象级的而不是线程级的,每一个对象都有锁,经过线程得到。若是线程须要等待某些锁那么调用对象中的wait()方法就有意义了。若是wait()方法定义在Thread类中,线程正在等待的是哪一个锁就不明显了。简单的说,因为wait,notify和notifyAll都是锁级别的操做,因此把他们定义在Object类中由于锁属于对象 。
interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除然后者不会。Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。
当中断线程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。
而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。不管如何,一个线程的中断状态有有可能被其它线程调用中断来改变 。
类似点:
这两种同步方式有不少类似之处,它们都是加锁方式同步,并且都是阻塞式的同步,也就是说当若是一个线程得到了对象锁,进入了同步块,其余访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的.
区别:
这两种方式最大区别就是对于Synchronized来讲,它是java语言的关键字,是原生语法层面的互斥,须要jvm实现。而ReentrantLock它是JDK 1.5以后提供的API层面的互斥锁,须要lock()和unlock()方法配合try/finally语句块来完成。
Synchronized进过编译,会在同步块的先后分别造成monitorenter和monitorexit这个两个字节码指令。在执行monitorenter指令时,首先要尝试获取对象锁。若是这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计算器加1,相应的,在执行monitorexit指令时会将锁计算器就减1,当计算器为0时,锁就被释放了。若是获取对象锁失败,那当前线程就要阻塞,直到对象锁被另外一个线程释放为止 。
因为ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,ReentrantLock类提供了一些高级功能,主要有如下3项:
1.等待可中断,持有锁的线程长期不释放的时候,正在等待的线程能够选择放弃等待,这至关于Synchronized来讲能够避免出现死锁的状况。
2.公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序得到锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是建立的非公平锁,能够经过参数true设为公平锁,但公平锁表现的性能不是很好。
3.锁绑定多个条件,一个ReentrantLock对象能够同时绑定对个对象 。
在多线程中有多种方法让线程按特定顺序执行,你能够用线程类的join()方法在一个线程中启动另外一个线程,另一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。
实际上先启动三个线程中哪个都行,由于在每一个线程的run方法中用join方法限定了三个线程的执行顺序 。
SynchronizedMap()和Hashtable同样,实现上在调用map全部方法时,都对整个map进行同步。而ConcurrentHashMap的实现却更加精细,它对map中的全部桶加了锁。因此,只要有一个线程访问map,其余线程就没法进入map,而若是一个线程在访问ConcurrentHashMap某个桶时,其余线程,仍然能够对map执行某些操做。因此,ConcurrentHashMap在性能以及安全性方面,明显比Collections.synchronizedMap()更加有优点。同时,同步操做精确控制到桶,这样,即便在遍历map时,若是其余线程试图对map进行数据修改,也不会抛出ConcurrentModificationException 。
线程安全就是说多线程访问同一代码,不会产生不肯定的结果。
在多线程环境中,当各线程不共享数据的时候,即都是私有(private)成员,那么必定是线程安全的。
但这种状况并很少见,在多数状况下须要共享数据,这时就须要进行适当的同步控制了。
线程安全通常都涉及到synchronized, 就是一段代码同时只能有一个线程来操做 否则中间过程可能会产生不可预制的结果。
若是你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。若是每次运行结果和单线程运行的结果是同样的,并且其余的变量的值也和预期的是同样的,就是线程安全的。
Yield方法能够暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法并且只保证当前线程放弃CPU占用而不能保证使其它线程必定能占用CPU,执行yield()的线程有可能在进入到暂停状态后立刻又被执行。
两个方法均可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法能够返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和
ScheduledThreadPoolExecutor都有这些方法 。
更多面试题,欢迎关注公众号【慕容千语】