不废话,直奔主题。
1、创建线程:
继承 Thread 重新 run 方法;
实现 Runnable 接口;
实现 Callable 接口。
2、线程状态:
3、线程同步 synchronized ,ReentrantLock
线程同步就是用加锁的机制控制临界区的访问,临界区就是多线程共享访问的内存区。
进入临界区前加锁,离开临界区时释放锁,就这么简单。实现锁(同步)的方式有很多。
例如使用 synchronized (没有超时机制),用ReentrantLock可定义竞争锁的超时时间,
使用阻塞队列ArrayBlockingQueue,使用信号量Semaphore,使用锁存器CountDownLatch等等都可以实现加锁功能。达到线程同步目的。
4、线程间通信 notify,notifyAll
5、线程池
通用线程池,其他提供的具体线程池,基本是基于此创建的。
private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5, 20, 30L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(200),
new ThreadPoolExecutor.DiscardPolicy() // 超出任务范围抛弃任务,另外有AbortPolicy(),CallerRunsPolicy()等
);
submit 与 execute 的区别是 : submit有返回值, execute没有返回值。
一般的,任务里都做成有返回值比较好,管他返回值有没有用,于是通用 submit 提交任务
所谓函数接口就是接口只有一个待实现的函数,这时候直接传入函数的具体内容即可,无需new一个接口的实现类。
6、线程安全的集合:
记得多线程共享访问的数据用这些线程安全的集合或变量存放即可。
记住常用的就行:Hashtable,Vector, ConcurrentHashMap, ArrayBlockingQueue, LinkedBlockingDeque,ThreadLocal,AtomicLong
7、如何防止死锁?
尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时退出防止死锁。
尽量使用 Java. util. concurrent 并发类代替自己手写锁。
尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
尽量减少同步的代码块。
如果一段代码需要几个锁,那么对象锁的顺序要一致。
8、如何查看线程状态,检查死锁?threaddump 分析
java多线程编程,同步,锁,线程池就上面这些内容,在项目里用到的。
光说不练假把式,上示例代码:
线程池ThreadPoolExecutor:
package com.cloud.thread;
import com.alibaba.fastjson2.JSONObject;
import java.util.concurrent.*;
public class ThreadPoolDemo {
/**
* 通用线程池构造函数,其他几个具体的线程池也基于此,例如
*
* public static ExecutorService newFixedThreadPool(int nThreads) {
* return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
* }
*
* public static ExecutorService newSingleThreadExecutor() {
* return new FinalizableDelegatedExecutorService
* (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
* }
*
*
* public static ExecutorService newCachedThreadPool() {
* return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
*
* }
*
* public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
* return new ScheduledThreadPoolExecutor(corePoolSize);
* }
*/
private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5, 20, 30L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(200),
new ThreadPoolExecutor.DiscardPolicy() // 超出任务范围抛弃任务,另外有AbortPolicy(),CallerRunsPolicy()等
);
public static void main(String[] args){
BusinessService businessService = new BusinessService();
businessService.setId("1");
for (int i=0;i<10;i++){
/**
* submit 与 execute 的区别是 : submit有返回值, execute没有返回值。
* 一般的,任务里都做成有返回值比较好,管他返回值有没有用,于是通用 submit 提交任务
* 所谓函数接口就是接口只有一个待实现的函数,这时候直接传入函数的具体内容即可,无需new一个接口的实现类。
*/
Future<JSONObject> rs = threadPoolExecutor.submit(()->{ return businessService.doSomething(businessService.getId());} );
try {
if(i%2==0) {
System.out.println(rs.get(1500, TimeUnit.MILLISECONDS));
}else{
System.out.println(rs.get(3500, TimeUnit.MILLISECONDS));
}
} catch (InterruptedException e) {
// throw new RuntimeException(e);
} catch (ExecutionException e) {
//throw new RuntimeException(e);
} catch (TimeoutException e) {
//throw new RuntimeException(e);
System.out.println("超时...");
}
}
}
}
package com.cloud.thread;
import com.alibaba.fastjson2.JSONObject;
public class BusinessService {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public BusinessService(){
}
public JSONObject doSomething(String id){
JSONObject rs = new JSONObject();
rs.put("id",id);
rs.put("code","200");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
rs.put("code","-1");
e.printStackTrace();
}finally {
}
return rs;
}
}
信号量Semaphore:
package com.cloud.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* 信号量 Semaphore 相当于定义了固定线程数量的一个线程池,它限制了工作线程的总数
*/
public class SemaphoreTest {
public static void main(String[] args) {
// 线程池
ExecutorService exec = Executors.newCachedThreadPool();
// 只能5个线程同时访问 ,流量控制
final Semaphore semp = new Semaphore(5);
// 模拟20个客户端访问
for (int index = 0; index < 30; index++) {
final int NO = index;
Runnable run = new Runnable() {
public void run() {
try {
// 获取许可,会阻塞
semp.acquire();
// 还可以指定等待时间
//semp.tryAcquire(3000, TimeUnit.MILLISECONDS);
System.out.println("Accessing: " + NO);
Thread.sleep((long) (Math.random() * 10000));
// 访问完后,释放 。
semp.release();
} catch (InterruptedException e) {
}
}
};
exec.execute(run);
}
// 退出线程池
exec.shutdown();
}
}
锁存器CountDownLatch,计数1可用做同步锁,有线程同步功能。它原本作用是控制所有子线程完成之后,主线程才继续,很有用,类似于线程屏障。前面的信号量Semaphore设为1,同样也可用作线程同步锁
package com.cloud.thread;
/**
* CountDownLatch
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。
CountDownLatch 是一个通用同步工具,它有很多用途。
将 1 初始化的 CountDownLatch 用作一个简单的开/关锁存器:在通过调用 countDown() 的线程打开入口前,其他所有调用 await 的线程都一直在入口处等待。
用 N 初始化的 CountDownLatch 可以使主线程在 N个子线程完成某项任务之前一直等待。
CountDownLatch 的一个有用特性是,它不要求调用 countDown 方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续。
*/
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestCountDownLatch {
public static void main(String[] args) throws InterruptedException {
// 开始的倒数锁
final CountDownLatch begin = new CountDownLatch(1);
// 结束的倒数锁
final CountDownLatch end = new CountDownLatch(10);
// 十个子线程
final ExecutorService exec = Executors.newFixedThreadPool(10);
for (int index = 0; index < 10; index++) {
final int NO = index + 1;
Runnable run = new Runnable() {
public void run() {
try {
begin.await();// 一直阻塞
Thread.sleep((long) (Math.random() * 10000));
System.out.println("No." + NO + " arrived");
} catch (InterruptedException e) {
} finally {
end.countDown();
}
}
};
exec.submit(run);
}
System.out.println("Game Start");
begin.countDown();
end.await();
System.out.println("Game Over");
exec.shutdown();
}
}
线程屏障CyclicBarrier
线程屏障用于控制子线程组和主线程之间执行顺序节奏,或协调线程组的执行顺序。
package com.cloud.thread;
/**
* CyclicBarrier
一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。
在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。
CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),
该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。
CyclicBarrier最重要的属性就是参与者个数,另外最要方法是await()。当所有线程都调用了await()后,就表示这些线程都可以继续执行,否则就会等待。
示例用法:下面是一个在并行分解设计中使用 barrier 的例子,很经典的旅行团例子:
* @author wuhaibo
*
*/
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestCyclicBarrier {
// 徒步需要的时间: Shenzhen, Guangzhou, Shaoguan, Changsha, Wuhan
private static int[] timeWalk = { 5, 8, 15, 15, 10 };
// 自驾游
private static int[] timeSelf = { 1, 3, 4, 4, 5 };
// 旅游大巴
private static int[] timeBus = { 2, 4, 6, 6, 7 };
static String now() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
return sdf.format(new Date()) + ": ";
}
//public static volatile long xy=1;
static class Tour implements Runnable {
private int[] times;
private CyclicBarrier barrier;
private String tourName;
public Tour(CyclicBarrier barrier, String tourName, int[] times) {
this.times = times;
this.tourName = tourName;
this.barrier = barrier;
}
public void run() {
try {
Thread.sleep(times[0] * 1000);
System.out.println(now() + tourName + " Reached Shenzhen");
barrier.await();
Thread.sleep(times[1] * 1000);
System.out.println(now() + tourName + " Reached Guangzhou");
barrier.await();
Thread.sleep(times[2] * 1000);
System.out.println(now() + tourName + " Reached Shaoguan");
barrier.await();
Thread.sleep(times[3] * 1000);
System.out.println(now() + tourName + " Reached Changsha");
barrier.await();
Thread.sleep(times[4] * 1000);
System.out.println(now() + tourName + " Reached Wuhan");
barrier.await();
} catch (InterruptedException e) {
} catch (BrokenBarrierException e) {
}
}
}
public static void main(String[] args) {
System.out.println(3*0.1==0.3);
System.out.println(0.3==0.3);
// 三个旅行团
CyclicBarrier barrier = new CyclicBarrier(3);
ExecutorService exec = Executors.newFixedThreadPool(3);
exec.submit(new Tour(barrier, "WalkTour", timeWalk));
exec.submit(new Tour(barrier, "SelfTour", timeSelf));
exec.submit(new Tour(barrier, "BusTour", timeBus));
exec.shutdown();
}
}
线程同步,可重入锁ReentrantLock
package com.cloud.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
/**
* 一个可重入的互斥锁定 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大。
ReentrantLock 将由最近成功获得锁定,并且还没有释放该锁定的线程所拥有。当锁定没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁定并返回。
如果当前线程已经拥有该锁定,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。
* @author admin
*
*/
public class MyReentrantLock extends Thread {
TestReentrantLock lock;
private int id;
public MyReentrantLock(int i, TestReentrantLock test) {
this.id = i;
this.lock = test;
}
@Override
public void run() {
lock.print(id);
}
public static void main(String args[]) {
ExecutorService service = Executors.newCachedThreadPool();
TestReentrantLock lock = new TestReentrantLock();
for (int i = 0; i < 10; i++) {
service.submit(new MyReentrantLock(i, lock));
}
service.shutdown();
}
}
class TestReentrantLock {
private ReentrantLock lock = new ReentrantLock();
public void print(int str) {
try {
lock.lock();
System.out.println(str + "获得");
Thread.sleep((int) (Math.random() * 100));
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(str + "释放");
lock.unlock();
}
}
}
阻塞队列BlockingQueue
为什么需要使用BlockingQueue?
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为BlockingQueue都一手给你包办好了。
特点:
线程安全:阻塞队列是线程安全的,多个线程可以并发访问它而不会发生冲突。
生产者-消费者模式:阻塞队列支持生产者-消费者模式,即生产者向队列中添加元素,消费者从队列中取出元素。
阻塞等待:当队列为空时,消费者会被阻塞等待直到队列中有元素可供消费;当队列已满时,生产者会被阻塞等待直到队列有空闲位置可供添加元素。
适用于异步订阅场景,一个往里面放,一个消费。
而且如果保持队列里最多只有一个元素,那么offer和poll可能都会阻塞,可以实现锁的功能,线程同步,还可以设置超时时间:
new BlockingQueue(1);
offer(); poll();
跟锁存器CountDownLatch类似,所以说,实现线程同步方式方法有多种。
package com.cloud.thread;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 支持两个附加操作的 Queue,这两个操作是:检索元素时等待队列变为非空,以及存储元素时等待空间变得可用。
BlockingQueue 不接受 null 元素。会抛出 NullPointerException。
BlockingQueue 可以是限定容量的。它在任意给定时间都可以有一个 remainingCapacity,超出此容量,便无法 put 额外的元素。
默认最大容量为 Integer.MAX_VALUE 。
BlockingQueue 实现主要用于生产者-使用者队列,但它另外还支持 Collection 接口。
BlockingQueue 实现是线程安全的。所有排队方法都可以使用内部锁定或其他形式的并发控制来自动达到它们的目的。
* @author wuhaibo
*
*/
public class MyBlockingQueue extends Thread {
public static BlockingQueue<String> queue = new LinkedBlockingQueue<String>(
3);
private int index;
public MyBlockingQueue(int i) {
this.index = i;
}
@Override
public void run() {
try {
queue.put(String.valueOf(this.index));
System.out.println("{" + this.index + "} in queue!");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String args[]) {
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
service.submit(new MyBlockingQueue(i));
}
Thread thread = new Thread() {
public void run() {
try {
while (true) {
Thread.sleep((int) (Math.random() * 1000));
if (MyBlockingQueue.queue.isEmpty())
break;
String str = MyBlockingQueue.queue.take();
System.out.println(str + " has take!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
service.submit(thread);
service.shutdown();
}
}
别忘了,还有一种线程安全类型:AtomicLong
就是以Atomic开头的,意思是原子性,保证变量值的改变过程是原子性的。我们知道普通变量long的赋值过程,CPU要操作好几步的,这就有线程安全风险,AtomicLong就是把这几步封装成一个原子操作,类似于数据库的事务。
package com.cloud.thread;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
/**
* Atomic
*/
public class AtomicTest {
public void testAtomic()
{
final int loopcount = 10700;
int threadcount = 10;
final NonSafeSeq seq1 = new NonSafeSeq();
final SafeSeq seq2 = new SafeSeq();
final CountDownLatch l = new CountDownLatch(threadcount);
for(int i = 0; i < threadcount; ++i)
{
final int index = i;
new Thread(new Runnable() {
@Override
public void run() {
for(int j = 0; j < loopcount; ++j)
{
try {
Thread.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
seq1.inc();
seq2.inc();
}
System.out.println("finished : " + index);
l.countDown();
}
}).start();
}
try {
l.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("both have finished....");
System.out.println("NonSafeSeq:" + seq1.get());
System.out.println("SafeSeq with atomic: " + seq2.get());
}
}
class NonSafeSeq{
private long count = 0;
public void inc()
{
count++;
}
public long get()
{
return count;
}
}
class SafeSeq{
private AtomicLong count = new AtomicLong(0);
public void inc()
{
count.incrementAndGet();
}
public long get()
{
return count.longValue();
}
}
此外,在Java中,volatile关键字提供了一种轻量级的同步机制,用于确保可见性和阻止重排序。实际上用得少。
另外,在分布式系统中,需要实现服务器之间的同步调度,此时则需要分布式锁了,上面的同步机制局限于同一个程序容器内。分布式锁一个简单高效的方式是使用redis执行lua代码实现,不过这属于分布式系统的范畴了。关于redis分布式锁,直接找一个有效代码保存使用即可。
给出测试过有用的分布式锁:
package com.cloud.admin.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Component
public class RedisDistributeLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public void setRedisTemplate(RedisTemplate<String, String> redisCommands) {
this.redisTemplate= redisCommands;
}
// 加锁脚本
private static final String SCRIPT_LOCK = "if redis.call('get', KEYS[1]) == ARGV[1] then\n" +
" return redis.call('pexpire', KEYS[1], ARGV[2])\n" +
"else\n" +
" local status = redis.call('set', KEYS[1], ARGV[1], 'PX', ARGV[2], 'NX')\n" +
" return status\n" +
"end";
// 解锁脚本
private static final String SCRIPT_UNLOCK = "if redis.call('get', KEYS[1]) == ARGV[1] then\n" +
" return redis.call('del', KEYS[1]) > 0\n" +
"else\n" +
" return false\n" +
"end";
private static final String renewLua="if redis.call('get', KEYS[1]) == ARGV[1] then\n" +
" return redis.call('pexpire', KEYS[1], ARGV[2]) == 1\n" +
"else\n" +
" return false;\n" +
"end";
/**
* 尝试获取分布式锁
*
* @param lockKey 分布式锁的id
* @param requestId 分布式锁的值
* @param timeoutMills 超时时间(毫秒)
* @return 获取成功返回true, 否者false
*/
public boolean tryLock(String lockKey, String requestId, int timeoutMills) {
DefaultRedisScript<Boolean> script = new DefaultRedisScript<Boolean>(SCRIPT_LOCK, Boolean.class);
List<String> keys = new ArrayList();
keys.add(lockKey);
boolean got = redisTemplate.execute(script, keys, requestId, timeoutMills + "");
if (log.isDebugEnabled()) {
log.debug("lockKey:{}, requestId:{}, result:{}", lockKey, requestId, got);
}
return got;
}
/**
* 释放分布式锁
*
* @param lockKey
* @param requestId
*/
public boolean release(String lockKey, String requestId) {
DefaultRedisScript<Boolean> script = new DefaultRedisScript<Boolean>(SCRIPT_UNLOCK, Boolean.class);
List<String> keys = new ArrayList();
keys.add(lockKey);
boolean del = redisTemplate.execute(script, keys, requestId);
if (log.isDebugEnabled()) {
log.debug("lockKey:{}, requestId:{}, result:{}", lockKey, requestId, del);
}
return del;
}
/**
* 强制释放掉锁, 注意有可能会释放掉别人的锁, 慎用
* @param lockKey
* @return
*/
public boolean forceRelease(String lockKey) {
return redisTemplate.delete(lockKey);
}
/**
* 对已经持有的锁续约
*
* @param lockKey 分布式锁的id
* @param requestId 分布式锁的值
* @param timeoutMills 超时时间(毫秒)
* @return
*/
public boolean renew(String lockKey, String requestId, int timeoutMills) {
DefaultRedisScript<Boolean> script = new DefaultRedisScript<Boolean>(renewLua, Boolean.class);
List<String> keys = new ArrayList();
keys.add(lockKey);
boolean status = redisTemplate.execute(script, keys, requestId, timeoutMills + "");
if (log.isDebugEnabled()) {
log.debug("lockKey:{}, requestId:{}, result:{}", lockKey, requestId, status);
}
return status;
}
/*@PostConstruct
@SneakyThrows
public void test() {
tryLock("werweweew","111",5000);
release("werweweew","111");
}*/
}