Java多线程(三)——多线程实现同步

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xcymorningsun/article/details/87933246

目录

一、引言

二、synchronize同步

1、synchronize代码块

2、synchronize方法

三、lock同步

1、普通lock+condition

2、lock+condition高级应用

3、lock+读写锁

四、总结


一、引言

先介绍两个概念

线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。

同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。

为什么使用同步?

多线程给我们带来了很大的方便,但是同时也给我们带来了一个致命的问题,当我们对线程共享数据进行非原子操作时,会带来知名的错误。当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。这就是多线程同步提上议程的原因,解决多线程安全问题。

举个例子:假设银行里某一用户账户有1000元,线程A读取到1000,并想取出这1000元,并且在栈中修改成了0但还没有刷新到堆中,线程B也读取到1000,此时账户刷新到银行系统中,则账户的钱变成了0,这个时候也想去除1000,再次刷新到行系统中,账号的钱变成0,这个时候A,B都取出1000元,但是账户只有1000,显然出现了问题。针对上述问题,假设我们添加了同步机制,那么就可以很容易的解决。

tips:java1.5新引入了concurrent包,里面都是关于并发的,里面还有locks和atomic两个包,很值得学习,使用方便。

二、synchronize同步

1、synchronize代码块

这里举了个复杂点的例子,模仿的生产者消费者的模式,每次添加一个数,然后读出一个数,添加后不能再次添加,读出后不能再次读出。synchronize(this){}中this为同步锁,可以是任意对象,这里是用的该类本身,只要每次锁一样就能保证两个线程再synchronize代码块中的执行是同步的,所以每次只能读取或者添加。

wait和notify是线程等待和唤醒线程的意思,每当线程wait后被notify会从等待位置继续执行,使用这个是为了在线程中进行通信,使多个线程按照自己的想法有序执行(当添加数据线程添加后在添加该线程就会wait,当读取数据线程读取数据后就会notify添加线程继续执行)。

创建多线程类

package com.lock;

public class Testlock {

    public static void main(String [] args)
    {

        data();

    }

    /***
     * synchronize
     */
    static void data()
    {
        final DataSynchronize data=new DataSynchronize();
        new Thread(new Runnable() {
            public void run() {
                for(int i=0;i<1000000000;i++)
                {
                    data.write();

                }
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                for(int i=0;i<1000000000;i++)
                {
                    data.get();

                }
            }
        }).start();
    }



}

 封装的共享数据及方法类

package com.lock;

/**
 * synchronize
 * 两个线程,一个存数据一个取数据——多个线程全部互斥
 */
class DataSynchronize {
        boolean hasdata;
        int x;

    public  void write()
    {
       synchronized (this)
       {
           //有数据
           if (hasdata==true)
           {
               try {
                   this.wait();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
           //没数据
           System.out.println(Thread.currentThread().getName()+"准备添加数据");
           x=(int)(Math.random()*100000);
           try {
               Thread.sleep(100);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println(Thread.currentThread().getName()+"添加数据"+x);
           //通过上述步骤有了数据,并唤醒取数据
           hasdata=true;
           this.notify();
       }

    }

    public  synchronized void get()
    {
        synchronized (this)
        {
            //没有数据
            if (hasdata==false)
            {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"准备读取数据");
            //x=(int)Math.random();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"读取数据为"+x);
            //通过上述步骤没有数据,并唤醒添加数据
            hasdata=false;
            this.notify();
        }

    }

}

2、synchronize方法

这里第一个类没有变化,主要对第二个类进行了改变,把synchronize放在方法上了,说明此方法是线程同步安全的,如在单例模式中getinstance方法中也要使用synchronize关键字。这个比较简单,需要注意的是synchronize加在非静态方法上锁是对象锁,加在静态对象上市类锁。

创建多线程类

package com.lock;

public class Testlock {

    public static void main(String [] args)
    {

        data();

    }

    /***
     * synchronize
     */
    static void data()
    {
        final DataSynchronize data=new DataSynchronize();
        new Thread(new Runnable() {
            public void run() {
                for(int i=0;i<1000000000;i++)
                {
                    data.write();

                }
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                for(int i=0;i<1000000000;i++)
                {
                    data.get();

                }
            }
        }).start();
    }



}

封装共享数据及方法类

/**
 * synchronize
 * 两个线程,一个存数据一个取数据——多个线程全部互斥
 */
class DataSynchronize {
        boolean hasdata;
        int x;
        public synchronized void write()
        {
            //有数据
            if (hasdata==true)
            {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //没数据
            System.out.println(Thread.currentThread().getName()+"准备添加数据");
             x=(int)(Math.random()*100000);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"添加数据"+x);
            //通过上述步骤有了数据,并唤醒取数据
            hasdata=true;
            this.notify();
        }

        public  synchronized void get()
        {
            //没有数据
            if (hasdata==false)
            {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"准备读取数据");
             //x=(int)Math.random();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"读取数据为"+x);
            //通过上述步骤没有数据,并唤醒添加数据
            hasdata=false;
            this.notify();
        }
    }

三、lock同步

1、普通lock+condition

首先最简单的lock就是把synchronize代码块外皮去掉,在代码块最开始添加lock.lock(),在代码块末尾添加lock.unlock(),所以很简单就不写例子了,lock是在jdk1.5出来的,在lock包里面。

这个方法要达到的效果和上述例子中一样,所以使用了condition,condition主要起条件限制的作用,它的await与signal与上面的wait和notify一个意思,为了使多个线程间通信控制线程执行顺序。

创建多线程类

public class Testlock {

    public static void main(String [] args)
    {

        //data();
        datacondition();
        //dataconditonOrder();
        //dataLockReadWrite();

    }

    /**
     * lock+condition
     */
    static void  datacondition()
    {
        final DataLockcondition data=new DataLockcondition();

        new Thread(new Runnable() {
            public void run() {
                for(int i=0;i<1000000000;i++)
                {
                    data.write();

                }
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                for(int i=0;i<1000000000;i++)
                {
                    data.get();

                }
            }
        }).start();
    }


  

}

封装共享数据和方法类

/**
 * lock condition
 * 两个线程,一个存数据一个取数据——多个线程全部互斥
 */
public class DataLockcondition {
    boolean hasdata;
    int x;
    Lock lock=new ReentrantLock();
    Condition condition=lock.newCondition();
    public void write()
    {
        lock.lock();
        try{
            //有数据
            if (hasdata==true)
            {
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //没数据
            System.out.println(Thread.currentThread().getName()+"准备添加数据");
            x=(int)(Math.random()*100000);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"添加数据"+x);
            //通过上述步骤有了数据,并唤醒取数据
            hasdata=true;
            condition.signal();
        }finally {
            lock.unlock();
        }


    }

    public  void get()
    {
        lock.lock();
        try{
            //没有数据
            if (hasdata==false)
            {
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"准备读取数据");
            //x=(int)Math.random();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"读取数据为"+x);
            //通过上述步骤没有数据,并唤醒添加数据
            hasdata=false;
            condition.signal();
        }finally {
            lock.unlock();
        }


    }
}

2、lock+condition高级应用

condition既然和synchronize中的wait和notify差不多为什么还使用它呢?是因为condition可以创建多个控制节点,然而synchronize中的代码块只有一个锁可以控制,下面展示下强大的功能。

让主线程、sub1线程、sub2线程中的循环逐条按顺序输出,多么牛逼!!

创建多线程类

public class Testlock {

    public static void main(String [] args)
    {

        //data();
        //datacondition();
        dataconditonOrder();
        //dataLockReadWrite();

    }


    /**
     * lock+condition+order
     */
    static void dataconditonOrder()
    {
        final DataLockConditonOrder lockConditonOrder=new DataLockConditonOrder();
        new Thread(new Runnable() {
            public void run() {
                for (int i=0;i<10;i++)
                {
                    lockConditonOrder.main(i);
                }
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                for (int i=0;i<10;i++)
                {
                    lockConditonOrder.sub1(i);
                }
            }
        }).start();

        for (int i=0;i<10;i++)
        {
            lockConditonOrder.sub2(i);
        }
    }




}

封装共享数据和方法类

/**
 * 三个线程,按照顺序执行——多个线程互斥,有序
 */
public class DataLockConditonOrder {
    int flag=0;
    Lock lock=new ReentrantLock();
    Condition condition1=lock.newCondition();
    Condition condition2=lock.newCondition();
    Condition condition3=lock.newCondition();
    void main(int order)
    {
        lock.lock();

        try{
            //没有轮到1执行
            if (flag!=0)
            {
                try {
                    condition1.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //轮到1执行
            for(int i=0;i<10;i++)
            {
                System.out.println("主线程:"+Thread.currentThread().getName()+"第"+order+"次循环输出"+i);
            }
            flag=1;
            condition2.signal();
        }
        finally {
            lock.unlock();
        }

    }
    void  sub1(int order)
    {
        lock.lock();
        try{
            //没有轮到2执行
            if(flag!=1)
            {
                try {
                    condition2.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //轮到2执行
            for(int i=0;i<20;i++)
            {
                System.out.println("sub1线程:"+Thread.currentThread().getName()+"第"+order+"次循环输出"+i);
            }
            flag=2;
            condition3.signal();
        }
        finally {
            lock.unlock();
        }


    }
    void  sub2(int order)
    {
        lock.lock();
        //没有轮到3执行
        try{
            if (flag!=2)
            {
                try {
                    condition3.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //轮到3执行
            for(int i=0;i<30;i++)
            {
                System.out.println("sub2线程:"+Thread.currentThread().getName()+"第"+order+"次循环输出"+i);
            }
            flag=0;
            condition1.signal();
        }
        finally {
            lock.unlock();
        }


    }


}

输出结果

主线程:Thread-0第0次循环输出0
主线程:Thread-0第0次循环输出1
主线程:Thread-0第0次循环输出2
主线程:Thread-0第0次循环输出3
主线程:Thread-0第0次循环输出4
主线程:Thread-0第0次循环输出5
主线程:Thread-0第0次循环输出6
主线程:Thread-0第0次循环输出7
主线程:Thread-0第0次循环输出8
主线程:Thread-0第0次循环输出9
sub1线程:Thread-1第0次循环输出0
sub1线程:Thread-1第0次循环输出1
sub1线程:Thread-1第0次循环输出2
sub1线程:Thread-1第0次循环输出3
sub1线程:Thread-1第0次循环输出4
sub1线程:Thread-1第0次循环输出5
sub1线程:Thread-1第0次循环输出6
sub1线程:Thread-1第0次循环输出7
sub1线程:Thread-1第0次循环输出8
sub1线程:Thread-1第0次循环输出9
sub1线程:Thread-1第0次循环输出10
sub1线程:Thread-1第0次循环输出11
sub1线程:Thread-1第0次循环输出12
sub1线程:Thread-1第0次循环输出13
sub1线程:Thread-1第0次循环输出14
sub1线程:Thread-1第0次循环输出15
sub1线程:Thread-1第0次循环输出16
sub1线程:Thread-1第0次循环输出17
sub1线程:Thread-1第0次循环输出18
sub1线程:Thread-1第0次循环输出19
sub2线程:main第0次循环输出0
sub2线程:main第0次循环输出1
sub2线程:main第0次循环输出2
sub2线程:main第0次循环输出3
sub2线程:main第0次循环输出4
sub2线程:main第0次循环输出5
sub2线程:main第0次循环输出6
sub2线程:main第0次循环输出7
sub2线程:main第0次循环输出8
sub2线程:main第0次循环输出9
sub2线程:main第0次循环输出10
sub2线程:main第0次循环输出11
……………………………………………………

3、lock+读写锁

读写锁跟数据库读写锁很像,就是读与读异步,读与写同步,写与写同步。从而达到数据不会出错。

程序目的达到上述目的,为读数据上读锁,写数据上写锁。

创建线程类

public class Testlock {

    public static void main(String [] args)
    {

        //data();
        //datacondition();
        //dataconditonOrder();
        dataLockReadWrite();

    }

    /**
     * lockReadwrite
     */
    static void dataLockReadWrite()
    {
        final DataLockReadwrite data=new DataLockReadwrite();

        for (int i=0;i<3;i++) {
            new Thread(new Runnable() {
                public void run() {
                        data.write();
                }
            }).start();
        }

        for (int i=0;i<3;i++){
            new Thread(new Runnable() {
                public void run() {
                        data.get();
                }
            }).start();
        }

    }

}

封装共享数据和方法类

/**
 * 多个线程,读取相同对象——多个线程,分类
 */
public class DataLockReadwrite {
    boolean hasdata;
    int x;
    ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
    //Condition condition=lock.newCondition();
    public void write()
    {


            //有数据
/*            if (hasdata==true)
            {
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }*/
            //没数据
            lock.writeLock().lock();

            System.out.println(Thread.currentThread().getName()+"准备添加++数据");
            x=(int)(Math.random()*100000);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"添加++数据"+x);
            //通过上述步骤有了数据,并唤醒取数据

            lock.writeLock().unlock();

    }

    public  void get()
    {

            //没有数据
/*            if (hasdata==false)
            {
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }*/
            lock.readLock().lock();

            System.out.println(Thread.currentThread().getName()+"准备读取——数据");
            //x=(int)Math.random();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"读取数据——为"+x);
            //通过上述步骤没有数据,并唤醒添加数据

            lock.readLock().unlock();

    }
}

输出结果:

添加数据内部都为原子操作,而读取数据可以被打断

Thread-0准备添加++数据
Thread-0添加++数据60461
Thread-2准备添加++数据
Thread-2添加++数据98717
Thread-1准备添加++数据
Thread-1添加++数据18315
Thread-5准备读取——数据
Thread-3准备读取——数据
Thread-4准备读取——数据
Thread-5读取数据——为18315
Thread-3读取数据——为18315
Thread-4读取数据——为18315

四、总结

  • synchronize代码块
  • synchronize方法
  • lock基本操作
  • lock+condition
  • lock+读写锁

猜你喜欢

转载自blog.csdn.net/xcymorningsun/article/details/87933246