synchronized用法

synchronized关键字是一种同步机制,用于控制多个线程访问共享资源的方式。这是防止线程干扰和内存一致性错误的一种方法。synchronized可以用于方法或代码块。

同步方法:

当一个线程调用 synchronizedMethod 时,其他线程必须等待,直到该线程执行完毕才能访问这个方法。

 示例一

public class Counter {
    private int count = 0;

    // 同步方法
    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();

        // 创建两个线程,同时增加计数器的值
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        thread1.start();
        thread2.start();

        try {
            // 等待两个线程执行完毕
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 输出最终计数器的值
        System.out.println("最终计数器的值:" + counter.getCount());
    }
}

synchronized用法_第1张图片

解释

Counter 类:
  1. private int count=0; 私有成员变量,用于存储计数值。这个计数器是共享资源,多个线程将会同时访问它。

  2. public synchronized void increment():这是一个同步方法,用于增加计数值。由于它被标记为 synchronized,因此只有一个线程可以同时访问这个方法。当一个线程调用 increment 方法时,其他线程必须等待,直到该线程执行完毕。这确保了对计数器的递增操作是线程安全的。

  3. public int getCount():这是一个普通的非同步方法,用于获取计数值。由于没有使用 synchronized 关键字,所以多个线程可以同时访问它。

Main类:
  1. main 方法中,首先创建了一个 Counter 对象 counter,用于存储计数值。

  2. 然后,创建了两个线程 thread1thread2,它们将同时执行 counter.increment() 方法来增加计数器的值。每个线程循环执行 1000 次递增操作。

  3. 使用 thread1.start()thread2.start() 启动这两个线程。

  4. 使用 thread1.join()thread2.join() 等待这两个线程执行完毕。这样确保了在输出最终计数值之前,两个线程都已经完成了它们的工作。

  5. 最后,使用 counter.getCount() 获取最终的计数器值,并将其打印到屏幕上。

示例二

public class SynchronizedExample {
    // 私有成员变量,用于存储计数值
    private int count = 0;

    // 同步方法,用于增加计数值
    public synchronized void increment() {
        count++;
    }

    // 同步方法,用于获取计数值
    public synchronized int getCount() {
        return count;
    }
}
public class SynchronizedExampleTest {
    public static void main(String[] args) {
        // 创建 SynchronizedExample 实例
        SynchronizedExample example = new SynchronizedExample();

        // 创建多个线程执行增加计数的操作
        Thread[] incrementThreads = new Thread[6];
        for (int i = 0; i < incrementThreads.length; i++) {
            incrementThreads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    example.increment();
                }
            });
        }

        // 创建多个线程执行获取计数的操作
        Thread[] getCountThreads = new Thread[6];
        for (int i = 0; i < getCountThreads.length; i++) {
            getCountThreads[i] = new Thread(() -> {
                int count = example.getCount();
                System.out.println("Count: " + count);
            });
        }

        // 启动所有增加计数的线程
        for (Thread thread : incrementThreads) {
            thread.start();
        }

        // 启动所有获取计数的线程
        for (Thread thread : getCountThreads) {
            thread.start();
        }

        // 等待所有线程完成
        try {
            for (Thread thread : incrementThreads) {
                thread.join();
            }
            for (Thread thread : getCountThreads) {
                thread.join();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 输出最终的计数值
        int finalCount = example.getCount();
        System.out.println("Final Count: " + finalCount);
    }
}

synchronized用法_第2张图片

解释

SynchronizedExample
  1. count:这是一个私有成员变量,用于存储计数值。
  2. increment() 方法:这是一个同步方法,用于安全地增加count的值。由于这个方法是synchronized的,当一个线程在执行这个方法时,其他线程必须等待,直到该方法执行完毕。
  3. getCount() 方法:这也是一个同步方法,用于安全地获取count的当前值。同样,当一个线程执行这个方法时,其他线程不能同时执行任何其他同步方法(比如increment)。
SynchronizedExampleTest
  1. main方法中,首先创建了一个SynchronizedExample的实例。
  2. 接着创建了12个线程,其中6个用于执行increment方法,另外6个用于执行getCount方法。
  3. 每个执行increment的线程都会将count增加1000次。
  4. 每个执行getCount的线程都会获取并打印当前的count值。
  5. 使用thread.start()启动所有线程。
  6. 使用thread.join()等待所有线程完成。这确保了在输出最终count值之前,所有增加和获取计数的操作都已完成。

同步代码块:

使用同步代码块可以保护共享资源

public class SynchronizedBlockExample {
    public static void main(String[] args) {
        // 创建共享资源对象
        SharedResource sharedResource = new SharedResource();

        // 创建线程1,用于递增计数器
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                sharedResource.increment();
            }
        });

        // 创建线程2,用于递增计数器
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                sharedResource.increment();
            }
        });

        // 启动线程1和线程2
        thread1.start();
        thread2.start();

        try {
            // 等待线程1和线程2执行完毕
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 获取最终计数器值并输出
        int finalCount = sharedResource.getCount();
        System.out.println("最终计数值: " + finalCount);
    }
}

class SharedResource {
    private int count = 0;
    private Object lock = new Object(); // 创建一个锁对象

    public void increment() {
        // 使用锁对象创建同步代码块,确保线程安全
        synchronized (lock) {
            count++;
        }
    }

    public int getCount() {
        // 使用锁对象创建同步代码块,确保线程安全
        synchronized (lock) {
            return count;
        }
    }
}

 解释

  1. 创建共享资源对象:创建了一个 SharedResource 的实例,该实例包含一个计数器和一个锁对象。

  2. 创建线程1和线程2:创建了两个线程,它们分别执行递增计数器的操作。

  3. 启动线程1和线程2:通过调用 start 方法启动线程,使它们开始执行递增操作。

  4. 等待线程1和线程2执行完毕:使用 join 方法等待线程1和线程2执行完毕,以确保获取最终计数值时线程已经完成。

  5. 获取最终计数器值并输出:使用 getCount 方法获取最终的计数器值,然后将其输出到控制台。

  6. 使用锁对象创建同步代码块:在 incrementgetCount 方法内部使用锁对象 lock 创建同步代码块,确保对计数器的递增和读取操作是线程安全的。只有一个线程可以同时持有 lock 锁,因此多线程不会同时访问计数器。这种同步机制确保了线程安全性。

静态方法同步:

synchronized静态方法同步提供了一种简单的方式来确保线程安全,但过度使用同步可能会导致性能问题,因为只能有一个线程同时执行同步方法。因此,在设计中需要权衡线程安全性和性能之间的取舍,仅在需要确保线程安全的情况下使用synchronized静态方法同步。

package three;

public class SynchronizedStaticMethodExample {
    public static void main(String[] args) {
        // 创建两个线程,分别访问静态同步方法
        Thread thread1 = new Thread(() -> {
            SharedResource.staticIncrement();
        });

        Thread thread2 = new Thread(() -> {
            SharedResource.staticIncrement();
        });

        // 启动线程1和线程2
        thread1.start();
        thread2.start();

        try {
            // 等待线程1和线程2执行完毕
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 输出最终的计数值
        int finalCount = SharedResource.getStaticCount();
        System.out.println("最终静态计数值: " + finalCount);
    }
}

class SharedResource {
    private static int staticCount = 0;

    // 静态同步方法,用于增加计数值
    public static synchronized void staticIncrement() {
        staticCount++;
    }

    // 静态同步方法,用于获取计数值
    public static synchronized int getStaticCount() {
        return staticCount;
    }
}

synchronized用法_第3张图片

 解释

  1. 创建两个线程:在 main 方法中,创建了两个线程 thread1thread2,它们将分别访问静态同步方法。

  2. 启动线程1和线程2:使用 thread1.start()thread2.start() 启动这两个线程,使它们开始执行静态同步方法。

  3. 等待线程1和线程2执行完毕:使用 thread1.join()thread2.join() 等待线程1和线程2执行完毕,以确保获取最终的计数器值时线程已经完成。

  4. 输出最终的计数值:使用 SharedResource.getStaticCount() 获取最终的静态计数器值,并将其输出到控制台。

  5. 静态同步方法:SharedResource 类中包含两个静态同步方法 staticIncrementgetStaticCount,它们分别用于递增计数器和获取计数器值。由于这两个方法都被标记为 synchronized,只有一个线程可以同时访问它们,从而确保了对静态计数器的递增和读取操作是线程安全的。

 总结如下:

  1. synchronized 关键字用于控制多个线程对共享资源的访问,以确保线程安全。

  2. 同步方法:通过将方法标记为 synchronized,可以确保在同一时刻只有一个线程可以执行该方法,其他线程必须等待。

  3. 同步代码块:通过使用同步代码块,可以在特定的代码块内部使用锁对象来确保对共享资源的安全访问。

  4. 静态方法同步:通过将静态方法标记为 synchronized,可以确保在同一时刻只有一个线程可以执行该静态方法,用于保护静态数据成员的访问。

  5. 使用同步可以防止线程干扰、内存一致性错误、竞态条件等多线程问题。

  6. 注意,过度使用同步可能会导致性能问题,因此需要在线程安全性和性能之间进行权衡。

 

你可能感兴趣的:(java知识点,java,开发语言)