Java中创建线程有4种方式:
继承Thread类,重写run()接口。
/*类继承*/
class NewThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开启。。。");
}
}
public static void main(String[] args) {
//类继承调用
NewThread newThread = new NewThread();
newThread.start();
//匿名内部类
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"线程开启。。。");
}).start();
}
实现Runnable接口,重写run()。(Java不可多继承)
//实现Runnable接口
class NewRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开启。。。");
}
}
public static void main(String[] args) {
NewRunnable newRunnable = new NewRunnable();
new Thread(newRunnable).start();
//匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开启。。。");
}
}).start();
}
实现Callable接口,重写call()方法,任务交由FutureTask,由线程处理,能够支持返回值。
//这里Callable<T> 决定返回值类型
class NewCallable implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName()+"线程开启。。。");
return Thread.currentThread().getName();
}
}
public static void main(String[] args) {
NewCallable newCallable = new NewCallable();
FutureTask<String> task = new FutureTask<>(newCallable);
Thread thread = new Thread(task);
thread.start();
try {
System.out.println("获取task返回值:"+task.get());
} catch (Exception e) {
e.printStackTrace();
}
}
Executors.newXXXXXPool()其底层都是通过ThreadPoolExecutor进行创建,根据《阿里巴巴Java开发手册》,推荐使用自定义ThreadPoolExecutor能够对线程池更深入的把控。
更详细关于线程池内容直接看:ThreadPoolExecutor线程池详解与使用
private static ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(5);
public static void main(String[] args) {
threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+"启动了。。。");
});
}
sleep()
使当前执行的线程休眠,但不会释放锁。先进入阻塞态,待休眠时间结束后,后转入就绪态等待执行机会。
//休眠5秒
Thread.sleep(5000);
join()
非静态方法,让一个线程等待另外一个线程完成才继续执行。
public static void main(String[] args) throws InterruptedException {
//线程a睡眠2秒
Thread a = new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"执行中");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"阻塞结束");
},"a");
//启动线程
a.start();
//main线程阻塞,等待a线程处理完成
a.join();
System.out.println(Thread.currentThread().getName()+"结束");
}
//输出结果如下:
//a执行中
//a阻塞结束
//main结束
yield()
当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程。yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!
countDown():计数器减1await():直到计数器为0时,线程继续执行,否则是被阻塞状态。await(long timeout, TimeUnit unit):直到计数器为0或超过指定时间内,线程继续执行,否则是被阻塞状态。Sync继承AbstractQueuedSynchronizer,维护volatile修饰的全局变量state。Sync类。CountDownLatch()对象时,调用new Sync(count)初始化Sync类,调用AbstractQueuedSynchronizer的setState(newState)方法,赋值全局变量statecountDown()方法时,调用AbstractQueuedSynchronizer的sync.releaseShared(1)方法。
tryReleaseShared(releases)通过for循环配合CAS尝试进行-1操作,当执行完成返回nextc == 0;当执行完成结果为0返回true(为0代表计数器结束),否则返回false。tryReleaseShared(releases)结果,若为true,则进入doReleaseShared(),返回true;否则返回false。await()方法时,调用sync.acquireSharedInterruptibly(1);
InterruptedException()异常tryAcquireShared()方法,尝试获取state值,如果为0则返回1;否则返回-1。
doAcquireSharedInterruptibly()方法,阻塞当前线程。 CyclicBarrier的应用场景为:实现一组线程相互等待,当所有线程都达到某个屏障点后再进行后续操作。
似乎和CountDownLatch差不多?
这个疑问在我一开始理解他的概念时就冒出来了,后来随着深入学习,发现他与CountDownLatch的使用区别在于:CyclicBarrier可以实现循环拦截。
CyclicBarrier是基于ReentrantLock与Condition组合使用。
CyclicBarrier原理
线程调用await(),告诉CyclicBarrier到达屏障,然后线程阻塞;等到所有线程达到屏障count==0,结束阻塞,继续线程后续逻辑。await()核心调用dowait()方法。
dowait()方法
ReentrantLock加锁BrokenBarrierException()异常generation.broken = true;);当前剩余需要拦截到数量置为拦截的总数量(count = parties;);唤醒所有等待的线程;抛出InterruptedException()异常int index = --count;generation.broken = true;);当前剩余需要拦截到数量置为拦截的总数量(count = parties;);唤醒所有等待的线程trip.await();否则进入nanos = trip.awaitNanos(nanos);,返回剩余时间generation.broken = true;);当前剩余需要拦截到数量置为拦截的总数量(count = parties;);唤醒所有等待的线程;抛出异常Thread.currentThread().interrupt();BrokenBarrierException()异常判断说明已换代,返回index= --count`timed && nanos <= 0L说明线程超时,则:当前屏障打破(generation.broken = true;);当前剩余需要拦截到数量置为拦截的总数量(count = parties;);唤醒所有等待的线程;抛出TimeoutException Semaphore可以控制同时访问共享资源的线程个数,线程通过 acquire方法获取一个信号量,信号量减一,如果没有就等待;通过release方法释放一个信号量,信号量加一。它通过控制信号量的总数量,以及每个线程所需获取的信号量数量,进而控制多个线程对共享资源访问的并发度,以保证合理的使用共享资源。相比synchronized和独占锁一次只能允许一个线程访问共享资源,功能更加强大
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据, 如果第一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
因此使用Exchanger的重点是成对的线程使用exchange()方法,当有一对线程达到了同步点,就会进行交换数据。因此该工具类的线程对象是成对的。 Exchanger类提供了两个方法,String exchange(V x):用于交换,启动交换并等待另一个线程调用exchange;String exchange(V x,long timeout,TimeUnit unit):用于交换,启动交换并等待另一个线程调用exchange,并且设置最大等待时间,当等待时间超过timeout便停止等待。
这两个类都可以实现一组线程达到某个条件之前进行等待,内部都有计数器,当计数器为0时被阻塞的线程会被唤醒。
区别:
CyclicBarrier计数器由await()方法控制,CountDownLatch计数器由countDown()方法控制。CyclicBarrier可以实现循环拦截,CountDownLatch则只能拦截一轮。 ThreadLocal用于线程间的数据隔离,为每一个线程都提供了数据副本,使得不同线程访问的数据不是同一个对象!!!
每个Thread对象都有一个ThreadLocalMap,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象添加到该Map中,其中键就是ThreadLocal,值可以是任意类型。
预先启动一些线程,线程无限循环从任务队列中获取一个任务进行执行,直到线程池被关闭。如果某个线程因为执行某个任务发生异常而终止,那么重新创建一个新的线程而已,如此反复。
根据《阿里巴巴Java开发手册》,推荐使用第7种方式。
1、 newSingleThreadExecutor():它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目;
2、 newCachedThreadPool():它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列;
3、 newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads;
4、newSingleThreadScheduledExecutor():创建单线程池,返回 ScheduledExecutorService,可以进行定时或周期性的工作调度;
5、newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()类似,创建的是个ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程;
6、newWorkStealingPool(int parallelism):这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序;
7、ThreadPoolExecutor():是最原始的线程池创建,上面创建方式都是对ThreadPoolExecutor的封装。
线程的生命周期分为5个阶段:
start()方法后,该线程就处于就绪状态。run()方法,系统分配时间内线程都是运行状态。stop()或run()方法执行完成,或抛出异常或错误,线程进入死亡状态,不会再转入其他状态。