java多线程初探(一)构建线程安全类

这里先记录一些基础概念和说明

一、构建线程安全类方式

如果多个线程访问同一个可变的状态变量时,没有使用合适的同步,那就会出现不可预知的错误,修复方式如下:

1、不在线程之间共性这个变量(线程封闭)

2、将变量修改为不可变(final)

3、在访问变量时使用同步(同步代码块)


二、无状态对象

无状态对象一定是线程安全的,无状态对象定义如下:

/**
 * 有状态bean:包含实例变量对象,如这里的user
 */
public class StatefulBean {
    public int state;
    // 由于多线程环境下,user是引用对象,是非线程安全的
    public User user;
    public int getState() {
        return state;
    }
    public void setState(int state) {
        this.state = state;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
}

/**
 * 无状态bean:没有实例变量对象。
 */
public class StatelessBeanService {
    public int state;
    public int getState() {
        return state;
    }
    public void setState(int state) {
        this.state = state;
    }
}

延伸到spring的bean就是,只包含简单字段(没有类类型的属性)的实体是线程安全的无状态对象,包含类类型属性的实体则相反(如在mybatis中可能会在Article实体内添加User user这样的类类型的属性,以便一次查询到作者)

在spring中默认mvc各层都是单例的,因为service和dao层一般都是无状态的(没有类类型属性,以及类类型属性的get,set方法)。这样多个线程使用同一个实例也是安全的。


三、竞态条件

竞态条件:当某个计算正确性,取决于多个线程的交替执行时序所引发,例如:

private int count=0;
++count;
System.out.println(count);
++count不是一个原子操作,是三个独立的操作:读取count值,给值加1,将结果写入count变量;是读取-修改-写入的操作。

多线程下在线程A写入时,线程B可能对count做了第二次的修改,结果就会偏差1,这种操作方式引发的现象就是竞态条件。

同理先检查-后执行(延迟初始化)的操作也会引发竞态条件,如A条件下执行a方法,在检查后和执行a方法前,其他线程操作将A条件改为了B,这时仍执行a方法得到的结果就可能是不正确的。常用的if else,判断条件如果是共享变量就可能引起竞态条件


四、复合操作

复合操作:如上例,将读取-修改-写入的操作,和先检查-后执行的操作,统称为复合操作:包含了一组必须已原子方式执行才能保证线程安全的操作。


五、综上简单案例如下:

package gcc.thread.test;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * Created by gcc on 2018/3/29.
 * thread.start(),内部会调用start0(),而这个方法是本地(native)方法,能启动线程,同时也创建了一个线程。
 * thread.run(),只是调用一个java方法,不是真正启动多线程。
 */
public class Test1 implements Runnable {

    private int count=0;
//    private final AtomicInteger count = new AtomicInteger();

    public static void main(String[] args) {
        Test1 test1 = new Test1();
        Thread thread1 = new Thread(test1);
        Thread thread2 = new Thread(test1);
        Thread thread3 = new Thread(test1);
        thread1.start();
        thread2.start();
        thread3.start();

    }

    public void run() {
        for (int i = 0; i <30 ; i++) {
// System.out.println("结果:"+Thread.currentThread().getName()+"--:"+count.incrementAndGet());
            System.out.println("结果:"+Thread.currentThread().getName()+"---:"+count++);
            //线程休眠时间0.1-0.5秒
            try {
                Thread.sleep(100*(new Random().nextInt(5)+1));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

如上,Atomic开头的类是java自带的原子操作类,可以将类似count++的复合操作改为同作用的原子操作。案例默认的是复合操作,会引发的错误结果。如下:

结果:Thread-1---:11
结果:Thread-0---:12
结果:Thread-2---:12
结果:Thread-1---:13
结果:Thread-0---:14
结果:Thread-2---:15


六、使用synchronized方法,如下:

    public synchronized void run() {
        for (int i = 0; i <50 ; i++) {
            ++count;
            System.out.println("结果:"+Thread.currentThread().getName()+"----"+count);
            //线程休眠时间0.1-0.3秒
            try {
                Thread.sleep(10*(new Random().nextInt(3)+1));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

其它代码与(五)相同,这里增加synchronized,这是java内置锁,加在方法上则在同一时间只有一个线程能执行这个方法,解决了多线程不安全问题,但效率大大降低,不推荐使用,测试结果如下:

结果:Thread-0----47
结果:Thread-0----48
结果:Thread-0----49
结果:Thread-0----50
结果:Thread-2----51
结果:Thread-2----52
结果:Thread-2----53


七、使用synchronized代码块,如下:

    public void run() {
        for (int i = 0; i <50 ; i++) {
            synchronized (this){
                ++count;
                System.out.println("结果:"+Thread.currentThread().getName()+"----"+count);
            }
            //线程休眠时间0.1-0.3秒
            try {
                Thread.sleep(10*(new Random().nextInt(3)+1));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

其它代码与(五)相同,这里增加synchronized(this){},添加代码块,效率高于synchronized方法,通过平衡性能、安全和编码的简单性来确定同步代码块的大小,相对于(六)此方式更优,测试结果如下:

结果:Thread-0----1
结果:Thread-2----2
结果:Thread-1----3
结果:Thread-1----4
结果:Thread-0----5
结果:Thread-2----6
结果:Thread-0----7

注意:

同步代码块防止了一个对象在被使用时,同时被另一个线程修改。

还有要知道的一点是,同步机制保证了可见性。

可见性:读操作和写操作在两个线程时,不能保证读操作可以适时的看到其他线程写入的值,为了保证写入可见,使用同步机制。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值