java线程浅析[多线程同步]

java线程浅析[多线程同步]

1:什么是多线程同步?
2:怎么解决多线程同步的问题?
3:synchronized同步锁机制
4:java中自带的一个同步锁ReentrantLock
5:通过java中的Semaphore实现线程的同步


什么是多线程同步?

首先举一个生活中最简单的例子,公用厕所中的一个大号坑,假设每一个想上厕所的人都是一个线程,本来按照正常的理解来说应该是一个接着一个,后面一个等前一个使用完毕出来之后才有使用权,但是万一出现这样一种情况,就是突然两个人同时抢到了一个坑 ,那么谁先用??而java的多线程同步相对来说也近似这样的一种场景,为了解决对共享资源同时并发的这样一种情况来定的

 多线程同步就是为了多个线程访问同一个数据对象,对数据造成破坏或者对程序结构引起很大的影响。它保证了多线程能够安全访问竞争资源的一个手段。

上述的例子中就是:厕所对所有人来说是共享的竞争资源。那么怎么有效的能够按照顺序来进行,就必须要给厕所安个门,安把锁,这样只要一个人抢占了资源后,那么就不会再出现另外一个也进入使用的场景,保证了其能够按照顺序对共享资源进行依次的访问。

下面上一个错误的代码:看看大概有多少个人同时去抢占厕所

package com.demo.thread;

    /**
         * 多人上厕所的问题,厕所只有一个坑,如果这样算,那现在如果有100个人想去上厕所,其必须是要一个一个进去,
         * 也就是这里的共享资源是厕所,而这100个人都可以去看成线程的操作
         * 
         * 主要是使用synchronized进行加锁同步
         * @author Administrator
         */
    public class ThreadTestSync {

            public static void main(String[] args) {
            //当多个线程对同一个共享资源进行竞争的时候,这个时候就是比较容易出现
                Toilet toilet= new Toilet();
                for(int i = 0 ;i<100 ; i++){
                    new Thread(new Person(toilet)).start();
            }
        }
    }

    class Toilet {
        void inToilet(){
            System.out.println("开厕所门");
        }

        void outToilet(){
            System.out.println("关厕所门");
        }
    }

    class Person extends Thread{

        Toilet toilet;
     public Person(Toilet toilet){
         this.toilet = toilet;
     }

        @Override
        public void run() {
            // TODO Auto-generated method stub
                toilet.inToilet();
                try {
                    sleep(5);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                toilet.outToilet();
            }

    }

下面是输出的结果的部分截图:从图中可以看出,这100个人很多时候都是去厕所,但是只有一个坑,在肯定是不符合逻辑的。
这里写图片描述

所以线程同步为的是解决什么问题??为的就是解决同一时间对同一资源访问的时候,容易出现的并发问题,也就是同时有多个线程对同一资源进行了访问,导致资源显示出现异常的现象

2:怎么解决多线程同步的问题?

那么在多线程出现并发的这样的一种情况下面,我们应该怎么去解决呢,目前主要就是采用加锁的形式去解决。试想一下什么是锁。门锁是用来干嘛的,一把锁一般情况下对应一个钥匙,按照上面的逻辑,我只要给厕所门上一把锁,进去的人把们锁着,然后出来后把锁还给另外一个人,这样按照顺序的形式,就可以了。那么这100个人也就是依次的去开门上厕所,目前最基础的就是下面3种方法:

 1:synchronized同步锁机制
 2:ReentrantLock锁,
 3:Semaphore实现线程信号量对线程锁的控制

3:synchronized同步锁机制

synchronized在英文中的意思本来就是有同步的意思。所以java中也是采用synchronized关键字来进行修饰的,
实现线程同步的关系,注意无论synchronized采用什么样的加锁方式,只要当加锁的代码域执行完毕之后。锁是有系统自动进行释放的

同步的方式:

1. synchronized去修饰方法:

直接去修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象,
注意:也就是整方法执行完毕的时候,锁才会去释放,切记,不要一个外部方法中去同时调用两个加锁的方法,
因为这个时候会导致,如果前面一个锁被释放掉之后,后面的方法如果没上锁
     这就同样会引起其他线程会获取锁资源,而导致同步失败
     如:public synchronized void inToilet(){}

2. synchronized去修饰代码块:

    //修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象(用的相对是比较多的),
    如:
    synchronized (Person.class) {
                toilet.inToilet();
        try {
            sleep(5);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }           
        toilet.outToilet();
    }

3 synchronized去修饰静态方法:

     syncchronized同步静态方法块的时候,这个锁是针对所有对象的,属于类的,不管是任何对象,其在做counter增加的时候都是会加锁操作的,
    这个时候对于静态变量的加锁操作同步的,同一时间,只允许一个person进行递增的操作
    如:
     public synchronized static void personCount(){
         person_count++;
         System.out.println("person_count:"+person_count);
     }

4. synchronized去修饰方法中的具体的执行:

 修饰方法中具体的实现,同样是针对一个对象来说的
 void function(){
    synchronized (this) {
        inToilet();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        outToilet();
    }
}

以下是完善后的代码,有兴趣的可以看一下

package com.demo.thread;

/**
 * 多人上厕所的问题,厕所只有一个坑,如果这样算,那现在如果有100个人想去上厕所,其必须是要一个一个进去,
 * 也就是这里的共享资源是厕所,而这100个人都可以去看成线程的操作
 * 
 * 主要是使用synchronized进行加锁同步
 * @author Administrator
 */
public class ThreadTestSync {

    public static void main(String[] args) {
        //当多个线程对同一个共享资源进行竞争的时候,这个时候就是比较容易出现
        Toilet toilet= new Toilet();
        for(int i = 0 ;i<100 ; i++){
            new Thread(new Person(toilet)).start();
        }
    }

}

class Toilet {
    void inToilet(){
        System.out.println("开厕所门");
    }

    void outToilet(){
        System.out.println("关厕所门");
    }

    /*
     * 直接去修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象,
     * 注意:也就是整方法执行完毕的时候,锁才会去释放,切记,不要一个外部方法中去同时调用两个加锁的方法,因为这个时候会导致,如果前面一个锁被释放掉之后,后面的方法如果没上锁
     * 这就同样会引起其他线程会获取锁资源,而导致同步失败
     * 
     * 如:synchronized void inToilet(){}
     *    synchronized void outToilet();
     *    void function(){inToilet(),outToilet()};  //锁被加给了子方法,这样就会导致
     * 
     */

    //  synchronized void function(){
    //      inToilet();
    //      outToilet();
    //  }

    /**
     * 修饰方法中具体的实现,同样是针对一个对象来说的
     */
    void function(){
        synchronized (this) {
            inToilet();
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            outToilet();
        }
    }
}


class Person extends Thread{
     Toilet toilet;

     static int person_count = 0;

     public Person(Toilet toilet){
         this.toilet = toilet;
     }

     /**
      * syncchronized同步静态方法块的时候,这个锁是针对所有对象的,属于类的,不管是任何对象,其在做counter增加的时候都是会加锁操作的,这个时候
      * 对于静态变量的加锁操作同步的,同一时间,只允许一个person进行递增的操作
      */
     public synchronized static void personCount(){
         person_count++;
         System.out.println("person_count:"+person_count);
     }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        //修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象(用的相对是比较多的)
//      synchronized (Person.class) {
//          toilet.inToilet();
            personCount();
            try {
                sleep(1);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
//          toilet.function();  

//          toilet.outToilet();
//      }   
    }
}

代码中混合注释掉的部分可能有点多,可以根据上面的分类来进行区分,代码上面的代码也是可以直接进行run的,因为加锁的方式比较多,生成的结果也都是一样的,所以在这里就不再去执行代码了

java中自带的一个同步锁ReentrantLock

ReentrantLock是jdk在高一点版本以后,在原来锁机制的基础之上封装而来的。也是为了解决同步锁机制,而生的,
它的使用比synchronized的使用更加简便。而且其内部也封装了很多查验,校验类的方法
package com.demo.thread;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 多人上厕所的问题,厕所只有一个坑,如果这样算,那现在如果有100个人想去上厕所,其必须是要一个一个进去,
 * 也就是这里的共享资源是厕所,而这100个人都可以去看成线程的操作
 * 
 * 主要是使用synchronized进行加锁同步
 * @author Administrator
 */
public class ThreadTetsSyncTwo {

    public static void main(String[] args) {
        //当多个线程对同一个共享资源进行竞争的时候,这个时候就是比较容易出现
        ToiletTwo toilet= new ToiletTwo();
        ReentrantLock lock = new ReentrantLock();
        for(int i = 0 ;i<100 ; i++){
            new Thread(new Human(toilet,lock)).start();
        }

    }

}

class ToiletTwo{
    void inToilet(){
        System.out.println("开厕所门");
    }

    void outToilet(){
        System.out.println("关厕所门");
    }

}


class Human extends Thread{
    ToiletTwo toilet;
     ReentrantLock lock;

     public Human(ToiletTwo toilet,ReentrantLock lock){
         this.toilet = toilet;
         this.lock = lock;
     }

    @Override
    public void run() {
        // TODO Auto-generated method stub
            //对执行的代码块进行上锁机制
            lock.lock();
            toilet.inToilet();
            try {
                sleep(5);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            toilet.outToilet();
            //执行完毕之后就释放锁
            lock.unlock();
    }

}

在上面的方法基础之上,稍微做了一些修改。一定要注意,所有争夺共享资源的线程一定是要同一把锁,如果是多把锁的话,那就并不适用,所有在这里我的锁是从外部传入进去的。那样,所有的线程都只会去针对这一把锁

通过java中的Semaphore实现线程的同步

Semaphore,是负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。也是操作系统中用于控制进程同步互斥的量。这个相对来说可能用的也比较少一点吧,但是在解决线程同步的时候却也是非常的重要

Semaphore其实是一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。拿到信号量的线程可以进入代码,否则就等待。通过acquire()和release()获取和释放访问许可。

首先先看一下Semaphore的构造参数吧:

/**
     * Creates a {@code Semaphore} with the given number of
     * permits and nonfair fairness setting.
     *
     * @param permits the initial number of permits available.
     *        This value may be negative, in which case releases
     *        must occur before any acquires will be granted.
     * 
     */
    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

    /**
     * Creates a {@code Semaphore} with the given number of
     * permits and the given fairness setting.
     *
     * @param permits the initial number of permits available.
     *        This value may be negative, in which case releases
     *        must occur before any acquires will be granted.
     * @param fair {@code true} if this semaphore will guarantee
     *        first-in first-out granting of permits under contention,
     *        else {@code false}
     */
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

Semaphore的构造方法中显示出来的都是有参的构造方法:

permits - 初始的可用许可数目。此值可能为负数,在这种情况下,必须在授予任何获取前进行释放。
fair - 如果此信号量保证在争用时按先进先出的顺序授予许可,则为 ture,否则为 false。

如果在上述案例的情况下,该怎么使用Semaphore来进行线程的同步呢??
直接上代码了,记住其为permits 为1的时候也就相当于对线程进行了加锁操作

package com.demo.thread;

import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 多人上厕所的问题,厕所只有一个坑,如果这样算,那现在如果有100个人想去上厕所,其必须是要一个一个进去,
 * 也就是这里的共享资源是厕所,而这100个人都可以去看成线程的操作
 * 
 * 主要是使用synchronized进行加锁同步
 * @author Administrator
 */
public class ThreadTetsSyncTwo {

    public static void main(String[] args) {
        //当多个线程对同一个共享资源进行竞争的时候,这个时候就是比较容易出现
        ToiletTwo toilet= new ToiletTwo();
        Semaphore lock = new Semaphore(1,false);
        for(int i = 0 ;i<100 ; i++){
            new Thread(new Human(toilet,lock)).start();
        }

    }

}

class ToiletTwo{
    void inToilet(){
        System.out.println("开厕所门");
    }

    void outToilet(){
        System.out.println("关厕所门");
    }

}


class Human extends Thread{
    ToiletTwo toilet;
    Semaphore lock;

     public Human(ToiletTwo toilet,Semaphore lock){
         this.toilet = toilet;
         this.lock = lock;
     }

    @Override
    public void run() {
        // TODO Auto-generated method stub
            //对执行的代码块进行上锁机制
            try {
                lock.acquire();
            } catch (InterruptedException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
            System.out.print(this.getName()+":");
            toilet.inToilet();
            try {
                sleep(500);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            toilet.outToilet();
            //执行完毕之后就释放锁
            lock.release();
    }

}

但是关于Semaphore的使用,可能很多时候都并不是用来进行加锁操作的,而是进行多线程执行数量上的控制,比如android在图片加载的时候一次性刷了100个图片。,但是我们不可能同时让其加载100个,而是选择一次只允许加载5个,那这个时候我们就开放5个许可,这样同时执行的也就只有5个线程。多用在与线程池配合使用上

如果想了解Semaphore的案例可以参考http://blog.csdn.net/shihuacai/article/details/8856526,其对Semaphore做了简单的使用

最后谢谢观看。写的不好的地方可以指出

欢迎访问博客

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值