线程的状态
New New状态是线程实例化后还没有执行start()方法的状态。
new Thread().getState();
RUNNABLE 线程进入运行的状态。
Thread t1 = new Thread(); t1.start();
TIMED_WAITING 有时间限制的等待。
Thread.sleep(XXX)
WAITING 线程执行了 lock.wait() 后的状态。永久等待,直到被另外一个线程lock.notify()
lock.wait()
BLOCKED 当一个线程在等待同步锁时,线程状态为BLOCKED
TERMINATED 当任务执行完毕后的状态
Callable接口与Runnable接口的区别
public interface Callable {
V call() throws Exception;
}
public interface Runnable {
public abstract void run();
}
(1)Callable接口的方法是call(),Runnable接口的方法是run()
(2)Callable的任务执行后可返回值,而Runnable的任务执行后没有返回值
(3)Callable的任务执行中可以抛异常,而Runnable的任务不能抛出异常
异步调用Future模式
java.util.concurrent.FutureTask
FutureTask必须要先执行run()方法,得出结果后,再调用get()方法可返回结果
public static void main(String[] args) throws ExecutionException,
InterruptedException {
Callable c = ()->{
System.out.println("睡两秒");
Thread.sleep(2000);
return "as";
};
FutureTask task = new FutureTask<>(c);
//必须要先执行run才有结果,否则get()将一直等待
task.run();
System.out.println(task.get());
}
FutureTask包装的是Callable或者Runnable,用的是构造函数接收的。因为FutureTask实现了Runnable接口,所以可以提交给ThreadPoolExecutor的execute(Runnable)执行。
public static void main(String[] args) throws InterruptedException,
ExecutionException {
Callable task = () -> {
//模拟计算时间
Thread.sleep(5000);
return "Callable接口实现的返回结果";
};
//创建FutureTask来包装Callable接口实现
FutureTask futureTask = new FutureTask<>(task);
//创建线程池准备执行任务
ExecutorService service = Executors.newFixedThreadPool(1);
//执行任务,线程池将会分配一个线程去执行指定的任务
service.execute(futureTask);
//主线程执行其它任务
Thread.sleep(2000);
System.out.println("主线程执行其它任务花费了2秒");
//主线程需要子线程任务的结果
String result = futureTask.get();
System.out.println("FutureTask任务的执行结果是:"+result);
//关闭线程池
service.shutdown();
线程池ThreadPoolExecutor:
创建多个线程,会消耗许多内存,也非常耗时。
线程池:创建线程变成了从线程池获取空闲的线程,关闭线程变成了向池子中归还线程。
降低内存资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。在线程池中的线程都是已经被创建好的,我们的任务直接获取一个空闲的线程就能够被执行了。
ThreadPoolExecutor 的 execute(Runnable)方法 与父类ExecutorService的submit方法的区别
ThreadPoolExecutor # execute(..) 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。
ExecutorService # submit(..) 方法用于提交需要返回值的任务。线程池会返回一个 future 类型的对象,通过这个 future 对象可以判断任务是否执行成功,并且可以通过 future 的 get() 方法来获取返回值,get() 方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
线程计数器CountDownLatch
线程计数器CyclicBarrier
构造函数接收计数器的值。
被计数的任务线程执行完毕后,记得调用一次 latch.countDown()方法,提示CountDownLatch对象,当前线程已经执行完毕。
在需要同步的地方,使用 latch.await()方法进行阻塞。再次恢复任务执行,需要CountDownLatch的计数器为0。
缺点:CountDownLatch这个类的缺点就很明显,如果子线程耗时过多,那么主线程也会一直等待,程序执行效率大大降低。
开始执行任务前,等待 N 个前置线程完成各自的准备任务:例如应用程序启动类要确保在处理用户请求前,所有 N 个外部系统已经启动和运行了。
实际用法:用于等待所有多线程执行的定时任务执行完毕后,打印日志耗时
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
private static final int THREAD_COUNT_NUM = 7;
private static CountDownLatch latch = new CountDownLatch(THREAD_COUNT_NUM);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < THREAD_COUNT_NUM; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "执行");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//每个线程执行完毕,都把计数器减1
latch.countDown();
}, "Thread" + i).start();
}
//CountDownLatch.await()阻塞主函数。
//那么,主函数需要等到 latch.countDown()被调用七次后,方可恢复执行
latch.await();
System.out.println("主函数阻塞结束");
/**
* Thread1执行
Thread3执行
Thread5执行
Thread0执行
Thread2执行
Thread4执行
Thread6执行
主函数阻塞结束
*/
}
}
无锁机制CAS (compareAndSwap)
加锁是一种悲观的策略,它总是认为每次访问共享资源的时候,总会发生冲突,所以宁愿牺牲性能(时间)来保证数据安全
无锁是一种乐观的策略,无锁的策略使用一种叫做比较交换的技术(CAS Compare And Swap)来鉴别线程冲突,一旦检测到冲突产生,就重试当前操作直到没有冲突为止。
无锁操作即CAS操作天生免疫死锁。
JUC包中有一个atomic包,它们是原子操作类
public final int incrementAndGet(){
for(;;){
int current = get();
int next = current + 1;
if(compareAndSet(current,next)){
return next;
}
}
}
ThreadLocal
每一个线程都可以独立地维护自己的副本
锁性能优化
一、减少线程持有锁的时间,例如可以把同步方法修改为同步代码块
二、锁分段:将一个对象的数据分割成多个部分,并为每个部分分配一把锁。ConcurrentHashMap
三、锁分离:读写锁ReentranceReadWriteLock,读读互不影响