多线程JDK1.5之后的特性:java.util.concurrent.*; java并发包
创建线程的三种方式:
1)继承 extends Thread类,并重写 void run() 方法
2)实现 implements Runnable接口。并重写 void run() 方法
3)实现 implements Callable接口。并重写 Object call() 方法
Runnable 和 Callable的区别:
(1)Runnable没有返回值 void , Callable 有返回值 Object
(2) Runable不支持抛出异常,只能 try-catch, 而 Callable 支持抛出异常 throws Exception
源码:
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
public interface Callable<V> {
V call() throws Exception;
}
例:创建一个线程
//Callable代表线程要要执行的代码
FutureTask task = new FutureTask(new Callable(){
public Object call(){
//要执行的代码块...
}
});
Thread t = new Thread(task);
//启动线程
t.start();
//获取 call() 方法的返回值
Object o = task.get();
线程池:创建有限线程资源为更多的任务提供服务的(享元模式) 好处:实现了对线程的重复使用
java中对线程池的抽象:ExecutorService 创建一个固定大小的线程池
//创建一个固定大小的线程池 10 个线程
ExecutorService threadpool = Executors.newFixedThreadPool(10);
for(int i=0; i<10; i++){
threadpool.submit(() -> {//向线程池提交一个线程
System.out.println(Thread.concurrent().getName());//获取当前正在运行的线程的名字
Thread.sleep(1000);//让当前线程休眠1秒
});
}
//关闭线程池,不再接受新的任务,当所有线程都执行完时,线程池关闭
threadpool.shutdown();
ExecutorService的核心实现类: ThreadPoolExecutor
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
参数列表:
1)int corePoolSize: 线程核心数目(即线程池里最多保留多少个线程)
2)int maximumPoolSize: 线程池最大线程数 corePoolSize + 救急线程 <= maximum
救急线程:当任务过多,连阻塞队列都满了,这时线程池会创建新的线程来救急
3)long keepAliveTime: 当线程执行完之后,最多存活的时间,针对于 救急线程
4)TimeUnit unit: 救急线程的存活时间的单位
5)BlockingQueue<Runnable> workQueue : 阻塞队列,如果超过了corePoolSize的数量,
其他线程就会进入阻塞队列进行排队
线程池的大小 = maximumPoolSize + 阻塞队列的大小 若超过线程池的大小,则会抛出 RejectedExecutionException
创建线程池的四种方式:
1)ExecutorService threadpool = Executors.newFixedThreadPool( 10 );
创建 固定大小 的线程池: 核心线程数 = 最大线程数 (没有救急线程)
特点:阻塞队列无上限,可存放任意数量的任务
适合执行数量有限,长时间运行的任务
2)ExecutorService threadpool = Executors.newCachedThreadPool();
创键 缓冲 的线程池: 核心线程数 = 0
特点: 最大线程数是 Integer 的最大值 ( 2的32次方 - 1),救急线程可无限被创键,生存时间是 60 s
适合任务数比较密集,但任务执行时间较短的情况
3)ExecutorService threadpool = Executors.newSingleThreadPool();
创键 单线程 线程池 : 核心线程数 = 1
特点: 核心线程数为 1,且不能修改
区别:
newFixedThreadPool( 1 ): 虽然线程数固定为 1, 但之后可修改
newSingleThreadPool( ): 线程数固定,且不能修改
4)ExecutorService threadpool = Executors.newScheduledThreadPool( 10 )
创建带有 日期安排 的线程池
// 创建一个带有 日程安排的线程
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
// 让任务推迟一段时间执行 , 参数1.任务对象, 参数2,3 推迟的时间
service.schedule(()->{ System.out.println("执行任务..."); } , 10L, TimeUnit.SECONDS);
// 以一定的频率反复执行任务(任务不会重叠)
service.scheduleAtFixedRate(()->{
try {
Thread.sleep(1200);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 参数1,任务对象, 参数2,初始推迟时间, 参数3,4 时间间隔和单位
System.out.println("hello");}, 0, 1, TimeUnit.SECONDS);
// delay表示从上一个任务结束,到下一个任务开始之间的时间 为 1 秒
service.scheduleWithFixedDelay(()->{
System.out.println("hello");},
0, 1, TimeUnit.SECONDS);
// service.shutdown();
原子类操作: jdk1.5之后,利用了现代处理器的特性
可以用非阻塞的方式完成原子操作
原子类有:AtomicXXX
AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicLong
AtomicLongArray
......
// 创建原子整数类
private static AtomicInteger i = new AtomicInteger(0);//相当于 int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
i.getAndIncrement(); // 获取并且自增 i++
//i.incrementAndGet(); // 自增并且获取 ++i
}
});
Thread t2 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
i.getAndDecrement(); // 获取并且自减 i--
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
线程安全集合类:
StringBuffered, String, Vector , HashTable, Random... 都是线程安全的
JDK1.5之后: 新增的线程安全集合类
1)ConcurrentHashMap: 实现了Map 集合, 线程安全
原理图:
2)CopyOnWriteArray: 实现了List,线程安全
原理图:
3) ConcurrentSkipListMap : 实现了Map(可排序),且线程安全
4) BlockingQueue: 阻塞队列,先进先出
阻塞队列的应用:
class Test{
//创建一个阻塞队列
private static BlockingQueue<Product> queue = new ArrayBolckingQueue<>( 5 );
public static void main(String[] args){
//创建生产者线程
new Thread(() -> {
for(int i=0; i<10; i++){
Product p = new Product();//生成者生产产品
System.out.println(Thread.currentThread().getName()+"生产了"+p);
try{
queue.put(p);//添加到阻塞队列中
}catch(InterruptedException e){
e.printStackTrace();
}
}
}).start();
//创建消费者线程
for(int i=0; i<5; i++){
new Thread(()->{
for(int j=0; j<2; j++){ //一次消费两个产品
try{
Product p = queue.take(); // 从阻塞队列中取到产品
System.out.println(Thread.currentThread().getName()+"消费了"+p);
} catch(InturrptedException e ){
e.printStackTrace();
}
}
}).start();
}
}
}
ThreadLocal: 线程本地变量 ,让每个线程各用个的资源,防止资源的争用 这种变量在多线程环境下访问时能够保证各个线程里变量的独立性 可实现不用synchronized就能保证线程安全
//线程局部变量
private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal() {
@Override // 初始值
protected Object initialValue() {
return new SimpleDateFormat("yyyy-MM-dd"); // 存入当前线程
}
};
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
SimpleDateFormat sdf = local.get(); // 获取本线程自己的局部变量
Date date = sdf.parse("1951-10-09"); // 每个线程使用的是自己的
SimpleDateFormat因此没有争用
System.out.println(Thread.currentThread().getName() + " " + date);
} catch (ParseException e) {
e.printStackTrace();
}
}).start();
}
}
CAS 机制(Compare And Swap): 比较并交换 乐观锁,如果修改失败,没关系,重新尝试
不使用 synchronized 就实现了线程同步
(乐观锁)CAS 思想: 首先不会给共享资源加锁,而是做一个尝试, 先拿到旧值,查看旧值是否跟共享区域的值相等
如果不等,那么说明别的线程改动了共享区域的值,我的修改失败, 如果修改失败,没关系,重新尝试
如果相等,那么就让我的修改成功
悲观锁:就像synchronized, 改动之前必须添加锁,防止别人修改
1)ReentrantLock: 重入锁
.lock(): 给当前对象进行上锁,让其在操作时保证元素的原子性
.unlock(): 将当前对象的锁解开
******** Synchronized 和 ReentrantLock 的区别???
1)Synchronized 是 虚拟机层面支持的,是关键字
而ReentrantLock 是 JDK 层面支持的,是类
2)性能上早期ReentrantLock要优越,但内存占用高,ReentrantLock灵活性更高,可以加入公平锁、锁条件等功能, 实现更多的控制。而在更高版本的jdk下synchronized有显著提升,接近ReentrantLock,仅在并发高时不如。
static int i = 0;
public static void main(String[] args) throws InterruptedException {
ReentrantLock rl = new ReentrantLock(); // 创建 重入锁 对象
Thread t1 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
try {
rl.lock(); // 加锁
i++;
} finally {
rl.unlock(); // 保证解锁一定被执行
}
}
});
Thread t2 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
try {
rl.lock(); // 加锁
i--;
} finally {
rl.unlock(); // 保证解锁一定被执行
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
2)CountDownLatch: 闭锁,倒计时锁,目的是 只有当多个线程都执行完毕后,
才能继续向下执行
public static void main(String[] args) throws InterruptedException {
// 构造方法需要指定倒计时的数字
CountDownLatch cdl = new CountDownLatch(3);
new Thread(()->{
System.out.println("线程1开始运行"+new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1准备完成"+new Date());
cdl.countDown();
}).start();
new Thread(()->{
System.out.println("线程2开始运行"+new Date());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2准备完成"+new Date());
cdl.countDown();
}).start();
new Thread(()->{
System.out.println("线程3开始运行"+new Date());
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程3准备完成"+new Date());
cdl.countDown();
}).start();
// 主线程等待,直到倒计时为0
System.out.println("主线程等待");
cdl.await();
System.out.println("ready go....");
}
3)CyclicBarrier: 可循环的屏障(栅栏),只有线程数达到指定数时,才执行,
否则继续等待到满足为止
*********CyclicBarrier 与 CountDownLatch 的区别???
CyclicBarrier : (循环栅栏) 允许一组线程相互等待,直到到达栅栏屏障点,栅栏可以重用
CountDownLatch:(倒计时锁) 是让一个线程等待其它任务(线程)执行完毕,结束倒计时后不能重用
// CyclicBarrier 可循环的 屏障(栅栏)
// 当满足CyclicBarrier设置的线程个数时,继续执行,没有满足则等待
CyclicBarrier cb = new CyclicBarrier(2); // 个数为2时才会继续执行
new Thread(()->{
System.out.println("线程1开始.."+new Date());
try {
cb.await(); // 当个数不足时,等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("线程1继续向下运行..."+new Date());
}).start();
new Thread(()->{
System.out.println("线程2开始.."+new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
cb.await(); // 2 秒后,线程个数够2,继续运行
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("线程2继续向下运行..."+new Date());
}).start();
4)Semapore: 信号量: 限制能同时运行的线程,当达到限制数时,线程同时执行,
继续等到又达到限制数时,才继续执行
// 限制了能同时运行的线程上限
Semaphore s = new Semaphore(3);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
s.acquire(); // 获得此信号量
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
s.release(); // 释放信号量
}
}).start();
}