Callable接口:
实现Thread的方法:
1.继承Thread
2.实现Runnable
3.基于lambda
4.基于Callable
5.基于线程池
Runnable和Callable区别:
Runnable关注过程,不关注结果,Runnable提供的run方法返回值为void。
Callable则关注结果,提供的call方法,返回值是就是线程执行任务得到的结果。
但是Callable不可以直接传入Thread,需要一个FutureTask来实现转换。.
下面是一个使用Callable接口来实现从1加到100的计算
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Thread00 {
public static void main(String[]args) throws InterruptedException, ExecutionException {
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int result = 0;
for (int i = 0; i <= 100 ; i++) {
result += i;
}
return result;
}
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
System.out.println(futureTask.get());
}
}
ReentrantLock:
ReentrantLock也就是可重入锁,在早期Java中,synchronized不够强大时,用来实现可重入锁的(现在synchronized已经实现可重入锁的功能了)
这个对象提供了两种方法:lock,unlock。
但是这种写法容易引起加锁后忘记解锁,或者在unlock之前触发了return或者异常,也会导致unlock执行不到。需要把unlock操作放入finally中。
ReentrantLock现在存在的意义:
1.ReentrantLock提供了tryLock操作。
lock直接进行加锁,如果加锁失败,就会阻塞。trylock,尝试进行加锁,如果加锁失败,不阻塞,直接返回false。
2.ReentrantLock提供了公平锁的实现,
在ReentrantLock的构造方法中填写对应参数,就可以设置为公平锁。
3.搭配的等待通知机制不同于synchronized。
synchronized搭配wait/notify。
ReentrantLock搭配Condition类,功能比wait/notify强一点。
信号量Semaphore
锁本质上也是一种特殊的信号量,计数值为1的二元信号量。释放状态为1,加锁状态为0 。
以下就是提供创建一个Semaphore来模拟线程加锁的例子
import java.util.concurrent.Semaphore;
public class Thread02 {
private static int count = 0;
public static void main(String[]args) throws InterruptedException {
Semaphore semaphore = new Semaphore(1);
Thread t1 = new Thread(()->{
for (int i = 0; i <= 100; i++) {
try {
semaphore.acquire();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
count+=i;
semaphore.release();
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i <= 100 ; i++) {
try {
semaphore.acquire();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
count+=i;
semaphore.release();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}
CountDownLatch
针对特定场景解决问题的小工具
比如:多线程执行一个任务,把大的任务拆成几个部分,由每个线程分别执行。比如多线程下载。
像多线程下载的场景,最终执行完成后,要把所有内容拼到一起,而要拼到一起,就要等到所有线程执行完毕,使用CountDownLatch就可以很方便的感知到这个事情,比多次使用join要方便很多。
以下是模拟多线程下载的代码
import java.util.Random;
import java.util.concurrent.CountDownLatch;
public class Thread03 {
public static void main(String[]args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
int id = i;
Thread t = new Thread(()->{
Random random = new Random();
int time = (random.nextInt(5)+1)*1000;
System.out.println("线程 "+id+"开始执行");
try {
Thread.sleep(time);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("第"+id+"个任务执行完毕");
latch.countDown();//告知 CountDownLatch 该线程已执行完毕
});
t.start();
}
latch.await();//阻塞,直到所有任务结束
System.out.println("所有任务都已经执行完毕");
}
}
线程安全的集合类
Vector,Stack,Hashtable,他们把关键方法加上了synchronized,但是他们在单线程时也加上了锁,会造成资源浪费,所以官方不建议使用了。
CopyOnWriteArrayList:写时拷贝。如果多个线程读这个集合时是没问题的,但是如果有线程想要修改这里的值时,它就会将顺序表复制一份,修改新的顺序表内容,并且修改引用的指向。但是也有局限性:修改不能太频繁;顺序表不能太大。
多线程使用队列
1.自己加锁。
2.BlockingQueue
多线程使用哈希表
Hashtable:在关键方法上添加了synchnorized。在此基础上,标准库引入了更好的ConcurrentHashMap
ConcurrentHashMap:1.缩小了锁的粒度。2.充分的使用了CAS原子操作,减少了加锁。3.针对扩容操作的优化。