线程同步Lock

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://glhcode.blog.csdn.net/article/details/70768583

转载请标明出处:
http://blog.csdn.net/hai_qing_xu_kong/article/details/70768583
本文出自:【顾林海的博客】


前言

除了上一篇文章的synchronized,Java还提供了同步代码块的另一种机制,这种机制基于Lock接口及其实现类。相比与synchronized来说更强大也更灵活。

关于线程相关知识可以查看:

有关线程的相关知识(1)
有关线程的相关知识(2)

关于synchronized的相关知识可以查看:

线程同步synchronized

Lock的使用

使用Lock能支持更灵活的同步代码块结构,也就是控制的获取和释放不出现在同一个块结构中。Lock接口提供了很多的功能,可以通过tryLock()方法来试图获取锁,如果锁已经被其他线程获取,它将返回false并继续往下执行代码。Lock接口允许分离读和写操作,允许多个读线程和只有一个写线程。

下面编写一个使用Lock接口和它的实现类ReentrantLock类来创建一个临界区:

public class Queue {

    // 声明一个锁对象,并且用ReentrantLock类初始化
    private final Lock queueLock = new ReentrantLock();

    /**
     * 打印信息 通过调用lock()方法获取对锁对象的控制
     */
    public void printJob() {
        queueLock.lock();
        System.out.println("正在打印信息..."+new Date());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            queueLock.unlock();
        }
    }

}
public class Event implements Runnable{

    private Queue queue;

    public Event(Queue queue){
        this.queue=queue;
    }

    @Override
    public void run() {
        queue.printJob();
    }

}
public class Client {
    public static void main(String[] args) {
        Queue queue=new Queue();

        Event event=new Event(queue);

        Thread[] thread=new Thread[10];

        for(int i=0,length=thread.length;i<length;i++){
            thread[i]=new Thread(event);
        }

        for(int i=0,length=thread.length;i<length;i++){
            thread[i].start();
        }

    }
}

上面Queue类中,定义来printJob()方法,通过lock()方法获取对锁对象的控制,最后通过unlock()方法释放对锁对象的控制。在主类中创建了10个线程,并启动这10个线程,运行程序,会在控制台上每隔1秒打印信息。

运行程序:
正在打印信息...Tue Apr 25 22:02:31 CST 2017
正在打印信息...Tue Apr 25 22:02:32 CST 2017
正在打印信息...Tue Apr 25 22:02:33 CST 2017
正在打印信息...Tue Apr 25 22:02:34 CST 2017
正在打印信息...Tue Apr 25 22:02:35 CST 2017
正在打印信息...Tue Apr 25 22:02:36 CST 2017
正在打印信息...Tue Apr 25 22:02:37 CST 2017
正在打印信息...Tue Apr 25 22:02:38 CST 2017
正在打印信息...Tue Apr 25 22:02:39 CST 2017
正在打印信息...Tue Apr 25 22:02:40 CST 2017

通过运行结果可以看出,对临界区的访问通过锁,来实现同一时间只有一个执行线程访问这个临界区,这里通过lock()方法获取对锁的控制,当某个线程访问这个方法时,如果没有其他线程获取对这个锁的控制,lock()方法将得到并且允许它立刻执行临界区。在线程离开临界区的时候,我们必须使用unlock()方法来释放它持有的锁,以让其他线程访问临界区,如果在离开临界区没有调用unlock()方法释放它持有的锁,其他线程将永远等待,从而导致死锁。


除了使用lock()方法来获取锁之外,Lock接口还提供了另一个方法来获取锁,即tryLock()方法。与lock()方法不同之处在于线程使用tryLock()不能够获取锁,tryLock()会立即返回,它不会将线程置入休眠,返回true表示线程获取了锁,false表示没有获取锁。


使用读写锁实现同步数据访问

ReadWriteLock接口和它唯一实现类ReentrantReadWriteLock,一个是读操作锁,另一个是写操作锁。使用读操作锁时可以允许多个线程同时访问,使用写操作锁时只允许一个线程进行。在一个线程执行写操作时,其他线程不能够执行读操作。

接下来通过ReadWriteLock接口来编写对某个对象的访问。

public class Product {

    private int number1;
    private int number2;

    private ReadWriteLock lock;

    public Product() {
        number1 = 10;
        number2 = 20;
        lock = new ReentrantReadWriteLock();
    }

    /**
     * 通过读锁获取对这个属性的访问
     * 
     * @return number1
     */
    public int getNumber1() {
        lock.readLock().lock();
        int number = number1;
        lock.readLock().unlock();
        return number;
    }

    /**
     * 通过读锁获取对这个属性的访问
     * 
     * @return number2
     */
    public int getNumber2() {
        lock.readLock().lock();
        int number = number2;
        lock.readLock().unlock();
        return number;
    }

    /**
     * 使用写锁来控制对这两个属性的访问
     * 
     * @param number1
     * @param number2
     */
    public void setNumber(int number1, int number2) {
        lock.writeLock().lock();
        System.out.println("写入number1:"+number1+"和number2:"+number2);
        this.number1 = number1;
        this.number2 = number2;
        lock.writeLock().unlock();
    }

}
/**
 * 读取类,读取Product的number1和number2
 * 
 * @author gulinhai
 *
 */
public class Reader implements Runnable {

    private Product product;

    public Reader(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("number1=" + product.getNumber1() + ";number2=" + product.getNumber2());
        }
    }

}
/**
 * 写入类,修改number1和number2
 * @author gulinhai
 *
 */
public class Writer implements Runnable {

    private Product product;

    public Writer(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {

            product.setNumber(i, i * 2);
            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
public class Client {
    public static void main(String[] args) {
        Product product = new Product();

        Reader[] reader = new Reader[5];
        Thread[] readerThread = new Thread[5];

        for (int i = 0, length = readerThread.length; i < length; i++) {
            reader[i] = new Reader(product);
            readerThread[i] = new Thread(reader[i]);
        }

        Writer writer = new Writer(product);
        Thread writerThread = new Thread(writer);

        for (int i = 0, length = readerThread.length; i < length; i++) {
            readerThread[i].start();
        }
        writerThread.start();

    }
}
运行结果:
写入number1:0number2:0
写入number1:1number2:2
number1=1;number2=2
number1=1;number2=2
number1=1;number2=2
number1=1;number2=2
number1=1;number2=2
写入number1:2number2:4
number1=2;number2=4
number1=2;number2=4
number1=2;number2=4
number1=2;number2=4
number1=2;number2=4
写入number1:3number2:6
number1=3;number2=6
number1=3;number2=6
number1=3;number2=6
写入number1:4number2:8
number1=4;number2=8
number1=3;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8
number1=4;number2=8

通过输出结果可以看出合理的利用ReentrantReadWriteLock类的两种锁,可以有效的避免数据不一致的问题。

ReentrantLock和ReentrantReadWriteLock类的构造器都含有一个布尔参数,它允许你控制这个两个类的行为,默认值是false,称为非公平模式,在非公平模式下,当有很多线程在等待锁时,锁将选择他们中的一个来访问临界区,这个选择是没有任何约束的。当布尔值为true时,称为公平模式,在公平模式下,当有很多线程在等待锁时,锁将选择它们中的一个来访问临界区,而且选择的是等待时机最长的。


锁中使用多条件

一个锁可能关联一个或者多个条件,这些条件通过Condition接口声明,目的允许线程获取锁并且查看等待的某一个条件是否满足,如果不满足就挂起直到某个线程唤醒它们,Condition接口提供了挂起线程和唤起线程的机制。

编程程序,利用Condition来解决生产者-消费者问题。

/**
 * 创建数据存储类EventStorage,并保存一个最大值maxSize和数据集合 LinkedList<Date>来保存存入的日期。
 * 
 * @author gulinhai
 *
 */
public class EventStorage {

    private int maxSize;
    private LinkedList<Date> storage;

    private ReentrantLock lock;

    private Condition condition1;
    private Condition condition2;

    public EventStorage() {
        lock = new ReentrantLock();
        condition1 = lock.newCondition();
        condition2 = lock.newCondition();
        this.maxSize = 10;
        this.storage = new LinkedList<>();
    }

    public int size() {
        return storage.size();
    }

    /**
     * <pre>
     * 获取锁,检查这个缓冲区是否满了,如果缓冲区满了,
     * 就调用条件condition1的await()方法
     * 等待空位出现,当其他线程调用条件condition1的
     * signal()或者signalAll()方法时,这个线程
     * 将被唤醒,在有空位后,线程会将数据保存到缓冲区中,
     * 并调用条件condition2的signalAll()方法。
     * </pre>
     */
    public void set() {
        lock.lock();
        try {
            while (storage.size() == maxSize) {
                condition1.await();
            }
            storage.add(new Date());
            condition2.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    /**
     * <pre>
     * 获取锁,检查这个缓冲区是否为空,如果缓冲区为空,
     * 就调用条件condition2的await()方法
     * 等待数据出现,当其他线程调用条件condition2的
     * signal()或者signalAll()方法时,这个线程
     * 将被唤醒,在有数据后,线程会从缓冲区中取出数据,
     * 并调用条件condition1的signalAll()方法。
     * </pre>
     */
    public void get() {
        lock.lock();
        try {
            while (storage.size() == 0) {
                condition2.await();
            }
            storage.poll();
            condition1.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

}
/**
 * 生产者
 * @author gulinhai
 *
 */
public class Producer implements Runnable{

    private EventStorage storage;

    public Producer(EventStorage storage){
        this.storage=storage;
    }

    @Override
    public void run() {
        for(int i=0;i<100;i++){
            storage.set();
        }
    }

}
/**
 * 消费者
 * @author gulinhai
 *
 */
public class Consumber implements Runnable{

    private EventStorage storage;

    public Consumber(EventStorage storage){
        this.storage=storage;
    }

    @Override
    public void run() {
        for(int i=0;i<100;i++){
            storage.get();
        }
    }

}
public class Client {

    public static void main(String[] args) {
        EventStorage storage=new EventStorage();

        Producer producer=new Producer(storage);
        Thread producerThread=new Thread(producer);

        Consumber consumber=new Consumber(storage);
        Thread consumberThread=new Thread(consumber);

        producerThread.start();
        consumberThread.start();

        try {
            producerThread.join();
            consumberThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("缓冲区数据个数:"+storage.size());

    }

}
输出结果:
缓冲区数据个数:0

与锁绑定的所有条件对象都是通过Lock接口声明的newCondition()方法创建的,在使用条件的时候,必须获取这个条件绑定的锁,所以带条件的代码必须在调用Lock对象的lock()方法和unlock()方法之间,当线程调用条件的await()方法时,它将自动释放这个条件绑定的锁,其他某个线程才可以获取这个锁并且执行相同的操作,或者执行这个锁保护的另一个临界区代码。

展开阅读全文

线程同步lock同步不了

05-30

[code=C/C++]rnvoid CDXCDlg::OnBnClickedStart()rnrn // TODO: 在此添加控件通知处理程序代码rn OnEnChangeEdit1();rn cx.Lock ();rn if(x=="")rn CWinThread *pw=AfxBeginThread(function,NULL,0,0,0,0);rn elsern AfxMessageBox(x);rn cx.Unlock ();rnrnUINT function(LPVOID pParam)rnrn CTime time;rn CString strTime;rn nflag=true;rn while(nflag)rn rn time=CTime::GetCurrentTime();rn strTime=time.Format("%H:%M:%S");rn ::SetDlgItemText(AfxGetMainWnd()->m_hWnd,IDC_TIME,strTime);rn Sleep(1000);rn rn return 0;rnrnrnvoid CDXCDlg::OnBnClickedButton1()rnrn // TODO: 在此添加控件通知处理程序代码rn nflag=FALSE;rnrnrnvoid CDXCDlg::OnEnChangeEdit1()rnrn // TODO: 如果该控件是 RICHEDIT 控件,则它将不会rn // 发送该通知,除非重写 CDialog::OnInitDialog()rn // 函数并调用 CRichEditCtrl().SetEventMask(),rn // 同时将 ENM_CHANGE 标志“或”运算到掩码中。rnrn // TODO: 在此添加控件通知处理程序代码rn UpdateData();rn cx.Lock ();rn if(edit.GetWindowTextLength()==5)rn rn edit.GetWindowText(x);rn nflag1=FALSE;rn rn elsern CWinThread *px=AfxBeginThread(func,(LPVOID)edit,THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);rn cx.Unlock ();rnrnUINT func(LPVOID pParam)rnrn while(nflag1)rn Sleep(50);rn return 0;rnrn[/code]rnrn我想请教一下,为什么我把x锁定之后返回的x的值还是空呢?要如何才能真正的把x锁定之后操作,等输入框输入完毕之后再执行,主线程中的AfxMessageBox函数? 论坛

使用lock线程同步问题

03-12

想用lock写一个线程同步的例子 rn但是好像没有锁住操作 下面是我的代码rn[code=Java]rnrnimport java.util.Random;rnimport java.util.concurrent.locks.Lock;rnimport java.util.concurrent.locks.ReentrantLock;rnrnpublic class TestThread extends Threadrnrn Random ran = new Random(); rn static int i = 0;rn ReentrantLock lock = new ReentrantLock();rn rn public void run()rn rn lock.lock(); rn play();rn lock.unlock();rn rn rn rn rn public void play()rn rn while(true)rn rn if( i > 50)rn rn break;rn rn rn int x = (int)(Math.random()*1000+1); rn System.out.println(Thread.currentThread()+"**** the indexof is: "+i+" the number is:"+x);rn i++; rn rn rn rn rn public static void main(String[] args)rn rn new TestThread().start();rn new TestThread().start();rn rn rn rn rnrnrn[/code]rnrn貌似没有锁住对I的操作rnrn下面是代码的执行结果rnrn[color=#808000]Thread[Thread-1,5,main]**** the indexof is: 0 the number is:137rnThread[Thread-0,5,main]**** the indexof is: 0 the number is:563[/color]rnThread[Thread-1,5,main]**** the indexof is: 1 the number is:624rnThread[Thread-0,5,main]**** the indexof is: 2 the number is:324rnThread[Thread-1,5,main]**** the indexof is: 3 the number is:543rnThread[Thread-0,5,main]**** the indexof is: 4 the number is:296rnThread[Thread-1,5,main]**** the indexof is: 5 the number is:171rnThread[Thread-0,5,main]**** the indexof is: 6 the number is:267rnThread[Thread-1,5,main]**** the indexof is: 7 the number is:360rnThread[Thread-0,5,main]**** the indexof is: 8 the number is:126rnThread[Thread-1,5,main]**** the indexof is: 9 the number is:658rnThread[Thread-0,5,main]**** the indexof is: 10 the number is:291rnThread[Thread-1,5,main]**** the indexof is: 11 the number is:72rnThread[Thread-0,5,main]**** the indexof is: 12 the number is:910rnThread[Thread-1,5,main]**** the indexof is: 13 the number is:159rnThread[Thread-0,5,main]**** the indexof is: 14 the number is:453rnThread[Thread-1,5,main]**** the indexof is: 15 the number is:109rnThread[Thread-0,5,main]**** the indexof is: 16 the number is:123rnThread[Thread-1,5,main]**** the indexof is: 17 the number is:23rn[color=#808000]Thread[Thread-0,5,main]**** the indexof is: 18 the number is:708rnThread[Thread-0,5,main]**** the indexof is: 20 the number is:158rnThread[Thread-0,5,main]**** the indexof is: 21 the number is:451[/color]Thread[Thread-0,5,main]**** the indexof is: 22 the number is:426rnThread[Thread-0,5,main]**** the indexof is: 23 the number is:286rnThread[Thread-1,5,main]**** the indexof is: 22 the number is:715rnThread[Thread-0,5,main]**** the indexof is: 24 the number is:729rnThread[Thread-1,5,main]**** the indexof is: 25 the number is:564rnThread[Thread-0,5,main]**** the indexof is: 26 the number is:495rnThread[Thread-1,5,main]**** the indexof is: 27 the number is:281rnThread[Thread-0,5,main]**** the indexof is: 28 the number is:543rnThread[Thread-1,5,main]**** the indexof is: 29 the number is:599rnThread[Thread-0,5,main]**** the indexof is: 30 the number is:529rnThread[Thread-1,5,main]**** the indexof is: 31 the number is:98rnThread[Thread-0,5,main]**** the indexof is: 32 the number is:866rnThread[Thread-1,5,main]**** the indexof is: 33 the number is:278rnThread[Thread-0,5,main]**** the indexof is: 34 the number is:355rnThread[Thread-1,5,main]**** the indexof is: 35 the number is:76rnThread[Thread-0,5,main]**** the indexof is: 36 the number is:745rnThread[Thread-1,5,main]**** the indexof is: 37 the number is:717rnThread[Thread-0,5,main]**** the indexof is: 38 the number is:755rnThread[Thread-1,5,main]**** the indexof is: 39 the number is:918rnThread[Thread-0,5,main]**** the indexof is: 40 the number is:401rnThread[Thread-1,5,main]**** the indexof is: 41 the number is:448rnThread[Thread-0,5,main]**** the indexof is: 42 the number is:467rnThread[Thread-1,5,main]**** the indexof is: 43 the number is:390rnThread[Thread-0,5,main]**** the indexof is: 44 the number is:775rnThread[Thread-1,5,main]**** the indexof is: 45 the number is:282rnThread[Thread-0,5,main]**** the indexof is: 46 the number is:542rnThread[Thread-1,5,main]**** the indexof is: 47 the number is:97rnThread[Thread-0,5,main]**** the indexof is: 48 the number is:272rnThread[Thread-1,5,main]**** the indexof is: 49 the number is:755rnThread[Thread-0,5,main]**** the indexof is: 50 the number is:481rnrnrn在上面两处位置出现了这样的东东 希望达人可以帮忙 rnrn谢谢了rn 论坛

[!!!help!!!] 线程同步,互斥锁lock的问题...lock为何不起作用???愚翁,速马等老大请进... [!!!help!!!]

08-09

写了一个简单的测试程序,发现有些很迷惑的地方...rnrn先贴出程序的主要部分:rnrnprivate System.Windows.Forms.Button button1;rnrnprivate Object syncObj;rnrnprivate Thread t1;rnprivate Thread t2;rnrnprivate volatile bool runT1;rnprivate volatile bool runT2;rnrnprivate void Form1_Load(object sender, System.EventArgs e)rnrn syncObj = new object();rnrn runT1 = true;rn runT2 = true;rnrnrnprivate void testObj()rnrn syncObj = "t1";rnrnrnprivate void testT1()rnrn Console.WriteLine("t1 started");rnrn lock(syncObj)rn rn Console.WriteLine("t1 locked syncObj");rnrn while(runT1)rn rn //syncObj = "t1";rnrn Thread.Sleep(1000);rn rn rnrn rnprivate void testT2()rnrn Console.WriteLine("t2 started");rn rn Thread.Sleep(1000);rnrn while(runT2)rn rn lock(syncObj)rn rn Console.WriteLine("t2 locked syncObj");rn rn Thread.Sleep(1000);rn rn rnrnrnrnprivate void button1_Click(object sender, System.EventArgs e)rnrn runT1 = true;rn runT2 = true;rnrn t1 = new Thread(new ThreadStart(testT1));rn t2 = new Thread(new ThreadStart(testT2));rnrn t1.Name = "t1";rn t2.Name = "t2";rnrn t1.IsBackground = true;rn t2.IsBackground = true;rnrn t1.Start();rn t2.Start();rnrn button1.Enabled = false;rnrnrn在线程t1一开始就已经锁住了syncObj,要知道t1退出才会释放syncObj的锁,这个时候t2才能够获得syncObj的锁,rnrn上面的代码实现了上面所说的逻辑,输出:rnt1 startedrnt1 locked syncObjrnt2 startedrnrn但是如果将testT1()中被屏蔽的语句“syncObj = "t1";” 取消屏蔽,rn程序就会出现t1在lock(syncObj)的期间,只要一修改sycnObj的值,rnt2马上就可以获得syncObj的锁...rn输出:rnt1 is startrnt1 locked syncObjrnt2 is startrnt2 locked syncObjrnt2 locked syncObjrnt2 locked syncObjrnt2 locked syncObjrnt2 locked syncObjrn...rn...rnrnrn在t1的生存期内,一直都没有释放过syncObj的锁,rn但是只要一修改它的值,t2就可以获取它的锁并对其进行操作呢? 论坛

没有更多推荐了,返回首页