谈起线程,有很重要的一点是关于解决线程间数据同步,或者说资源的访问问题。

参考java编程思想 书籍,在21章第三节开始讲有关共享资源问题,所以接下来探讨关于线程同步机制;解决线程并发引起的数据访问问题,实际上就要控制访问的顺序和确定相关的规则,一般的我们可以用以下方式去实现:

synchronized 关键字实现 可以参考文章

synchronized 又分为同步方法和同步代码块

同步方法:

private synchronized void method(){

        // 在方法定义中加一个关键字synchronized
    }

情况分类:

  • 一个实例的多个线程,一次只能有一个线程进入非静态同步的方法
  • 多个实例的线程能同时进入同一个非静态同步的方法
  • 多个实例的线程进入静态的同步方法

一个实例的多个线程,一次只能有一个线程进入非静态同步的方法


代码验证:

public class SyncObject {

    Object lock = new Object();

    public static synchronized void fun(){
        System.out.println(Thread.currentThread().getName() + " is running");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " is stop");

    }



    private synchronized void fun1(){
        System.out.println("fun1" + Thread.currentThread().getName() + " is running");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("fun1" + Thread.currentThread().getName() + " is stop");
    }

    private void fun2(){
        synchronized (lock){
            System.out.println("fun2" + Thread.currentThread().getName() + " is running");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("fun2" + Thread.currentThread().getName() + " is stop");
        }
    }
class NewThread extends Thread {

    //static 保证是同一个实例 不会创建多次
    static SyncObject syncFunc = new SyncObject();

    @Override
    public void run() {
        syncFunc.fun();

//        aMethod();
//        bMethod();
    }

    public synchronized void aMethod(){
        System.out.println("NewThread Amethod start");
        try {
            sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("NewThread Amethod end");
    }


    public synchronized void bMethod(){
        System.out.println("NewThread Bmethod start");
        try {
            sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("NewThread Bmethod end");
    }
}

 验证结果:

public static void main(String[] args) {
        NewThread newThread1 = new NewThread();
        NewThread newThread2 = new NewThread();
        NewThread newThread3 = new NewThread();
//
        newThread1.start();
        newThread2.start();
        newThread3.start();

}

 结果显示:

Thread-0 is running
Thread-0 is stop
Thread-2 is running
Thread-2 is stop
Thread-1 is running
Thread-1 is stop

多个实例的线程能同时进入同一个非静态同步的方法——也就是说多个实例之间互相没有关系,互不干扰

代码验证:把NewThread类中的SyncObject 前的static 去掉,就会形成多个实例对象:

class NewThread extends Thread {

    SyncObject syncFunc = new SyncObject();

    @Override
    public void run() {
        syncFunc.fun();

//        aMethod();
//        bMethod();
    }

 ......

}

然后打印结果是:

Thread-0 is running
Thread-1 is running
Thread-2 is running
Thread-0 is stop
Thread-2 is stop
Thread-1 is stop

 多个实例的线程进入静态的同步方法——一次只能有一个类实例进入

public class SyncObject {

    Object lock = new Object();

    //这里加一个static 变成访问静态方法
    public static synchronized void fun(){
        System.out.println(Thread.currentThread().getName() + " is running");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " is stop");

    }

 ......

 打印结果:

Thread-0 is running
Thread-0 is stop
Thread-2 is running
Thread-2 is stop
Thread-1 is running
Thread-1 is stop

 对比结论:非静态和静态的区别主要在于(以同步方法为例): 
非静态的同步方法是锁定类的实例的,而静态的同步方法是锁定类的;

也就是说,对于非静态的同步方法,在同一时刻,一个类的一个实例中,只有一个线程能进入同步的方法。但是对于多个实例,每一个实例的一个线程都可以进入同一同步的方法。

同步方法块 (也叫临界区)

 private void method(){
        //用关键字锁住方法部分实现
        synchronized (lock){
            .....
        }
    }

 

一、当两个并发线程访问同一个对象object中的具有相同锁的同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。 
二、当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块,但其他的synchronized(this)同步代码块方法被阻塞;

对比:

Java 线程——共享受限资源

注 :从表中及实现打印可以得到 :synchronized void method() 和 synchronized (this) {} 锁住的对象是同一个对象。

 

voliate 关键字

volatile关键字为域变量的访问提供了一种免锁机制,相当于告诉虚拟机该域可能会被其他线程更新. 然后此变量不使用自己的工作内存,而是使用主存中的值,这样就避免了读写不一致的问题,具体的做法就是在变量前面加一个voliate 修饰符。这样线程间数据同步问题就解决了。

重入锁 ReentrantLock (源码解读

代码示例

class X {
 *   private final ReentrantLock lock = new ReentrantLock();
 *   // ...
 *
 *   public void m() {
 *     lock.lock();  // block until condition holds
 *     try {
 *       // ... method body
 *     } finally {
 *       lock.unlock()
 *     }
 *   }
 * }}<

该锁也叫显示锁,对比隐式锁synchrozied而言,我们可以在finally处理其他事情,而synchrozied 则不行。Reentrant中文和翻译为“重入”,则也叫重入锁,其概念指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。其意义是防止死锁。

可重入和不可重入的理解参考ReentrantLock的源码,或者 文章 。其原理就是内部有一个计数器,统计线程进入该锁的次数并且关联占用该锁的线程,同一个线程每次进入的时候计数器加一,出去减一,直到为零被释放。Synorized 也是重入锁,系统JDK 没有提供不可重入锁,这种锁可以自己编写一个出来,另外由于ReentrantLock的源码,又有了公平锁和非公平锁,其区别就是线程在队列中是否按照FIFO的顺序进行处理,是的话就是公平锁,不是即为非公平锁,显然会公平锁的吞吐量要大一些。这些概念可以研究源码继续深挖,这里就不深入了。

ThreadLocal  线程本地存储

这个ThreadLocal 原理是给每个线程一个本地存储,通过threadLocal 的内部类 ThreadLocalMap实现的,通过这个map去存储变量。这样数据就不是共享的,所以也就没有数据共享问题了。

原子类Atomic 实现 

Atomic 相关类的核心思想是CAS ,比较并交换  compareAndSwap . Atomic 都内置了一个unsafe类,都是在这完成的逻辑,并且compareAndSwap这个方法是通过c 代码完成的。

相关文章:

  • 2022-12-23
  • 2021-11-26
  • 2021-12-30
  • 2022-12-23
  • 2022-01-15
  • 2021-12-25
  • 2022-01-21
猜你喜欢
  • 2021-07-04
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-02-12
  • 2022-12-23
相关资源
相似解决方案