博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java线程浅析[多线程同步]
阅读量:3746 次
发布时间:2019-05-22

本文共 10805 字,大约阅读时间需要 36 分钟。

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的案例可以参考,其对Semaphore做了简单的使用

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

你可能感兴趣的文章
对阿里达摩院里的语音实验室以及AI下的温暖人情的思考
查看>>
python_装饰器
查看>>
python_os模块
查看>>
python_random模块
查看>>
智能客服搭建(6)——5步骤之二富集数据资源
查看>>
python_json模块和pickle模块
查看>>
python_configparser模块、hashlib模块和subprocess模块
查看>>
项目:ATM+购物车(超级详解,已完结)
查看>>
一、算法基础+递归算法+查找算法
查看>>
走进算法工程师(算法工程师开发修炼之路)
查看>>
我与2021有个约
查看>>
100多个免费API接口分享 调用完全不限次数,以后总用得着
查看>>
leetcode每日一题【509. 斐波那契数】
查看>>
leetcode每日一题【830. 较大分组的位置】
查看>>
图灵测试过时了?
查看>>
leetcode每日一题【399. 除法求值】==》并查集&模板
查看>>
二、数据结构基础+栈+队列+迷宫问题
查看>>
python_多态
查看>>
项目:python3实现选课系统(超级详解,已完结)
查看>>
方法论:面向对象的软件工程
查看>>