大家好,我有一个浅显的线程问题。求解答。class A{
 private int counter;
 public void incrementCounter(){
  counter++;
 }
 public int getCounter(){
  return counter;
 }
}class B{
 public doStuff(A a){
  int tmp = a.getCounter();
  if(tmp < 100){
   a.incrementCounter();
  }
  //do something else
 }
}我有两个这样的class 。请问怎样保证doStuff是线程安全才是最好的?我应该在doStuff里synchronize a吗?谢谢!

解决方案 »

  1.   

    这样写:
    package com.zhyea;class A {
    private int counter; public void incrementCounter() {
    counter++;
    } public int getCounter() {
    return counter;
    }
    }class B {
    public synchronized void doStuff(A a) {
    int tmp = a.getCounter();
    if (tmp < 100) {
    a.incrementCounter();
    }
    //do something else
    }
    }关键是同步监视器的控制范围,同步方法的同步监视器是this,如果类里面只有一个同步方法,那么那么使用同步代码块还是同步方法影响不大。再者,如果涉及到了多读少写这样特殊的需求,可以考虑使用Lock
      

  2.   

    public doStuff(A a){
    int tmp = a.getCounter();
    if(tmp < 100){
      synchronized (a) {
    if(a.getCounter() < 100){
    a.incrementCounter();
    }
     }
      }
     //do something else
     }
      

  3.   

    谢谢回复!!!但是实际情况是,我有可能有一个list的不同的class A的实例被传入同一个doStuff方法里,如果是这样的话,我synchronize这整个方法,那么几个thread就不可以共用这个方法了。如果我synchronize的是A的实例,类似下面这样:class B {
        public void doStuff(A a) {
            synchronized(a){
              int tmp = a.getCounter();
              if (tmp < 100) {
                  a.incrementCounter();
              }
            }
            //do something else
        }
    }是不是我就可以用不同的thread,作用于不同的a,执行这个dostuff?不好意思初学者,可能没有讲清楚。
      

  4.   

    谢谢回复!!!但是实际情况是,我有可能有一个list的不同的class A的实例被传入同一个doStuff方法里,如果是这样的话,我synchronize这整个方法,那么几个thread就不可以共用这个方法了。如果我synchronize的是A的实例,类似下面这样:class B {
        public void doStuff(A a) {
            synchronized(a){
              int tmp = a.getCounter();
              if (tmp < 100) {
                  a.incrementCounter();
              }
            }
            //do something else
        }
    }是不是我就可以用不同的thread,作用于不同的a,执行这个dostuff?不好意思初学者,可能没有讲清楚。应该是可以的。减小同步控制的作用域,可以提高程序的灵活度
      

  5.   

    谢谢回复!!!但是实际情况是,我有可能有一个list的不同的class A的实例被传入同一个doStuff方法里,如果是这样的话,我synchronize这整个方法,那么几个thread就不可以共用这个方法了。如果我synchronize的是A的实例,类似下面这样:class B {
        public void doStuff(A a) {
            synchronized(a){
              int tmp = a.getCounter();
              if (tmp < 100) {
                  a.incrementCounter();
              }
            }
            //do something else
        }
    }是不是我就可以用不同的thread,作用于不同的a,执行这个dostuff?不好意思初学者,可能没有讲清楚。应该是可以的。减小同步控制的作用域,可以提高程序的灵活度只要明确竞争资源是哪个就好了。
      

  6.   

    关于线程安全,首先明确一点,有没有共用资源,没有共用资源就不存在线程安全问题。按照你的描述:“一个list的不同的class A的实例被传入同一个doStuff方法里”
    doStuff方法中唯一可能共用的是a,其他都是私有变量,不存在共用。
    如果list中的A可能同时被多个线程操作,这需要加同步或锁,否则不需要。其次,如果要加锁,可以这么做,最大限度的减少同步代码作用域及访问次数。class B {
        public void doStuff(A a) {
    if (a.getCounter() < 100){ 
    // 两次判断,第一次是为了避免,count大于100时,无效地进入同步代码块。
    synchronized(a){
    if (a.getCounter() < 100) {
    // 第二个判断是为了确保值的准确性,可能在进入第一个判断后,另一个线程将count的值++到100了。
    a.incrementCounter();
    }
    }
    }
            //do something else
        }
    }
      

  7.   

    补充:如果list中的同一个A可能同时被多个线程操作,这需要加同步或锁,否则不需要。
      

  8.   

    如果你打算发起多个线程并行执行doStuff方法,更合适的方法是把B做成FutureTask接口的实现类,然后使用ExecutorService去管理一组B实例。加锁始终是不好的,如果能通过改变代码结构实现相同逻辑,就尽可能不要加锁
      

  9.   

    这个问题我最近想起来一个关键的点,就是加锁的范围可能会引起的问题。写了两段代码:
    第一段,在方法上加锁:package com.zhyea.test;/**
     * @ClassName: MyThread 
     * @Description: 自己实现的多线程类
     * @Site: www.zhyea.com 
     * @author robin
     * @date 2016年2月6日 下午4:06:50
     */
    public class MyThread implements Runnable { private static Integer count = 0; @Override
    public synchronized void run() {
    for (int i = 0; i < 1000; i++) {
    ++count;
    }
    } public void test() throws Exception {
    MyThread mt = new MyThread();
    new Thread(mt, "+++++++ 线程 A ").start();
    new Thread(mt, "------- 线程 B ").start();
    new Thread(mt, "******* 线程 C ").start();
    Thread.sleep(1000L);
    System.out.println("-------" + count);
    } public static void main(String[] args) throws Exception {
    new MyThread().test();
    }
    }第二段,在竞争属性上加锁:package com.zhyea.test;/**
     * @ClassName: MyThread 
     * @Description: 自己实现的多线程类
     * @Site: www.zhyea.com 
     * @author robin
     * @date 2016年2月6日 下午4:06:50
     */
    public class MyThread implements Runnable { private static Integer count = 0; @Override
    public void run() {
    for (int i = 0; i < 1000; i++) {
    synchronized (count) {
    ++count;
    }
    }
    } public void test() throws Exception {
    MyThread mt = new MyThread();
    new Thread(mt, "+++++++ 线程 A ").start();
    new Thread(mt, "------- 线程 B ").start();
    new Thread(mt, "******* 线程 C ").start();
    Thread.sleep(1000L);
    System.out.println("-------" + count);
    } public static void main(String[] args) throws Exception {
    new MyThread().test();
    }
    }执行一下这两段就能发现区别。你那个程序中,如果要保证a的同步处理,需要保证a的获取和操作整个过程都是原子性的,将a作为加锁条件不能实现预期的目标。