Java——同步与互斥

今年大三下学期了,学校开了一门《多核多线程》课程,老师给我们主要讲解的就是Java的线程方面的知识,上午老师讲完课程了,觉得蛮精髓就想写一篇博客记录下来,免得忘记。闲话不多说了,下面来看看:
首先来引进一个例子:程序的输出是5吗?
创建一个Javaproject,创建包,创建一个名称为Demo1的Class,代码如下:

“`
public class Demo1 extends Thread{

public static int a = 0;

@Override
public void run() {
    for (int i = 0; i < 5; i++) {
        a = a + 1;
    }
}

public static void main(String[] args) throws Exception {
    Runnable r = new Demo1();
    Thread t = new Thread(r);
    t.start();
    System.out.println(a);
}

}

控制台输出的信息是:0。运行过程:当主线程main方法运行System.out.println(a)的时候,子线程还没有真正的运行,或许正在分配资源准备运行。当执行t.start()之后,接着执行输出语句System.out.println(a),这个时候得到的a的值还没有改变。那么如何才能让输出结果为5呢?可以在t.start()后面加一行代码,t.join().方法join的作用是:等待调用该方法的线程终止。
上面这个例子的变量a就是一个共享变量,主线程main可以调用,子线程也可以调用。这也就引发出许多问题:两个或者两个以上的线程同时对一个变量进行操作,就可能发生两个线程改变同一个值的问题。下面再来一个稍微麻烦点的例子:

同步访问

创建一个对象类IntGenerator,没有什么实质性的用处

public abstract class IntGenerator {
private volatile boolean canceled = false;
public abstract int next();
public void cancel(){
canceled = true;
}

public boolean isCanceled(){
return canceled;
}
}

一个EvenGenerator类,主要问题就出现在这里,这个类其实就是一个偶数生成器,num的初始值为0,自加两次,那么返回的值必定也是一个偶数,而事实区不一定。

public class EvenGenerator extends IntGenerator{
private int num = 0;
@Override
public int next(){
    num++;
    num++;
    return num;
}
}

接着再定义一个AccessThread类继承自Thread:

public class AccessThread extends Thread{

private int id;
private EvenGenerator gen;

public AccessThread(int id,EvenGenerator gen)
{
    super();
    this.id = id;
    this.gen = gen;
}

public long getId() {
    return id;
}

public void setId(int id) {
    this.id = id;
}

public EvenGenerator getGen() {
    return gen;
}

public void setGen(EvenGenerator gen) {
    this.gen = gen;
}

@Override
public void run() {
    while (!gen.isCanceled()) {
        int num = gen.next();
        if (num%2!=0) { 
        // 如果num不是一个偶数的话,输出信息,并且打印num的值,退出循环,程序终止。
            System.out.println("出错" + id + ":" + num);
            gen.cancel();
        }
    }
}
}

接着来一个测试类:EvenTest:

public class EvenTest {
   public static void main(String[] args) {

    // 共享对象
    EvenGenerator gen = new EvenGenerator();

    ExecutorService exe = Executors.newCachedThreadPool();//开启一个线程池
    // 创建100个线程同时对Gen对象进行操作,都执行next()方法
    for (int i = 0; i < 100; i++) {
        exe.execute(new AccessThread(i, gen));
    }
    exe.shutdown();
}
}

程序运行可以看到输出信息:出错**,说明偶数生成器生产出来奇数了,问题出现了。假设这100个线程中的线程1和线程2运行过程为:这里写图片描述
问题就出现在红色框框里面,这个时候num的值为3,恰好这个时候进行判断num%2,得到的结果就会和预想的不一样了,那么怎么更改才能得到正确的呢?只需要在EvenGenerator类的next方法改为:public synchronized int next()就可以避免这种错误了。(synchronized的意思为同步的)

并发访问

下面还有一个更简洁的例子:
创建一个类SharedObject:

public class SharedObject {

private int val = 0;

public synchronized int getVal() {
    return val;
}

public synchronized void setVal(int val) {
    this.val = val;
}
}

一个AccessThread继承自Thread:

public class AccessThread extends Thread {

private SharedObject o;

public AccessThread(SharedObject o) {
    this.o = o;
}

@Override
public void run() {
    synchronized (o) 
    {
        for (int i = 0; i < 1000; i++) {
            o.setVal(o.getVal() + 1);
            o.setVal(o.getVal() - 1);
        }
    }
}
}

测试类:

public class SharedTest {

public static void main(String[] args) {
    ExecutorService exe = Executors.newCachedThreadPool();
    SharedObject o = new SharedObject();
    for (int i = 0; i < 100; i++) {
        exe.execute(new AccessThread(o));
    }
    System.out.println(o.getVal());
    exe.shutdown();
}
}

这个例子和上面一个差不多,但是更加简洁一些,重点在SharedObject 的get()和set()方法上面,添加了一个synchronized,并且AccessThread类的run()方法中,synchronized (o)就是相当于把当前的对象给锁住了,即:当前线程在对val进行更改,不允许其他线程使用,也就是互斥了。如果去掉这几个synchronized ,那么程序输出的val就不会是0了,并且get(),set()方法前的synchronized 也不可以删除,此处是为了防止其他进程(不是AccessThread里面的线程)来访问。

个人总结


同步:多个线程可以同时对一个对象进行操作(如:多个人对一个银行账号进行操作)
互斥:多个线程同时对一个对象进行操作。A进行操作的时候,B不能进行操作,B进行操作的时候,A不能进行操作。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值