需求:最近做一个项目需要用多线程文件落地,文件名末尾按批次号命名,file1..file2 3 4 ....
开3个固定大小的线程池,不断往里面加任务,每个线程共用一个计数器,争抢计数,从1开始递增。
package com.demo.thread;
import java.util.concurrent.atomic.AtomicInteger;
public class MyCounter {
private AtomicInteger batchNum = new AtomicInteger(0);
public AtomicInteger getBatchNum() {
return batchNum;
}
public void setBatchNum(AtomicInteger batchNum) {
this.batchNum = batchNum;
}
}
public class TestA extends Thread {
private MyCounter myCounter;
public TestA(MyCounter myCounter){
this.myCounter = myCounter;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"---"+myCounter.getBatchNum().incrementAndGet());
}
}
import com.demo.thread.MyCounter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
long start = System.currentTimeMillis();
MyCounter counter = new MyCounter();
while (System.currentTimeMillis() - start <10) {
TestA t = new TestA(counter);
executorService.execute(t);
}
}
}
可以看到线程池里的不同线程共享计数器
创建一个数据库表,把打印出来的数据插入到主键字段,可以看到,正常插入,没有重复。
例子分析
下面例子能够使多个线程共同争抢一个计数器,不会出现两个线程之间出现重复数字。
package com.demo.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadTest {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) {
int i = 0;
ExecutorService executorService = Executors.newFixedThreadPool(3);
while (i < 3) {
TA tt = new TA(atomicInteger);
executorService.execute(tt);
i++;
}
}
}
class TA implements Runnable {
AtomicInteger atomicInteger;
long start = System.currentTimeMillis();
public TA(AtomicInteger atomicInteger) {
this.atomicInteger = atomicInteger;
}
@Override
public void run() {
while(System.currentTimeMillis() -start<20) {
System.out.println(atomicInteger.incrementAndGet());
}
}
}
但是,如果你要用 AtomicInteger.get()呢,因为会有很多的方法,又不想取出atomicInteger.incrementAndGet()然后一个个方法参数传进去,这样也太麻烦了。
用get()用注意,用的不好,有可能出现重复情况。上面线程TA改成如下代码:
public void run() {
while(System.currentTimeMillis() -start<20) {
atomicInteger.incrementAndGet();
System.out.println(atomicInteger.get());
}
}
将打印出来的数字放入oracle主键字段,发现重复数据:
脏读的几种写法:
1. 先incrementAndGet,再get
package com.demo.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadTest {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) {
int i = 0;
ExecutorService executorService = Executors.newFixedThreadPool(3);
while (i < 10) {
TA tt = new TA(atomicInteger);
executorService.execute(tt);
i++;
}
}
}
class TA implements Runnable {
AtomicInteger atomicInteger;
long start = System.currentTimeMillis();
public TA(AtomicInteger atomicInteger) {
this.atomicInteger = atomicInteger;
}
@Override
public void run() {
atomicInteger.incrementAndGet();
try {
//模拟程序执行耗时
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicInteger.get());
}
}
2.先incrementAndGet,再赋值get,再使用。
网上有人说先赋值再使用就不会脏读,事实证明,还是会的。
int cnt = atomicInteger.get();
System.out.println(cnt);
3. 线程里面while(true)自增incrementAndGet,再逻辑耗时,再get
package com.demo.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadTest {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) {
int i = 0;
ExecutorService executorService = Executors.newFixedThreadPool(3);
while (i < 10) {
TA tt = new TA(atomicInteger);
executorService.execute(tt);
i++;
}
}
}
class TA implements Runnable {
AtomicInteger atomicInteger;
long start = System.currentTimeMillis();
public TA(AtomicInteger atomicInteger) {
this.atomicInteger = atomicInteger;
}
@Override
public void run() {
while(System.currentTimeMillis() -start<20) {
atomicInteger.incrementAndGet();
try {
//模拟程序执行耗时
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( atomicInteger.get());
}
}
}
4. 线程while(true),先incrementAndGet,马上get,再逻辑耗时
package com.demo.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadTest {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) {
int i = 0;
ExecutorService executorService = Executors.newFixedThreadPool(3);
while (i < 10) {
TA tt = new TA(atomicInteger);
executorService.execute(tt);
i++;
}
}
}
class TA implements Runnable {
AtomicInteger atomicInteger;
long start = System.currentTimeMillis();
public TA(AtomicInteger atomicInteger) {
this.atomicInteger = atomicInteger;
}
@Override
public void run() {
while(System.currentTimeMillis() -start<30) {
atomicInteger.incrementAndGet();
int cnt = atomicInteger.get();
try {
//模拟程序执行耗时
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( cnt);
}
}
}
执行一次,发现减少脏读概率,但是多执行几次还是出现脏读了。 但是概率小很多,大概执行10次,出现一次重复。
分享一个不会出现脏读的写法:
package com.demo.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadTest {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) {
int i = 0;
ExecutorService executorService = Executors.newFixedThreadPool(3);
while (i < 3) {//3改成100效果一样,还是不会出现脏读,3其实相当于Thread,没用到线程池的重复利用。
TA tt = new TA(atomicInteger);
executorService.execute(tt);
i++;
}
}
}
class TA implements Runnable {
AtomicInteger atomicInteger;
long start = System.currentTimeMillis();
public TA(AtomicInteger atomicInteger) {
this.atomicInteger = atomicInteger;
}
private int cnt;//线程自己的变量
@Override
public void run() {
while(System.currentTimeMillis() -start<3000) {
cnt = atomicInteger.incrementAndGet();
try {
//模拟程序执行耗时
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( cnt);
}
}
}
总结
1.线程池加任务的时候,new Thread一定要放在循环里面,否则,线程里面的成员变量就编程线程不安全了,就要用局部变量取。
2.new Thread 放在循环里添加任务,线程里面就可以用成员变量。
3.用incrementAndGet()取值,别用get。
4.incrementAndGet可以保证不重复,get会出现重复,即使自增完马上取,还是会出现重复。