springboot实现多线程service实现
線程安全:既然是線程安全問題,那么毫無疑問,所有的隱患都是在多個線程訪問的情況下產生的,也就是我們要確保在多條線程訪問的時候,我們的程序還能按照我們預期的行為去執行,我們看一下下面的代碼:
Integer count = 0;public void getCount() {count ++;System.out.println(count);}我開啟的3條線程,每個線程循環10次,得到以下結果:
我們可以看到,這里出現了兩個26,出現這種情況顯然表明這個方法根本就不是線程安全的,出現這種問題的原因有很多。
最常見的一種,就是我們A線程在進入方法后,拿到了count的值,剛把這個值讀取出來,還沒有改變count的值的時候,結果線程B也進來的,那么導致線程A和線程B拿到的count值是一樣的。
那么由此我們可以了解到,這確實不是一個線程安全的類,因為他們都需要操作這個共享的變量。其實要對線程安全問題給出一個明確的定義,還是蠻復雜的,我們根據我們這個程序來總結下什么是線程安全。
當多個線程訪問某個方法時,不管你通過怎樣的調用方式、或者說這些線程如何交替地執行,我們在主程序中不需要去做任何的同步,這個類的結果行為都是我們設想的正確行為,那么我們就可以說這個類是線程安全的。?
搞清楚了什么是線程安全,接下來我們看看Java中確保線程安全最常用的兩種方式。先來看段代碼。
大家覺得這段代碼是線程安全的嗎?
????毫無疑問,它絕對是線程安全的,我們來分析一下,為什么它是線程安全的?
????我們可以看到這段代碼是沒有任何狀態的,就是說我們這段代碼,不包含任何的作用域,也沒有去引用其他類中的域進行引用,它所執行的作用范圍與執行結果只存在它這條線程的局部變量中,并且只能由正在執行的線程進行訪問。當前線程的訪問,不會對另一個訪問同一個方法的線程造成任何的影響。
兩個線程同時訪問這個方法,因為沒有共享的數據,所以他們之間的行為,并不會影響其他線程的操作和結果,所以說無狀態的對象,也是線程安全的。
添加一個狀態呢?
如果我們給這段代碼添加一個狀態,添加一個count,來記錄這個方法并命中的次數,每請求一次count+1,那么這個時候這個線程還是安全的嗎?
public class ThreadDemo {int count = 0; // 記錄方法的命中次數public void threadMethod(int j) {count++ ;int i = 1;j = j + i;} }很明顯已經不是了,單線程運行起來確實是沒有任何問題的,但是當出現多條線程并發訪問這個方法的時候,問題就出現了,我們先來分析下count+1這個操作。
進入這個方法之后首先要讀取count的值,然后修改count的值,最后才把這把值賦值給count,總共包含了三步過程:“讀取”一>“修改”一>“賦值”,既然這個過程是分步的,那么我們先來看下面這張圖,看看你能不能看出問題:
可以發現,count的值并不是正確的結果,當線程A讀取到count的值,但是還沒有進行修改的時候,線程B已經進來了,然后線程B讀取到的還是count為1的值,正因為如此所以我們的count值已經出現了偏差,那么這樣的程序放在我們的代碼中,是存在很多的隱患的。
springboot中,多線程實現service
首先看一下Dao的實現:
@Repository @Slf4j public class UserThreadDao {@Autowiredprivate JdbcTemplate jdbcTemplate;public void add(String username, int threadId, String thread_status) {String sql = "insert into userthread (username, thread_id, thread_status) values (?,?,?)";jdbcTemplate.update(sql, new Object[]{username, threadId, thread_status});log.info("threadId:{}, jdbcTemplate:{}", threadId, jdbcTemplate);}public void update(String username, int threadId, String thread_status) {String sql = "update userthread set thread_status=? where username=? and thread_id=?";jdbcTemplate.update(sql, new Object[]{thread_status, username, threadId});}}這里的JDBCTemplate是一個線程安全的類,原因是JdbcTemplate使用了ThreadLocal實現,使各線程能夠保持各自獨立的一個對象,其實就是一個變量副本,實現了線程安全。
因此在這個UserThreadDao中沒有共享數據,沒有成員變量(JdbcTemplate是線程安全的),因此是線程安全的。
在service中使用多線程來調用dao,代碼如下所示:
@Service @Slf4j public class UserThreadServiceImpl implements UserThreadService {@AutowiredUserThreadDao userThreadDao;@Override@Async("asyncServiceExecutor")public void serviceTest(String username) {log.info("開啟執行一個Service, 這個Service執行時間為30s, threadId:{}",Thread.currentThread().getId());userThreadDao.add(username, Integer.parseInt(Thread.currentThread().getId() +""), "started");try {Thread.sleep(30000);} catch (InterruptedException e) {e.printStackTrace();}log.info("執行完成一個Service, threadId:{}",Thread.currentThread().getId());userThreadDao.update(username, Integer.parseInt(Thread.currentThread().getId() +""), "ended");} }這里的serviceTest方法就是一個多線程方法。線程池的配置如下:
@Configuration @EnableAsync @Slf4j public class ExecutorConfig {@AutowiredVisiableThreadPoolTaskExecutor visiableThreadPoolTaskExecutor;@Beanpublic Executor asyncServiceExecutor() {log.info("start asyncServiceExecutor");ThreadPoolTaskExecutor executor = visiableThreadPoolTaskExecutor;//配置核心線程數executor.setCorePoolSize(5);//配置最大線程數executor.setMaxPoolSize(5);//配置隊列大小executor.setQueueCapacity(5);//配置線程池中的線程的名稱前綴executor.setThreadNamePrefix("async-service-");// rejection-policy:當pool已經達到max size的時候,如何處理新任務// CALLER_RUNS:不在新線程中執行任務,而是有調用者所在的線程來執行executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//執行初始化executor.initialize();return executor;} }VisiableThreadPoolTaskExecutor 繼承了ThreadPoolTaskExecutor。
詳細代碼:在https://github.com/vincentduan/mavenProject?下的threadManagement目錄下。
總結
以上是生活随笔為你收集整理的springboot实现多线程service实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ThreadPoolExecutor里面
- 下一篇: 二叉堆与二叉堆的构建