线程同步基础

两种基本的同步机制:synchronized与锁

1. 同步方法

每一个被synchronized方法就是一个关键代码段,在同一时间,Java只能执行某个对象的其中一个关键代码块。然而,静态方法的行为不同,只有一个执行线程能够访问所有synchronized的静态方法中的一个,但是另一个线程可以访问该类的某个对象的非静态方法。也就是说,如果有两个同步方法,一个是静态的,另一个是非静态的,则两个线程是可以同时分别访问两个方法的。这一原则有可能导致两个方法去修改同一数据。例如,
class Account {
    public static double balance;
    public static synchronized void addAmount(double amount) {
        balance += amount;
    }

    public synchronized void substractAmount(double amount) {
        balance -= amount;
    }
}

2. 不同属性用不同对象同步

通常地,this关键字会被用来作为同步代码块的控制变量,但是也可以用其他的对象引用作为同步代码块的关键字。例如,用两个对象类分别作为某个类的两个属性的同步控制变量,这样两个线程可以同时独立地访问两个属性。
class Cinema {
    private long vacanciesCinema1;
    private long vacanciesCinema2;

    private final Object controlCinema1;
    private final Object controlCinema2;

    public Cinema() {
        controlCinema1 = new Object();
        controlCinema2 = new Object();

        vacanciesCinema1 = 20;
        vacanciesCinema2 = 20;
    }

    public boolean sellTickets1(long number) {
        synchronized (controlCinema1) {
            if (number <= vacanciesCinema1) {
                vacanciesCinema1 -= number;
                return true;
            }
            else {
                return false;
            }
        }
    }

    public boolean sellTickets2(long number) {
        synchronized (controlCinema2) {
            if (number <= vacanciesCinema2) {
                vacanciesCinema2 -= number;
                return true;
            }
            else {
                return false;
            }
        }
    }

    public long getVacanciesCinema1() {
        return vacanciesCinema1;
    }

    public long getVacanciesCinema2() {
        return vacanciesCinema2;
    }
}
 
 

3. wait/notify/notifyAll

在生产者-消费者模型中,通常用一个buffer作为共享的数据结构,当buffer满的时候,生产者不能再放入数据;当buffer空的时候,消费者不能取数据。对于以上两种情况,java在Object类中提供了wait notify notifyAll方法。wait方法需要在同步代码块中被调用,否则,将会抛出IllegalMonitorStateException。 当被调用时,JVM将会使当前线程sleep,同时释放控制同步代码块的对象锁,必须使用nofity或者notifyAll来唤醒该线程。

class EventStorage {
    int maxSize;
    LinkedList<Date> storage;
    
    public EventStorage() {
        maxSize = 10;
        storage = new LinkedList<>();
    }
    
    public synchronized void set() {
        while (storage.size() == maxSize) {
            try {
                wait();
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        storage.offer(new Date());
        System.out.printf("Set: %d\n", storage.size());
        notifyAll();
    }
    
    public synchronized void get() {
        while (storage.size() == 0) {
            try {
                wait();
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        storage.poll();
        System.out.printf("Get: %d\n", storage.size());
        notifyAll();
    }
}

4. Lock

Java提供了另一种同步代码块的机制,它是基于Lock接口和它的实现类(例如ReentrantLock)来实现的,这种机制更加强大和灵活,主要的优点表现在:
1)Lock接口允许更加复杂的结构,synchronized关键字必须要用结构化的方法来获取或者释放同步代码块;
2)Lock接口提供了一些额外的功能。例如tryLock()方法。
3)当只有一个写和多个读的线程时,Lock接口允许读写操作的分离
4)Lock接口的性能更高

class PrintQueue {
    private final Lock queueLock = new ReentrantLock();
    
    public void printJob(Object document) {
        queueLock.lock();

        try {
            long duration = (long)(Math.random()*10000);
            System.out.println(Thread.currentThread().getName() + ": PrintQueue: Printing a Job during " + (duration/1000) + " seconds");
            Thread.sleep(duration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            queueLock.unlock();
        }
    }
}

class Job implements Runnable {
    private PrintQueue printQueue;
    
    public Job(PrintQueue printQueue) {
        this.printQueue = printQueue;
    }

    @Override
    public void run() {
        System.out.printf("%s: Going to print a document\n", Thread.currentThread().getName());
        printQueue.printJob(new Object());
        System.out.printf("%s: The document has been printed\n", Thread.currentThread().getName()); 
    }
}

5. 读写锁

ReadWriteLock是所有Lock接口中最重要的接口之一,ReentrentReadWriteLock是它的唯一实现类。该类有两个锁,一个是读操作另一个是写操作。它能够同时包括多个读操作,但是只能有一个写操作。当某个线程执行写操作时,其他任何线程都不能执行读操作。

class PricesInfo {
    private double price1;
    private double price2;

    private ReadWriteLock lock;

    public PricesInfo() {
        price1 = 1.0;
        price2 = 2.0;
        lock = new ReentrantReadWriteLock();
    }

    public double getPrice1() {
        lock.readLock().lock();
        double value = price1;
        lock.readLock().unlock();
        return value;
    }

    public double getPrice2() {
        lock.readLock().lock();
        double value = price2;
        lock.readLock().unlock();
        return value;
    }

    public void setPrices(double price1, double price2) {
        lock.writeLock().lock();
        this.price1 = price1;
        this.price2 = price2;
        lock.writeLock().unlock();
    }
}

6. 公平锁

ReentrantLock和ReentrantReadWriteLock的构造函数可以传入一个boolean型的参数fair,该参数默认为false,如果设置为true,则在多个线程等待同一个锁时,选择等待时间最长的线程优先执行。

class MessageQueue implements MyQueue {
    private Lock queueLock = new ReentrantLock(true);

    public void printJob(Object document) {
        queueLock.lock();
        try {
            long duration = (long)(Math.random()*10000);
            System.out.println(Thread.currentThread().getName() + ": PrintQueue: Printing a Job during " + (duration/1000) + " seconds");
            Thread.sleep(duration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            queueLock.unlock();
        }

        queueLock.lock();
        try {
            long duration = (long)(Math.random()*10000);
            System.out.println(Thread.currentThread().getName() + ": PrintQueue: Printing a Job during " + (duration/1000) + " seconds");
            Thread.sleep(duration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            queueLock.unlock();
        }
    }
}

7. 一个锁的多个condition

class Buffer {
    private LinkedList<String> buffer;
    private int maxSize;
    private ReentrantLock lock;
    private Condition lines;
    private Condition space;
    private boolean pendingLines;

    public Buffer(int maxSize) {
        this.maxSize = maxSize;
        buffer = new LinkedList<>();
        lock = new ReentrantLock();
        lines = lock.newCondition();
        space = lock.newCondition();
        pendingLines = true; //是否还会有lines insert到Buffer中
    }

    public void insert(String line) {
        lock.lock();
        try {
            while(buffer.size() == maxSize) {
                space.wait();
            }
            buffer.offer(line);
            System.out.printf("%s: Inserted Line: %d\n", Thread.currentThread().getName(), buffer.size());
            lines.signalAll();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public String get() {
        String line = null;
        lock.lock();
        try {
            while ((buffer.size() == 0) && hasPendingLines()) {
                lines.await();
            }

            if(hasPendingLines()) {
                line = buffer.poll();
                System.out.printf("%s: Line Readed: %d\n", Thread.currentThread().getName(), buffer.size());
                space.signalAll();
            }
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }

        return line;
    }

    public boolean hasPendingLines() {
        return pendingLines || buffer.size() > 0;
    }

    public void setPendingLines(boolean pendingLines) {
        this.pendingLines = pendingLines;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值