Java 线程——共享受限资源

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

参考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)同步代码块方法被阻塞;

对比:

在这里插入图片描述

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

 

voliate 关键字

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

重入锁 ReentrantLock (源码解读

提到锁还有一种锁,就是接口 Lock, 它的常用实现类ReentrantLock 。比较二者

 synhicedlock
语法关键字一个接口
锁的释放执行完同步代码,释放锁手动释放,在finally中,调用unlock
锁的获取假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待
锁类型可重入、非公平、不可中断可重入 可判断 可公平(两者皆可)
性能低,使用少量同步高、可以同步大量代码

代码示例

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的顺序进行处理,是的话就是公平锁,不是即为非公平锁。

公平锁和非公平锁

公平锁,就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁, 否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。而ReentrackLock 默认还是非公平锁,因为效率高。

非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式

共享锁和排他锁

共享锁:就是查询的时候,如果没有修改,可以支持多线程查询;CountDownLatch 就是共享锁。

排它锁    就是修改的时候,锁定共享锁,停止查询,同时,锁定排它锁,只允许一个线程进行修改,修改完成后,再解开共享锁;

ThreadLocal  线程本地存储

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

原子类Atomic 实现 

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值