方法签名 | 描述 |
---|---|
void lock(); | 获取锁(不死不休) |
boolean tryLock(); | 获取锁(浅尝辄止) |
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; | 获取锁(过时不候) |
void lockInterruptibly() throws InterruptedException; | 获取锁(任人摆布) |
void unlock(); | 释放锁 |
Condition newCondition(); |
结论:
1、lock()最常用;
2、lockInterruptibly()方法一般更昂贵,有的impl可能没有实现lockInterruptibly(),只有真的需要效应中断时,才使用,使用之前看看impl对该方法的描述。
优点:
缺点:无法实现一些锁的高级功能如:公平锁、中断锁、超时锁、读写锁、共享锁等。
优点:
缺点:需手动释放锁unlock,新手使用不当可能造成死锁。
Object中的wait(),notify(),notifyAll()方法是和synchronized配合使用的,可以唤醒一个或者全部(单个等待集);Condition是需要与Lock配合使用的,提供多个等待集合,更精确的控制(底层是park/unpark机制);
协作方式 | 死锁方式1(锁) | 死锁方式2(先唤醒,再挂起) | 备注 |
---|---|---|---|
suspend/resume | 死锁 | 死锁 | 已弃用 |
wait/notify | 不死锁 | 死锁 | 只用于synchronized关键字 |
park/unpark | 死锁 | 不死锁 |
实例:
package com.dongnao.concurrent.period6;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo3_Condition {
private static Lock lock = new ReentrantLock();
//condition来自哪个lock,signal方法的就要在相应的lock的lock()方法之后,唤醒的是被该lock锁住且被condition.await()阻塞的线程
private static Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
Thread th = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
System.out.println("子线程或得锁...\n");
try {
System.out.println("开始wait...\n");
condition.await(); // waiting park
//await方法会释放lock锁,和waiting会释放synchronized类似
System.out.println("main unlock后,wait才结束...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
th.start();
Thread.sleep(4000L);
lock.lock();
System.out.println("唤醒子线程...");
//这个方法必须在lock.lock之后调用,否则会抛出异常,唤醒的是condition.signal()前面被lock.lock()中的lock锁锁住且被condition.await()阻塞的线程
condition.signal();
Thread.sleep(10000L);
lock.unlock();
System.out.println("main unlock...\n");
}
}
子线程或得锁...
开始wait...
唤醒子线程...
main unlock...
main unlock后,wait才结束...
等待队列是一个FIFO的队列,在队列中的每个节点都包含了一个线程引用,该线程就是在Condition对象上等待的线程,如果一个线程调用了Condition.await()方法,那么该线程将会释放锁、构造成节点加入等待队列并进入等待状态。
一个Condition包含一个等待队列,新增节点只需要将原有的尾节点nextWaiter指向它,并且更新尾节点即可。
调用Condition的signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中。
调用该方法的前置条件是当前线程必须获取了锁,可以看到signal()方法进行了isHeldExclusively()检查,也就是当前线程必须是获取了锁的线程。接着获取等待队列的首节点,将其移动到同步队列并使用LockSupport唤醒节点中的线程。
通过调用同步器的enq(Node node)方法,等待队列中的头节点线程安全地移动到同步队列。
Condition的signalAll()方法,相当于对等待队列中的每个节点均执行一次signal()方法,效果就是将等待队列中所有节点全部移动到同步队列中,并唤醒每个节点的线程。
ThreadLocal本质是个map,map的键就是每个线程对象,值就是在每个线程所设置的值。
常用方法:
initialValue()
get()
set()
remove():
ThreadLocal拥有的这个变量,在线程之间很独立的,相互之间没有联系。内存占用相对来说比较大。
package period1;
public class Demo5_ThreadLocal {
/** threadLocal变量,每个线程都有一个副本,互不干扰 */
public static ThreadLocal value = new ThreadLocal<>();
public void threadLocalTest() throws Exception {
// threadlocal线程封闭示例
value.set("123"); // 主线程设置值
String v = value.get();
System.out.println("主线程,v:" + v);
new Thread(new Runnable() {
@Override
public void run() {
String v = value.get();
System.out.println("子线程:线程1取到的值:" + v);
// 设置 threadLocal
value.set("456");
v = value.get();
System.out.println("子线程:重新设置之后,线程1取到的值:" + v);
System.out.println("子线程 执行结束");
}
}).start();
Thread.sleep(12000L); // 等待所有线程执行结束
value.set("vvv"); // 主线程设置值
v = value.get();
System.out.println("主线程,v:" + v);
}
public static void main(String[] args) throws Exception {
new Demo5_ThreadLocal().threadLocalTest();
}
}
运行结果
主线程,v:123
子线程:线程1取到的值:null
子线程:重新设置之后,线程1取到的值:456
子线程 执行结束
主线程,v:vvv
并行执行任务的框架,把大任务拆分成很多的小任务,汇总每个小任务的结果得到大任务的结果。
1、ForkJoinTask:我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务中执行fork()和join()操作的机制。通常情况下,我们不需要直接继承ForkJoinTask类,只需要继承它的子类,Fork/Join框架提供了以下两个子类。
·RecursiveAction:用于没有返回结果的任务。
·RecursiveTask:用于有返回结果的任务。
2、ForkJoinPool:ForkJoinTask需要通过ForkJoinPool来执行。
执行流程梳理:
用例;
package com.dongnao.concurrent.period9;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
public class ForkJoinTest {
static ArrayList urls = new ArrayList(){
{
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
add("http://www.baidu.com");
add("http://www.sina.com");
}
};
// 本质是一个线程池,默认的线程数量:CPU的核数
static ForkJoinPool forkJoinPool = new ForkJoinPool(3,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null,
true);
public static void main(String args[]) throws ExecutionException, InterruptedException {
Job job = new Job(urls, 0, urls.size());
ForkJoinTask forkjoinTask = forkJoinPool.submit(job);
String result = forkjoinTask.get();
System.out.println(result);
}
public static String doRequest(String url, int index){
//模拟网络请求
return (index + "Kody ... test ... " + url + "\n");
}
static class Job extends RecursiveTask{
List urls;
int start;
int end;
public Job(List urls, int start, int end){
this.urls = urls;
this.start = start;
this.end = end;
}
@Override
protected String compute() {
int count = end -start; //计算这个任务有多大
//什么时候对任务进行拆分
if (count <=10){
//直接执行
String rsult = "";
for (int i=start; i< end;i++){
String response = doRequest(urls.get(i), i);
rsult +=response;
}
return rsult;
}else{
//拆分任务
int x = (start + end) / 2;
Job job1 = new Job(urls, start, x);
job1.fork();
Job job2 = new Job(urls, x, end);
job2.fork();
//固定写法
String result = "";
result +=job1.join();
result += job2.join();
return result;
}
}
}
}
允许一个或多个线程等待其他线程完成操作。CountDownLatch的构造函数接收一个int类型的参数作为计数器,如果你想等待N个点完成,这里就传入N。当我们调用CountDownLatch的countDown方法时,N就会减1,CountDownLatch的await方法会阻塞当前线程,直到N变成零。由于countDown方法可以用在任何地方,所以这里说的N个点,可以是N个线程,也可以是1个线程里的N个执行步骤。用在多个线程时,只需要把这个CountDownLatch的引用传递到线程里即可。
调用countDownd方法计数器减一。
package com.dongnao.concurrent.period4_1;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
//Count Down Latch 倒计时开关
//CountDownLatch的基本使用
public class Demo6_CountDownLatch {
static AtomicLong num = new AtomicLong(0);
public static void main(String args[]) throws InterruptedException {
CountDownLatch ctl = new CountDownLatch(10);
for (int i=0; i< 10; i++){
new Thread(){
@Override
public void run() {
for (int j=0; j< 10000000; j++){
num.getAndIncrement();
}
ctl.countDown();//计数器减一
}
}.start();
}
ctl.await(); //设置开关,设置门栓,当计数器变为0时,开关打开
System.out.println(num.get());
}
}
Semaphore是一个计数信号量,常用于限制可以访问某些资源(物理或逻辑的)线程数目。
简单说,是一种用来控制并发量的共享锁。
如下图,当有多个并发过来的时候,只有一定数量的线程能过通过运行,其他线程只能等待。
Semaphore构造函数指定通道数量,调用acquire方法获取一个信号量, 即抢占一个访问通道,调用release方法,释放信号量, 即通道释放。还可以用tryAcquire()方法尝试获取信号量。
package com.dongnao.concurrent.period4_1;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.LockSupport;
public class Demo9_Semaphore {
public static void main(String args[]){
Semaphore sp = new Semaphore(10);//同一时间只能有10个通道
//这么短的时间,发送1000个请求,并发会很高,数据库会受不了
for (int i=0; i<1000; i++){
new Thread(){
@Override
public void run() {
try {
sp.acquire();//获取一个信号量, 即抢占一个访问通道
} catch (InterruptedException e) {
e.printStackTrace();
}
queryDb("localhost:3306"); //模拟DB查询
sp.release();//释放信号量, 讲通道释放
}
}.start();
}
}
//发送一个HTTP请求
public static void queryDb(String uri) {
System.out.println("do query...:" + uri);
LockSupport.parkNanos(1000 * 1000 * 1000);
}
}
Semaphore还提供一些其他方法,具体如下:
循环栅栏,可以循环利用的屏障。等待筹齐一定数量的线程之后再一起运行。
举例:排队上摩天轮时,每到齐四个人,就可以上同一个车厢。
package com.dongnao.concurrent.period4_1;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.locks.LockSupport;
public class Demo10_CyclicBarrier {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(4,
new Runnable() {
@Override
public void run() {
System.out.println(">>> 这是一个栅栏。。。");
}
});
//传入一个Runnable,打印栅栏
for (int i=0; i< 100; i++){
new Thread(){
@Override
public void run() {
try {
//LockSupport.parkNanos(1000 * 1000 * 1000 * 10L);
barrier.await(); //
System.out.println("上到摩天轮...");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}.start();
LockSupport.parkNanos(1000 * 1000 * 1000L);
}
}
}
输出结果:
>>> 这是一个栅栏。。。
上到摩天轮...
上到摩天轮...
上到摩天轮...
上到摩天轮...
>>> 这是一个栅栏。。。
上到摩天轮...
上到摩天轮...
上到摩天轮...
上到摩天轮...
>>> 这是一个栅栏。。。
上到摩天轮...
上到摩天轮...
上到摩天轮...
上到摩天轮...
>>> 这是一个栅栏。。。
上到摩天轮...
上到摩天轮...
上到摩天轮...
上到摩天轮...
>>> 这是一个栅栏。。。
。。。。
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
一定发生在多个线程争夺多个资源里的情况下,发生的原因是每个线程拿到了某个(某些)资源不释放,同时等待着其他线程所持有的资源。解决死锁的原则就是确保正确的获取资源的顺序,或者获取资源时使用定时尝试机制。
常见的死锁:简单顺序死锁、动态顺序死锁。
预防简单顺序死锁的方法是保证正确的加锁顺序,不同线程抢占不同锁的顺序一致就不会造成死锁。
public class NormalTransfer implements ITransfer{
@Override
public void transfer(Account from, Account to, int amount)
throws InterruptedException {
synchronized (from){
System.out.println(Thread.currentThread().getName()+" get "+from.getName());
Thread.sleep(100);
synchronized (to){
System.out.println(Thread.currentThread().getName()
+" get "+to.getName());
from.flyMoney(amount);
to.addMoney(amount);
}
}
}
}
但是由于上面代码中的from和to是调用者传进来的,所以不同线程传递的顺序是有可能是反的,那就是动态顺序死锁,这种情况可以有两种处理方式:
方式一:用哈希区分不同锁对象,另外为了防止哈希碰撞,另加入一个锁
public class SafeTransfer implements ITransfer {
private static Object tieLock = new Object();
@Override
public void transfer(Account from, Account to, int amount)
throws InterruptedException {
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
// 用哈希值区分锁对象,以保证锁顺序
if(fromHash
方式二:循环动态抢锁
public class TryLockTransfer implements ITransfer {
@Override
public void transfer(Account from, Account to, int amount)
throws InterruptedException {
Random r = new Random();
while(true){
if(from.getLock().tryLock()){
try{
System.out.println(Thread.currentThread().getName()
+" get from "+from.getName());
if(to.getLock().tryLock()){
try{
System.out.println(Thread.currentThread().getName()
+" get to "+to.getName());
from.flyMoney(amount);
to.addMoney(amount);
System.out.println(from);
System.out.println(to);
break;
}finally {
to.getLock().unlock();
}
}
}finally {
from.getLock().unlock();
}
}
Thread.sleep(r.nextInt(5));//防止产生活锁
}
}
}
1、程序的安全性优于性能的提升
2、使用多线程会带来额外的性能开销,滥用线程,有可能导致得不偿失。
3、所谓性能,包含多个指标。例如“多快”:服务时间、等待时间、延迟时间;例如“多少”:吞吐量,例如可伸缩性等等。
4、性能的各个指标方面,是完全独立的,有时候甚至是相互矛盾。
5、所以性能的提升是个包括成本在内多方面权衡和妥协的结果。
性能优化的黄金原则:首先保证程序正确,然后再提高运行速度(如果有确切的证据表明程序确实慢)。