如何正确使用Java synchronized关键字?

wtlkbnrh  于 2022-12-21  发布在  Java
关注(0)|答案(4)|浏览(126)

我正在测试java中的同步功能,但是看起来我没有正确使用它。我想要两个线程递增一个整数,并且在方法的签名中使用synchronized关键字,递增的结果应该是200000,但是我得到的结果小于200000,这意味着线程没有正确同步。
我的代码:

public class Threadproblem extends Thread {
    static long counter;
    synchronized public void run() {
        for (int i=0; i<100000;i++) {
            counter++;
        }
    }
    public static void main(String[] args) {
        Threadproblem thr=new Threadproblem();
        Threadproblem thr2=new Threadproblem();
        thr.start();
        thr2.start();
        try {
            thr.join();
            thr2.join();
        } catch(InterruptedException e) {
            System.out.println(e);
        }
        System.out.println("Counts:"+counter);
    }
}

执行:

Counts:137978
ct3nt3jp

ct3nt3jp1#

正确的同步要求对受保护变量的所有访问都在保持 * 相同 * 监视器的同时执行。对于示例方法,synchronized关键字会使方法自动获取调用它的示例的监视器。您有两个独立的Threadproblem示例,因此每个示例都使用自己的监视器。这根本不提供同步。
在使用synchronized时解决这个问题的一个方法是给予你的类一个synchronized,static 方法来增加计数器,Synchronized static方法使用一个与类关联的监视器,这样你的ThreadProblem的两个示例就可以使用同一个监视器:

public class Threadproblem extends Thread {
    static long counter;

    synchronized static void incrementCounter() {
        counter++;
    }

    public void run() {
        for (int i = 0; i < 100000;i++) {
            Threadproblem.incrementCounter();
        }
    }

    public static void main(String[] args) {
        Threadproblem thr = new Threadproblem();
        Threadproblem thr2 = new Threadproblem();

        thr.start();
        thr2.start();
        try {
            thr.join();
            thr2.join();
        } catch(InterruptedException e) {
            System.out.println(e);
        }
        System.out.println("Counts:" + counter);
    }       
}

或者像@HiranChaudhuri在评论中建议的那样,你可以让两个线程使用同一个Runnable对象,同步方法属于这个对象。

public class Threadproblem implements Runnable {
    static long counter;

    synchronized public void run() {
        for (int i = 0; i < 100000;i++) {
            counter++;
        }
    }

    public static void main(String[] args) {
        Threadproblem problem = new Threadproblem();

        // Note: instances of plain Thread:
        Thread thr = new Thread(problem);
        Thread thr2 = new Thread(problem);

        thr.start();
        thr2.start();
        try {
            thr.join();
            thr2.join();
        } catch(InterruptedException e) {
            System.out.println(e);
        }
        System.out.println("Counts:" + counter);
    }       
}

不管同步问题如何,这样做都更好,因为扩展Thread几乎总是错误的。应该通过为线程提供适当的Runnable来定义线程的工作,而不是重写Thread本身的run()方法。
请注意,主线程和另外两个线程之间也有可能发生数据竞争,但这些都已经避免了,因为启动一个线程和加入一个线程在所涉及的两个线程之间提供了适当的排序语义。

nnvyjq4y

nnvyjq4y2#

'run'的每次执行都在它自己的对象上同步,这意味着根本没有同步。
您需要在同一对象上进行同步。在您的情况下,类可能是合适的。请将其表示为命名要同步的对象(在本例中为类文本)的“synchronized”语句。

public void run() {
   synchronized (Threadproblem.class) { 
      ... 
   }
}
5w9g7ksd

5w9g7ksd3#

Java中的每个Object都有一个隐式锁,这是一个允许同步和互斥的元素。每当你在一个特定的对象上调用一个非静态函数时,它的锁就被获取了,并且在第一个线程释放它之前,其他线程都不能调用该对象上的动态函数。
所以你实际上应该做的是

  • 使用要以互斥方式执行的方法创建一个新类
  • 在main方法中创建该类的一个对象
  • 每个线程通过那个对象调用synchronized函数。这样,一次只有一个线程可以获得锁并互斥地递增计数器。

这里有一个很好的例子:https://www.geeksforgeeks.org/object-level-lock-in-java/

// Java program to illustrate
// Object lock concept

// Class
// Extending Runnable interface
class Geek implements Runnable {

    // Method of this class
    public void run() { Lock(); }

    // Synchronization of non-static methods
    // (object lock) as different synchronized
    // non-static methods are called in both threads

    // Then both threads need to acquire the object lock
    // After one is acquired, the other thread must wait
    // for one thread to finish the executing
    // before the other thread starts to execute.
    public void Lock()
    {
        System.out.println(
            Thread.currentThread().getName());
        synchronized (this)
        {
            System.out.println(
                "in block "
                + Thread.currentThread().getName());
            System.out.println(
                "in block "
                + Thread.currentThread().getName()
                + " end");
        }
    }

    // Main driver method
    public static void main(String[] args)
    {
        // Creating an object of above class
        // in the main() method
        Geek g = new Geek();

        // Sharing the same object across two Threads

        // Here, t1 takes g
        Thread t1 = new Thread(g);
    
        // Here, t2 takes g
        Thread t2 = new Thread(g);

        // Creating another object of above class
        Geek g1 = new Geek();

        // Here, t3 takes g1
        Thread t3 = new Thread(g1);

        // setname() method is used to change
        // name of the thread
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");

        // start() method beginning the execution of threads
        // as JVM calls the run() method of thread
        t1.start();
        t2.start();
        t3.start();
    }
}
lc8prwob

lc8prwob4#

您可以在不需要同步的情况下执行此任务。只需确保增量操作是原子操作。使用AtomicLong及其方法而不是counter++
static AtomicLong counter = new AtomicLong(0);
counter.addAndGet(1);

相关问题