(二)线程同步_1---同步一个方法

同步一个方法(Synchronizing a method)

在并发编程中,最常见的情景莫过于多线程共享同一资源的情景了,例如多个线程同时读写相同的数据或者同时访问相同的文件或数据库连接,如果不采取一个控制机制,那么就会造成数据的不一致甚至是错误发生;

为了解决这些问题,引入了临界区域(critical section)的概念,一个临界区域是指一个用来控制资源访问的代码块,以保证被访问的资源不会同时被多个线程访问,从而保证了数据的一致性;

java中提供了Synchronization机制,当一个线程访问一个临界区域时,会首先使用这些同步机制找出是否有多个线程在同时执行这个临界区域代码块,如果不是,那么这个线程正常访问,如果是,那么这个线程将会挂起,直到正在执行这个临界区域代码块的线程结束后才恢复,当有多个线程被挂起,在恢复后JVM采用一定算法选择哪个线程将会访问这个临界区域代码块;

Java中提供的最基本的同步机制有两个:

  • 关键字synchronized
  • Lock接口以及其实现

在接下来的实例中,将会展示最基本的方法同步,即利用synchronized关键字去控制一个方法的并发访问;如果一个对象有多个方法的声明都有synchronized关键字,那么在同一时刻只有一个线程能够访问这些方法中的其中一个;如果有另外一个线程在同一时刻试图访问这个对象的任何一个带有synchronized声明的方法,都将会被挂起,直到第一个线程执行完这个方法;换句话说,方法声明带有synchronized关键字的方法都是一个临界区域(critical
section),对于同一个对象,同一时间只允许执行一个临界区域(critical section)。

举个通俗的例子:比如一个对象里面有两个都声明了synchronized关键字的方法,一个方法是读取文件A,并追加一行信息;另外一个方法是读取文件A,并追加两行信息;那么这两个方法都属于临界区域,不能够被两个线程同时访问的;如果同时访问了,那么两个方法读取相同文件A,并同时写入了不同的信息,返回的结果就错误了;从这个角度可以理解为什么同一时间只能有一个线程可以访问一个对象中其中一个带有synchronized关键字的方法;

然而对于静态方法,则有不同的行为;一个对象中有多个声明了synchronized的静态方法和多个非静态方法,在同一时刻,一个线程只能访问其中一个静态同步方法,然而另外一个线程可以访问其中一个非静态方法;对于这种情况需要非常小心,因为在同一时刻,两个线程可以访问一个对象的两种不同类型的同步方法:一个静态的,一个非静态的;

如果这两个方法控制的是相同的数据,这样有可能导致数据不一致;

下面实现一个例子:两个线程同时访问一个对象;一个线程向银行账户中转钱,一个线程从相同银行账户中取钱,分别观察利用同步机制和不同同步机制的区别

动手实现

1.创建一个账户对象

public class Account {
    private double balance;

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public synchronized void addAmount(double amount){
        double tmp=balance;
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tmp+=amount;
        balance=tmp;
    }

    public synchronized void subtractAmount(double amount) {
        double tmp=balance;
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tmp-=amount;
        balance=tmp;
    }
}

2.创建一个银行线程,用来取出钱

public class Bank implements Runnable {
    private Account account;

    public Bank(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        for (int i=0; i<100; i++){
            account.subtractAmount(1000);
        }
    }
}

3.创建一个公司线程用来转入钱

public class Company implements Runnable {
    private Account account;

    public Company(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        for (int i=0; i<100; i++){
            account.addAmount(1000);
        }
    }
}

4.创建一个监控线程,每隔2毫秒监控上面两个线程的状态,可以用来验证synchronized同步

public class StatusMonitor implements Runnable{
    private Thread[] threads;

    public StatusMonitor(Thread[] threads) {
        this.threads=threads;
    }

    private String status(){
        StringBuffer sb=new StringBuffer();
        sb.append("status list:\t\n");
        for (Thread thread : threads) {
            sb.append("\t").append(thread.getName()).append(" status:").append(thread.getState()).append("\n");
        }
        return sb.toString();
    }
    @Override
    public void run() {
        boolean flag=true;
        while(flag) {
            System.out.println(status());
            try {
                TimeUnit.MILLISECONDS.sleep(5);
            } catch (InterruptedException e) {
                flag=false;
            }
        }
    }
}

5.Main方法

public class Main {
    public static void main(String[] args) {
        Account account = new Account();
        account.setBalance(1000);

        Company company = new Company(account);
        final Thread companyThread = new Thread(company);

        final Bank bank = new Bank(account);
        final Thread bankThread = new Thread(bank);

        System.out.printf("Account : Initial Balance: %f\n", account.getBalance());
        companyThread.start();
        bankThread.start();

        // Monitor bankThread and companyThread's states
        StatusMonitor statusMonitor = new StatusMonitor(new Thread[]{companyThread, bankThread});
        Thread monitor = new Thread(statusMonitor);
        monitor.start();

        //Waiting for threads finished
        try {
            companyThread.join();
            bankThread.join();
            //Ending monitor
            monitor.interrupt();
            System.out.printf("Account : Final Balance: %f\n", account.getBalance());

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

可以观察监控线程的输出,这两个线程基本上都是一个处于阻塞,一个处于运行状态;

要点

理解synchronized的用法;synchronized关键字这种同步机制对性能上有一定的损耗;

synchronized关键字指定在方法签名上,同步的是整个方法;也可以指定更小的同步块,如在一个方法内部同步更小的部分:

public void method(){

// java code

synchornized(this){

// java code

}

}

这里有一个问题,一个方法里面sychronized同步块之前的代码会不会可以同时被多个线程执行呢?答案是可以被多个线程执行;下面的例子可以证实:

public class BlockTest extends Thread {
    private Block block;

    public BlockTest(Block block) {
        this.block=block;
    }

    @Override
    public void run() {
        block.read();
    }

    public static void main(String[] args) {
        Block block=new Block();
        BlockTest thread1=new BlockTest(block);
        BlockTest thread2=new BlockTest(block);
        thread1.start();
        thread2.start();

    }
    private static class Block{
        public void read(){
            System.out.printf("Thread %s start to read.\n", Thread.currentThread().getName());
            synchronized (this){
                System.out.printf("Current thread:%s\n",Thread.currentThread().getName());
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.printf("Thread %s read over.\n", Thread.currentThread().getName());
        }
    }
}

一次运行结果:

Thread Thread-0 start to read.

Thread Thread-1 start to read.

Current thread:Thread-0

Thread Thread-0 read over.

Current thread:Thread-1

Thread Thread-1 read over.

从运行结果的输出过程可以看到多个线程可以同时访问一个方法内部synchronized之前的部分,但是运行到synchronized部分,将只有一个线程继续运行,其它的线程将会被阻塞;

时间: 2024-08-07 01:42:55

(二)线程同步_1---同步一个方法的相关文章

获取线程处理结果的一个方法

package thread.demo; import java.util.ArrayList; import java.util.List; /** * @Description: * @创建人:[email protected] * @创建时间:2015-8-15 下午10:32:23 * */ public class ThreadFiledValue { /** * @param args * @throws InterruptedException */ public static v

java多线程二之线程同步的三种方法

java多线程的难点是在:处理多个线程同步与并发运行时线程间的通信问题.java在处理线程同步时,常用方法有: 1.synchronized关键字. 2.Lock显示加锁. 3.信号量Semaphore. 线程同步问题引入: 创建一个银行账户Account类,在创建并启动100个线程往同一个Account类实例里面添加一块钱.在没有使用上面三种方法的情况下: 代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

C# 多线程(二) 线程同步基础(上)

本系列的第一篇简单介绍了线程的概念以及对线程的一些简单的操作,从这一篇开始讲解线程同步,线程同步是多线程技术的难点.线程同步基础由以下几个部分内容组成 1.同步要领(Synchronization Essentials) 2.锁(Locking) 3.线程安全(Thread Safety) 4.事件等待句柄(Signaling with Event Wait Handles) 5.同步上下文(Synchronization Contexts) 同步要领(Synchronization Essen

C#线程同步的几种方法

我们在编程的时候,有时会使用多线程来解决问题,比如你的程序需要在后台处理一大堆数据,但还要使用户界面处于可操作状态:或者你的程序需要访问一些外部资源如数据库或网络文件等.这些情况你都可以创建一个子线程去处理,然而,多线程不可避免地会带来一个问题,就是线程同步的问题.如果这个问题处理不好,我们就会得到一些非预期的结果. 在网上也看过一些关于线程同步的文章,其实线程同步有好几种方法,下面我就简单的做一下归纳. 一.volatile关键字 volatile是最简单的一种同步方法,当然简单是要付出代价的

归纳一下:C#线程同步的几种方法

转自原文 归纳一下:C#线程同步的几种方法 我们在编程的时候,有时会使用多线程来解决问题,比如你的程序需要在后台处理一大堆数据,但还要使用户界面处于可操作状态:或者你的程序需要访问一些外部资源如数据库或网络文件等.这些情况你都可以创建一个子线程去处理,然而,多线程不可避免地会带来一个问题,就是线程同步的问题.如果这个问题处理不好,我们就会得到一些非预期的结果. 在网上也看过一些关于线程同步的文章,其实线程同步有好几种方法,下面我就简单的做一下归纳. 一.volatile关键字 volatile是

Linux下线程同步的几种方法

Linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量和信号量. 一.互斥锁(mutex) 锁机制是同一时刻只允许一个线程执行一个关键部分的代码.  1. 初始化锁 int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr); 其中参数 mutexattr 用于指定锁的属性(见下),如果为NULL则使用缺省属性. 互斥锁的属性在创建锁的时候指定,在LinuxThreads实

线程通讯和同步的两种实现方法

在多线程编程中,不免要涉及同步和通讯两个方面. 同步有两种方法实现,一种是利用synchronized标示,另外一种是加锁. 生成锁的对象的方法是:private static Lock lock = new ReentrantLock();Lock是一个接口,而Reentrantlock是一个实现的类.构造方法有:ReentrantLock()和ReentrantLock(fair:boolean)两种.其中第二种传递的是一个boolean值的参数,当设置为true时系统会按照等待的先后时间让

C# 多线程(二) 线程同步基础

本系列的第一篇简单介绍了线程的概念以及对线程的一些简单的操作,从这一篇开始讲解线程同步,线程同步是多线程技术的难点.线程同步基础由以下几个部分内容组成 1.同步要领(Synchronization Essentials) 2.锁(Locking) 3.线程安全(Thread Safety) 4.事件等待句柄(Signaling with Event Wait Handles) 5.同步上下文(Synchronization Contexts) 同步要领(Synchronization Essen

线程同步的几种方法

线程同步的方式包括:互斥锁.读写锁.条件变量.信号量和令牌. 互斥锁和读写锁: 提供对临界资源的保护,当多线程试图访问临界资源时,都必须通过获取锁的方式来访问临界资源.(临界资源:是被多线程共享的资源)当读写线程获取锁的频率 差别不大时,一般采用互斥锁,如果读线程访问临界资源的频率大于写线程,这个时候采用读写锁较为合适,读写锁允许多个读线程同时访问临界资源,读写线程必 须互斥访问临界资源.读写锁的实现采用了互斥锁,所以在读写次数差不多的情况下采用读写锁性能没有直接采用互斥锁来的高. 条件变量:提