问题链接转载 Java面试通关要点汇总集【终极版】
方式有三种:继承Thread类创建线程类,通过Runnable接口创建线程类和通过Callable和Future创建线程
1)继承Thread类创建线程类
public Test extends Thread {
int i = 0;
public void run(){
for(;i<100;i++){
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args){
Test t = new Test();
t.start();
}
}
2) 通过Runnable接口创建对象
public class Test implements Runnable{
private int i;
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("hello");
}
public static void main(String[] args){
new Thread(new Test()).start();
}
}
3)通过Callable和Future创建线程
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Beetle implements Callable{
public static void main(String[] args){
Beetle ctt = new Beetle();
FutureTask ft = new FutureTask<>(ctt);
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
if(i == 20)
new Thread(ft,"有返回值的线程").start();
}
try{
System.out.println("子线程的返回值: "+ft.get());
}catch(InterruptedException e){
e.printStackTrace();
}catch(ExecutionException e){
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
int i = 0;
for(;i<100;i++)
System.out.println(Thread.currentThread().getName()+" "+i);
return i;
}
}
三种方式的对比
class MyThread1 extends Thread{
public void run(){
for(int i=0;i<5;i++)
System.out.println("线程1第"+i+"次执行!");
}
}
public class Beetle{
public static void main(String[] args){
Thread t1 = new MyThread1();
t1.start();
for(int i=0;i<10;i++){
System.out.println("主线程第"+i+"次执行!");
if( i > 2 ){
try{
t1.join();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
}
输出
线程1第0次执行!
线程1第1次执行!
线程1第2次执行!
主线程第0次执行!
主线程第1次执行!
主线程第2次执行!
线程1第3次执行!
线程1第4次执行!
主线程第3次执行!
主线程第4次执行!
主线程第5次执行!
主线程第6次执行!
主线程第7次执行!
主线程第8次执行!
主线程第9次执行!
CountDownLatch在多线程并发编程中充当一个计时器的功能,维护一个count变量,并且其操作都是原子操作,该类主要通过countDown()和await()两个方法实现功能。首先通过建立CountDownLatch对象,并且传入参数即count初始值。如果一个线程调用await(),那么这个线程便进入阻塞状态,并进入阻塞队列。如果一个线程调用了countDown()则会是count-1,当count为0时,阻塞队列中调用await()的线程便会逐个被唤醒,执行。
如下所示,读操作调用了await(),而写操作调用countDown(),直到count为0后读操作才开始
public class Beetle{
private final static CountDownLatch cdl = new CountDownLatch(3);
private final static Vector v = new Vector();
public static class WriteThread extends Thread{
private final String writeThreadName;
private final int stopTime;
private final String str;
public WriteThread(String name,int time,String str){
this.writeThreadName = name;
this.stopTime = time;
this.str = str;
}
public void run(){
System.out.println(writeThreadName+"开始写入工作");
try{
Thread.sleep(stopTime);
}catch(InterruptedException e){
e.printStackTrace();
}
cdl.countDown();
v.add(str);
System.out.println(writeThreadName+"写入内容为: "+str+"。写入工作结束");
}
}
public static class ReadThread extends Thread{
public void run(){
System.out.println("读操作之前必须先进行写操作");
try{
cdl.await();
}catch(InterruptedException e){
e.printStackTrace();
}
for(int i=0;i
join()方法也可以实现CountDownLatch的按顺序执行,但如果使用线程池,线程池的线程不能直接使用,使用只能用CountDownLatch,而不能用join()
CyclicBarrier也叫同步屏障,在JDK1.5被引入,可以让一组线程达到一个屏障时被阻塞,直到最后一个线程达到屏障时,之前被阻塞的线程才能继续开始执行。CyclicBarrier有两个构造函数:
其实现原理:在CyclicBarrier的内部定义一个Lock对象,每当一个线程调用await()方法时,将拦截的线程数减1,然后判断剩余拦截数是否等于初始值parties,如果不是,进入Lock对象的条件队列等待。如果是则执行barrierAction对象的Runnable方法,将锁的条件队列中的所有线程都放入锁等待队列中,这些线程会依次地获得锁,释放锁。
public class Beetle{
private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4,10,60,TimeUnit.SECONDS,new LinkedBlockingQueue());
private static final CyclicBarrier cb = new CyclicBarrier(4,new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("寝室四兄弟一起出发去球场");
}
});
private static class GoThread extends Thread{
private final String name;
public GoThread(String name){
this.name = name;
}
public void run(){
System.out.println(name+"开始从宿舍出发");
try{
Thread.sleep(1000);
cb.await();
System.out.println(name+"从楼底下出发");
Thread.sleep(1000);
System.out.println(name+"到达操场");
}catch(InterruptedException e){
e.printStackTrace();
}catch(BrokenBarrierException e){
e.printStackTrace();
}
}
}
public static void main(String[] args){
String[] str = {"李明","王强","刘凯","赵杰"};
for(int i=0;i<4;i++)
threadPool.execute(new GoThread(str[i]));
try{
Thread.sleep(4000);
System.out.println("四人一起到达球场,现在开始打球");
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
输出
李明开始从宿舍出发
赵杰开始从宿舍出发
刘凯开始从宿舍出发
王强开始从宿舍出发
寝室四兄弟一起出发去球场
李明从楼底下出发
赵杰从楼底下出发
刘凯从楼底下出发
王强从楼底下出发
李明到达操场
刘凯到达操场
赵杰到达操场
王强到达操场
四人一起到达球场,现在开始打球
以上便是CyclicBarrier使用实例,通过await()对线程拦截,拦截数加1,当拦截数达到初始值parties后首先执行barrierAction,然后对拦截的线程队列依次获取锁解放锁。
CountDownLatch和CyclicBarrier比较
Semaphore是一种在多线程环境下使用的设施,该设施负责协调各个线程,以保证他们能够正确,合理的使用公共资源的设施,也是操作系统中用于控制进程同步互斥的量。Semaphore是一种计数信号量,用于管理一组资源,内部是基于AQS的共享模式,相当于给线程规定一个量从而控制允许活动的线程数。
Semaphore的主要方法:
public class Beetle{
public static void main(String[] args){
final Semaphore window = new Semaphore(5); // 声明5个许可
for(int i=0;i<8;i++){
new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
try{
window.acquire();
System.out.println(Thread.currentThread().getName()+":开 始买票");
sleep(2000);
System.out.println(Thread.currentThread().getName()+": 购票成功");
window.release();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}.start();
}
}
}
输出
Thread-0:开 始买票
Thread-3:开 始买票
Thread-4:开 始买票
Thread-2:开 始买票
Thread-1:开 始买票
Thread-2: 购票成功
Thread-0: 购票成功
Thread-5:开 始买票
Thread-4: 购票成功
Thread-3: 购票成功
Thread-7:开 始买票
Thread-6:开 始买票
Thread-1: 购票成功
Thread-5: 购票成功
Thread-7: 购票成功
Thread-6: 购票成功
Exchanger(交换者)是用于线程间协作的工具类,Exchanger用于进行线程间的数据交换。它提供了一个同步点,在这个同步点两个线程可以交换彼此的数据。他们通过exchange()交换数据,如果第一个线程先执行exchange(),它会一直等待第二个线程也执行exchange(),当两个线程都到达同步点时,两个线程就可以交换数据。因此使用Exchanger的重点是成对的线程使用exchange(),当一对线程达到同步点就可以进行交换数据
Exchanger类提供了两个方法:
public class Beetle{
public static void main(String[] args){
ExecutorService executor = Executors.newCachedThreadPool();
final Exchanger exchanger = new Exchanger();
executor.execute(new Runnable(){
String data1 = "克拉克森,小拉林庵寺";
@Override
public void run() {
// TODO Auto-generated method stub
nbaTrade(data1,exchanger);
}
});
executor.execute(new Runnable(){
String data1 = "格里芬";
@Override
public void run() {
// TODO Auto-generated method stub
nbaTrade(data1,exchanger);
}
});
executor.execute(new Runnable(){
String data1 = "哈里斯";
@Override
public void run() {
// TODO Auto-generated method stub
nbaTrade(data1,exchanger);
}
});
executor.execute(new Runnable(){
String data1 = "科比";
@Override
public void run() {
// TODO Auto-generated method stub
nbaTrade(data1,exchanger);
}
});
executor.shutdown();
}
private static void nbaTrade(String data1,Exchanger exchanger){
try{
System.out.println(Thread.currentThread().getName()+"在交易截止之前把 "+data1+" 交易出去");
Thread.sleep((long)(Math.random()*1000));
String data2 = (String)exchanger.exchange(data1);
System.out.println(Thread.currentThread().getName()+"交易得到 "+data2);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
输出结果
pool-1-thread-1在交易截止之前把 克拉克森,小拉林庵寺 交易出去
pool-1-thread-2在交易截止之前把 格里芬 交易出去
pool-1-thread-3在交易截止之前把 哈里斯 交易出去
pool-1-thread-4在交易截止之前把 科比 交易出去
pool-1-thread-1交易得到 格里芬
pool-1-thread-2交易得到 克拉克森,小拉林庵寺
pool-1-thread-3交易得到 科比
pool-1-thread-4交易得到 哈里斯
ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同的线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量
public class Beetle{
private static final ThreadLocal
当调用ThreadLocal.get()时,实际上时从当前线程中获取ThreadLocalMap
使用线程池的好处
线程池的创建
public ThreadPoolExecutor(
int corePoolSize, // 线程池核心线程数量
int maximumPoolSize, // 线程池最大线程数量
long keepAliverTime, // 当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
TimeUnit unit, // 存活时间的单位
BlockingQueue workQueue, // 存放任务的队列
RejectedExecutionHandler handler // 超出线程范围和队列容量的任务的处理程序
)
三种类型的线程池
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads,nThreads,0L,
TimeUnit.MILLISECONDS,new LinkedBlockingQueue());
}
public static ExecutorService newSingleThreadExecutor(){
return new FinalizableDelegateExecutorService(
new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue())
);
}
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,
TimeUnit.SECONDS,new SynchronousQueue()
);
}
执行过程与前两种稍微不同:
(1)主线程调用SynchronousQueue的offer()方法放入task,倘若此时线程池中有空闲的线程尝试读取SynchronousQueue的task,即调用了SynchronousQueue的poll(),那么主线程将该task交给空闲线程执行,否则执行(2)
(2)当线程池为空或者没有空闲的线程则创建新的线程执行任务
(3)执行完成任务的线程倘若在60s内仍空闲则会终止。因此长时间空闲的CachedThreadPool不会持有任何线程资源
线程池的实现原理
提交一个任务到线程池中,线程池的处理流畅如下:
RejectedExecutionHandler:饱和策略
当队列和线程池都满了,说明线程池处于饱和状态,就要采用饱和策略。默认的策略是AbortPolicy,即无法处理新的任务而抛出异常。Java的四种策略:
备注:
一般如果线程池任务队列采用LinkedBlockingQueue队列的话,那么不会拒绝任何任务(因为队列大小没有限制),这种情况下,ThreadPoolExecutor最多仅会按照最小线程数来创建线程,也就是说线程池大小被忽略了。
如果线程池任务队列采用ArrayBlockingQueue队列的话,那么ThreadPoolExecutor将会采取一个非常负责的算法,比如假定线程池的最小线程数为4,最大为8所用的ArrayBlockingQueue最大为10。随着任务到达并被放到队列中,线程池中最多运行4个线程(即最小线程数)。即使队列完全填满,也就是说有10个处于等待状态的任务,ThreadPoolExecutor也只会利用4个线程。如果队列已满,而又有新任务进来,此时才会启动一个新线程,这里不会因为队列已满而拒接该任务,相反会启动一个新线程。新线程会运行队列中的第一个任务,为新来的任务腾出空间。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize){
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize){
super(corePoolSize,Integer.MAX_VALUE,0,TimeUnit.NANOSECONDS,
new DelayedWorkQueue());
}
Java线程具有5种基本状态:
注:
就绪状态转换为运行状态:当此线程得到处理器资源
运行状态转换为就绪状态:当此线程主动调用yield()或运行过程中失去处理器资源
运行状态转换为死亡状态:当此线程执行完毕或发生异常