聊聊java线程
线程池
创建线程池
首先通过ExecutorService threadPool = Executors.newFixedThreadPool(thread);
构建的线程池阿里java开发规范不提倡,其源码本质创建方式:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
手动创建线程池:
private static int corePoolSize = 100;
private static int maxPoolSize = 100;
private static long keepAliveSeconds = 1L;
private static int queueCapacity = 500;
private static String ThreadNamePrefix = "thread-dataSet-job-%d";
private static ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat(ThreadNamePrefix).build();
private static ExecutorService taskExecutorService = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveSeconds,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(queueCapacity), threadFactory);
关闭线程池
这里聊一下线程池的关闭,shutdown()
和shutdownNow()
,这两个关闭方式都会在执行完成后,isShutdown()
的状态变成true。在并发线程中,shutdown()
执行完后,会将剩下线程执行完,shutdownNow()
会在执行完后,剩下的线程运行时会报错,这里需要在并发的时候特别注意,即注意shutdown()
和shutdownNow()
在代码逻辑中存放的位置。
线程
线程的返回值
线程可以解决很多问题,比如之前文章的进程阻塞问题,还可以提高批量操作的执行效率等。
1、无返回值
public class FileCopyThread extends Thread {
private String srcPath;
private String destPath;
boolean blockOK = true;
public FileCopyThread(String srcPath, String destPath){
this.srcPath = srcPath;
this.destPath = destPath;
}
@Override
public void run(){
long beginTime = System.currentTimeMillis();
Path start = Paths.get(srcPath);
Path target = Paths.get(destPath);
Path targetBlock = start;
//todo
long endTime = System.currentTimeMillis();
System.out.println("====destPath: "+ targetBlock + ", cost time: " +
(endTime-beginTime) + ", result: " + blockOK);
}
}
使用方式:
executorService.submit(new FileCopyThread(srcMap.toString(), destMap.toString()));
2、有返回值
public class FileCopyThreadCallBack implements Callable{
private String srcPath;
private String destPath;
boolean blockOK = true;
public FileCopyThreadCallBack(String srcPath, String destPath){
this.srcPath = srcPath;
this.destPath = destPath;
}
@Override
public Boolean call(){
long beginTime = System.currentTimeMillis();
Path start = Paths.get(srcPath);
Path target = Paths.get(destPath);
Path targetBlock = start;
//todo
long endTime = System.currentTimeMillis();
System.out.println("====destPath: "+ targetBlock + ", cost time: " +
(endTime-beginTime) + ", result: " + blockOK);
return blockOK;
}
}
使用方式:
Future<Boolean> resultFuture = executorService.submit(
new CopyThreadCallback(srcMap.toString(), destMap.toString(), uuid));
有返回值,获取返回值结果时,如果在创建线程后直接获取结果resultFuture.get()
这里当前线程执行完后,再往后继续执行下一个线程,造成结果是顺序执行,这里如果使用线程进行并发操作,需要注意resultFuture.get()
的使用位置。
线程取消操作
这里介绍常见的线程取消函数cancel,例:
for (Future<Boolean> booleanFuture : futureList) {
if (!booleanFuture.isDone()) {
booleanFuture.cancel(true);
}
}
在取消操作完之后,需要在代码中线程继续执行位置判断Thread.currentThread().isInterrupted()
进行return跳出终止即可。
线程暂停与继续
有时候我们的业务开发会长时间消耗占用资源,暂停和继续可以帮助我们解决这类问题。
1、暂停方法:
/**
* 暂停
*
* @param uuid
*/
public void suspend(String uuid) {
uuid=true;
}
2、继续方法
/**
* 继续
*
* @param uuid
*/
public void resume(String uuid) {
synchronized (uuid) {
uuid=false;
uuid.notifyAll();
}
}
这里针对的是uuid的暂停和继续。在用过程中调用方法即可。
3、函数暂停后,具体函数中判断暂停以及超时的例子如下:
boolean isEnd = false;
Boolean suspended = SuspendCacheUtil.get(uuid);
synchronized (uuid) {
while (suspended != null && suspended) {
log.info("====== Searching suspend ======" + uuid);
try {
uuid.wait(CbbMngConstants.WAITINGTIME);
Long nowTime = System.currentTimeMillis();
Long startTime = TimeCacheUtil.get(uuid);
if (startTime != null && nowTime - startTime > CbbMngConstants.WAITINGTIME) {
log.info("===== " + uuid + " ===== timeout end.");
SuspendCacheUtil.set(uuid, false);
isEnd = true;
break;
}
} catch (InterruptedException e) {
log.info("===== " + uuid + " ===== isInterrupted.");
SuspendCacheUtil.set(uuid, false);
isEnd = true;
break;
}
suspended = SuspendCacheUtil.get(uuid);
}
}
if (isEnd) {
//todo
return;
}
这里是代码片段,不能直接使用,为了助于了解逻辑,SuspendCacheUtil
TimeCacheUtil
为缓存函数。
线程锁
线程中非常重要的一个问题是要保证线程安全。其中synchronized和volatile关键字保证相关方法变量的安全,当然java中有些方法本身就是线程安全的。
volatile关键字保证了在多线程环境下,被修饰的变量在别修改后会马上同步到主存,这样该线程对这个变量的修改就是对所有其他线程可见的,其他线程能够马上读到这个修改后值。
例如:volatile并不能保证非源自性操作的多线程安全问题得到解决,volatile解决的是多线程间共享变量的可见性问题,而例如多线程的i++,++i,依然还是会存在多线程问题。
Java提供了java.util.concurrent.atomic 包来提供线程安全的基本类型包装类。即i++
变成i.incrementAndGet();
,需引入import java.util.concurrent.atomic.AtomicInteger;
synchronized
1、在多线程使用同一个对象的测试中,只允许同时使用一个对象锁,一个类锁。其中类锁,即类中静态方法和静态类。
1)静态类锁
public static synchronized void method3(String uuid)
2)对象锁
public synchronized void method1(String uuid)
其他操作(两个对象锁,两个类锁)搭配都互斥,只能等前一个线程解锁才能让下一个线程使用;
2、在对象分别new一个对象的情况下,允许同时使用任意的对象锁,也允许对象锁和一个类锁同时使用。
但是类锁不能够同时使用,会互斥,只能等前一个线程解锁才能让下一个线程使用。这里需要注意static加synchronized
的方法的使用。
感兴趣的小伙伴可以做下测试,这里省略。
线程不安全方法举例
线程不安全会报错各种各样的问题,我们都知道List 线程不安全,其中的操作不是原子操作。Vector 是线程安全的,其中的操作有sync修饰,为同步操作。
在多线程使用List的过程中List<String> list = new ArrayList<>();
对进行list进行add
、remove
、removeAll
操作时会经常出现异常ArrayIndexOutOfBoundsException
即数组越界,代码中看逻辑没有任何问题,原因在于List是线程不安全的。
在多线程中使用List时需要注意,按照如下方式使用List<String> list = Collections.synchronizedList(new ArrayList<>());
多线程中使用非线程安全的方法需要特别注意。