暴力的stop
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
public class StopTest {
static Object lock = new Object();
@SuppressWarnings("deprecation")
public static void main(String[] args) {
User u1 = new User();
u1.setId(1);
u1.setAmount(new BigDecimal(1000));
User u2 = new User();
u2.setId(2);
u2.setAmount(new BigDecimal(0));
System.out.println(u1);
System.out.println(u2);
BigDecimal changeAmount = new BigDecimal(500);
Change change = new Change(u1,u2,changeAmount);
change.start();
try {
Thread.sleep(1000);//让change线程执行1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
change.stop();
try {
change.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(u1);
System.out.println(u2);
}
static class Change extends Thread{
private User u1,u2;
private BigDecimal change;
Change(User u1,User u2,BigDecimal change){
this.u1 = u1;
this.u2 = u2;
this.change = change;
}
@Override
public void run() {
synchronized(lock){
BigDecimal subResult = u1.amount.subtract(change);
u1.setAmount(subResult);
try {
TimeUnit.SECONDS.sleep(2);//回main线程,让main线程stop当前线程
} catch (InterruptedException e) {
e.printStackTrace();
}
BigDecimal addResult = u2.amount.add(change);
u2.setAmount(addResult);
}
}
}
static class User{
private int id;
private BigDecimal amount;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
@Override
public String toString() {
return "User [id=" + id + ", amount=" + amount + "]";
}
}
}
这个实例是想把用户u1的amount扣除500,用户u2的amount加上500的,这种类型的事务看似简单,但是在实际应用中却非常多。在上面的例子中我们有意让同步快只执行一半,就在main中stop了。这样就很容易发现用户u1扣除了500,但是用户u2并没有加上500,显然是不满足约束的,数据的一致性遭到了破坏。
可能有同学会问:实际中谁会写这么傻的代码啊?的确不会这么极端,但是使用了stop基本本质上就和这么傻的代码差不多了。除非你非常清楚的知道自己的run逻辑代码中在任意位置终止都不存在破坏数据约束的行为。
stop的代替模式
上面我们说了stop的不安全的,但是我们有时候的确需要在其他线程中终止一个线程怎么办呢?我们把上面的代码简单的改一下就可以了,可能例子并不少非常恰当,但是模式是通用的。
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
public class StopTest2 {
private volatile static boolean stoped = false;
static Object lock = new Object();
public static void stop(){
stoped = true;
}
public static void main(String[] args) {
User u1 = new User();
u1.setId(1);
u1.setAmount(new BigDecimal(1000));
User u2 = new User();
u2.setId(2);
u2.setAmount(new BigDecimal(0));
System.out.println(u1);
System.out.println(u2);
BigDecimal changeAmount = new BigDecimal(500);
Change change = new Change(u1,u2,changeAmount);
change.start();
try {
Thread.sleep(1000);//让change线程执行1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
// change.stop();
StopTest2.stop();
try {
change.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(u1);
System.out.println(u2);
}
static class Change extends Thread{
private User u1,u2;
private BigDecimal change;
Change(User u1,User u2,BigDecimal change){
this.u1 = u1;
this.u2 = u2;
this.change = change;
}
@Override
public void run() {
while(!stoped){
synchronized(lock){
BigDecimal subResult = u1.amount.subtract(change);
u1.setAmount(subResult);
try {
TimeUnit.SECONDS.sleep(2);//回main线程,让main线程stop当前线程
} catch (InterruptedException e) {
e.printStackTrace();
}
BigDecimal addResult = u2.amount.add(change);
u2.setAmount(addResult);
}
}
}
}
static class User{
private int id;
private BigDecimal amount;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
@Override
public String toString() {
return "User [id=" + id + ", amount=" + amount + "]";
}
}
}
首先有一个volatile的Boolean变量stoped来标记线程释放终止,volatile关键字是为了保证多线程的可见性,如果有线程修改了,其他线程立即可见。然后提供一个共有接口来修改stoped的值。在业务逻辑代码run中根据业务逻辑需要处理线程终止,一般需要终止的线程是一个无限循环,这里只是为了说明终止线程的模式硬编上去的。
stop的代替方法
其实还可以利用Java提供给我们的接口来终止线程,Java提供了3个相关的接口:、
//Object的 wait(), wait(long),wait(long, int)
//或者Thread的join(), join(long), join(long, int),
//sleep(long), sleep(long, int) 会清除线程的中断标记,
//其他情况会,添加中断标志
public void interrupt()
public boolean isInterrupted()//检查是否中断
public static boolean interrupted()//静态方法,判断是否中断,清除中断状态
import java.math.BigDecimal;
public class StopTest4 {
static Object lock = new Object();
public static void main(String[] args) {
User u1 = new User();
u1.setId(1);
u1.setAmount(new BigDecimal(1000));
User u2 = new User();
u2.setId(2);
u2.setAmount(new BigDecimal(0));
System.out.println(u1);
System.out.println(u2);
BigDecimal changeAmount = new BigDecimal(500);
Change change = new Change(u1,u2,changeAmount);
change.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// change.stop();
change.interrupt();
try {
change.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(u1);
System.out.println(u2);
}
static class Change extends Thread{
private User u1,u2;
private BigDecimal change;
Change(User u1,User u2,BigDecimal change){
this.u1 = u1;
this.u2 = u2;
this.change = change;
}
@Override
public void run() {
synchronized(lock){
BigDecimal subResult = u1.amount.subtract(change);
u1.setAmount(subResult);
while(!Thread.currentThread().isInterrupted());//空循环
BigDecimal addResult = u2.amount.add(change);
u2.setAmount(addResult);
}
}
}
static class User{
private int id;
private BigDecimal amount;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
@Override
public String toString() {
return "User [id=" + id + ", amount=" + amount + "]";
}
}
}
这个例子也是硬编上去的,只是为了说明用interrupt来终止线程,interrupt并不会想stop那样简单粗暴的终止线程,有点像stop模式,但是对于使用interrupt来说,检查标志位的方式不统一,这在处理上会有一定的麻烦,所以很多时候使用stop模式。
总结
- 尽量不要使用stop的方法,考虑使用wait,notify模式
- 如果的确要使用stop的方法,可以考虑interrupt方法,建议考虑使用stop模式