多线程(一)线程同步

一,线程的同步有以下方法

  1,使用synchronized实现同步方法;

  2,使用非依赖属性实现同步;

  3,在同步代码块中使用条件;

  4,使用锁实现同步;

  5,使用读写同步数据访问;

  6,修改锁的公平性;

  7,在锁中使用多条件;

  多个执行线程共享一个资源的情况,是最常见的并发编程情况之一。在并发应用中常常遇到这样的情景:多个线程读写相同的数据,或者访问相同的文件或数据库连接。为了防止这些共享资源可能出现的错误或者数据不一致,我们必须实现一些机制来防止这些错误的发生。

  为了解决这些问题,人们引入了临界区(Critical Section)概念。临界区是一个用以访问共享资源的代码块,这个代码块在同一时间内只允许一个线程执行。

  Java提供了同步机制,当一个线程试图访问一个临界区时,它将使用一种同步机制来查看是不是已经有其他线程进入了临界区。如果没有其他线程进入临界区,它就被同步机制挂起,直到进入的线程离开这个临界区。如果在等待进入临界区的线程不止一个,JVM会选择其中一个,其余的将继续等待。

二,使用synchronized实现同步方法

  每一个用synchronized关键字声明的方法都是临界区。在Java中,同一个对象的临界区,在同一时间只有一个允许被访问。

  静态方法则有不同的行为。用synchronized关键字声明的静态方法,同时只能够被一个执行线程访问,但是其他线程可以访问这个对象的非静态方法。必须非常谨慎这一点,因为两个线程可以同时访问一个对象的两个不同的synchronized方法,即其中一个是静态方法,另一个是非静态方法。如果两个方法都改变了相同的数据,将会出现数据不一致的错误。

案例:

 1 //创建名为Account的账号类,它是银行账户模型,只有一个double类型的属性balance
 2 public class Account {
 3
 4     private double balance;
 5
 6     // 实现getBalance(),setBalance()方法来写入和读取余额balance 的值。
 7     public double getBalance()
 8     {
 9         return balance;
10     }
11
12     public void setBalance(double balance)
13     {
14         this.balance = balance;
15     }
16
17     /**
18      * 实现addAmount()方法。它会将传入的数量加入到余额balance中,
19      * 并且在同一时间只允许一个线程改变这个值,
20      * 所以我们使用synchronized关键字将这个方法标记成临界区。
21      * @param amount
22      */
23     public synchronized void addAmount(double amount)
24     {
25         double tmp = balance;
26         try {
27             TimeUnit.MILLISECONDS.sleep(10);
28         } catch (InterruptedException e) {
29             e.printStackTrace();
30         }
31         tmp += amount;
32         balance = tmp;
33     }
34     /**
35      * 实现subtranctAmount()方法。它会将传入的数量从余额中扣除,
36      * 并且在同一时间只允许一个线程改变这个值,
37      * 所以我们使用synchronized关键字将这个方法标记成临界区。
38      * @param amount
39      */
40     public synchronized void subtranctAmount(double amount)
41     {
42         double tmp = balance;
43         try {
44             TimeUnit.MILLISECONDS.sleep(10);
45         } catch (InterruptedException e) {
46             e.printStackTrace();
47         }
48         tmp -= amount;
49         balance = tmp;
50     }
51 }

实现一个ATM模拟类BANK。它使用subtractAmount()方法对账户余额进行扣除。这个类实现Runnable接口以作为线程执行。

1 public class Bank implements Runnable{
2 }

为这个类增加账户类Account对象,用构造器初始化这个对象。

1 private Account account;
2
3 public Bank(Account account) {
4     this.account = account;
5 }

实现run()方法。它将调用subtranctAmount()方法对账户余额进行扣除,并循环执行100次。

1 public void run()
2 {
3     for (int i = 0; i < 100; i++) {
4         account.subtranctAmount(100);
5     }
6
7 }
8         

实现公司模拟类Company。它使用addAmount() 对账户的余额进行充值。这个类实现Runnable接口以作为线程运行。

1 public class Company implements Runnable {}

为Company类增加账户类Account 对象,用构造器初始化这个对象。

1 private Account account;
2
3 public Company(Account account) {
4     this.account = account;
5 }

实现run()方法。它将调用addAmount()方法对账户余额进行充值,并循环执行100次。

1 public void run()
2 {
3     for (int i = 0; i < 100; i++) {
4         account.addAmount(100);
5     }
6 }

范例主程序

 1 public static void main(String[] args) throws InterruptedException
 2     {
 3         //创建账户类初始值1000
 4         Account account=new Account();
 5         account.setBalance(1000);
 6
 7         //创建公司类
 8         Company company=new Company(account);
 9         Thread companyThread=new Thread(company);
10
11         //创建ATM模拟类
12         Bank bank=new Bank(account);
13         Thread bankThread=new Thread(bank);
14
15         //显示初始余额
16         System.out.println("Account: initial balance:"+account.getBalance());
17
18         //启动两个线程
19         companyThread.start();
20         bankThread.start();
21
22         //使用join()等待线程运行完成
23         companyThread.join();
24         bankThread.join();
25
26         //显示最终结果
27         System.out.println("Account: final balance:"+account.getBalance());
28     }

  在这个案例中通过使用tmp来临时存储账户余额,已经制造了一个错误的情景:这个临时变量先获取余额,然后进行数额累加,之后把最终结果更新为账户余额。此外,案例中还通过sleep()方法增加了延时,使得正在执行这个方法的线程休眠10ms,而此时其他线程也可能会执行这个方法,因此可能会改变余额,引发错误。而synchroniized关键字机制避免了这类错误的发生。

  我们先把addAmount()和subtractAmount()方法的synchroniized关键字去掉看下运行结果:

  

  如果多次运行这个程序,会有不同的结果。因为JVM并不保证线程执行的顺序。

  在把synchroniized关键字加上运行结果:

  

  synchroniized关键字的使用,保证了再并发程序中对共享数据的正确访问。

  一个对象的方法采用synchroniized关键字进行声明,只能被一个线程访问。如果线程A正在执行一个同步方法syncMethodA(),线程B要执行这个对象的其他同步方法syncMethodB(),线程B将被阻塞直到线程A访问完。但如果线程B访问的是这个类的不同对象,那么两个线程都不会被阻塞。

更多信息

  synchroniized关键字会降低系统的性能,因此只能在并发情景中需要修改共享数据的方法上使用它。

  可以使用递归调用被synchroniized关键字声明的方法。当线程访问一个对象的同步方法时,它还可以调用这个对象的其他同步方法,也包含正在执行的方法,而不必再次去获取这个方法的使用权。

  我们可以通过synchroniized关键字来保护代码块的访问。临界区的访问应尽可能的短。

时间: 2024-10-10 18:03:55

多线程(一)线程同步的相关文章

mfc小工具开发之定时闹钟之---多线程急线程同步

一.MFC对多线程编程的支持 MFC中有两类线程,分别称之为工作者线程和用户界面线程.二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环. 工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等.用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等.但对于Win32的API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务. 在MFC中,一般用全局函数Afx

MFC——9.多线程与线程同步

Lesson9:多线程与线程同步 程序.进程和线程是操作系统的重点,在计算机编程中,多线程技术是提高程序性能的重要手段.本文主要讲解操作系统中程序.进程和线程之间的关系,并通过互斥对象和事件对象实例说明多线程和线程同步技术. 1.      程序.进程和线程 1.1  程序和进程 程序是计算机指令的集合,它以文件的形式存储在磁盘上.进程通常被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动.进程是资源申请.调度和独立运行的单位,因此,它使用系统中的运行资源:而程序不能

java多线程之 ---- 线程同步

java多线程之线程同步 线程同步 定义:同步是指在同一时间段内只能运行一个线程. 分类:同步方法.同步块. 作用:安全解决共享问题. 同步块: 语法: synchronized (同步对象) { 需要同步的代码; } 例子: public class ThreadDemo implements Runnable{ private int ticket = 5; public void run(){ for(int i=1;i<=5;i++){ synchronized (this){ if(t

Linux程序设计学习笔记----多线程编程线程同步机制之互斥量(锁)与读写锁

互斥锁通信机制 基本原理 互斥锁以排他方式防止共享数据被并发访问,互斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个互斥锁逻辑上绑定之后,对该资源的访问操作如下: (1)在访问该资源之前需要首先申请互斥锁,如果锁处于开状态,则申请得到锁并立即上锁(关),防止其他进程访问资源,如果锁处于关,则默认阻塞等待. (2)只有锁定该互斥锁的进程才能释放该互斥锁. 互斥量类型声明为pthread_mutex_t数据类型,在<bits/pthreadtypes.h>中有具体的定义. 互斥量

C#多线程之线程同步3

在上一篇C#多线程之线程同步2中,我们主要学习了AutoResetEvent构造.ManualResetEventSlim构造和CountdownEvent构造,在这一篇中,我们将学习Barrier构造.ReaderWriterLockSlim构造和SpinWait构造. 七.使用Barrier构造 在这一小节中,我们将学习一个比较有意思的同步构造:Barrier.Barrier构造可以帮助我们控制多个等待线程达到指定数量后,才发送通知信号,然后所有等待线程才能继续执行,并且在每次等待线程达到指

关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文)

Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文) 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其他博客主的博客,并且将我个人的理解也分享给大家. 一.对于线程同步和同步锁的理解(注:分享了三篇高质量的博客) 以下我精心的挑选了几篇博文,分别是关于对线程同步的理解和如何选择线程锁以及了解线程锁的作用范围. <一>线程同步锁的选择 1. 这里我推荐下Java代码质量改进之:同步对象的选择这篇博文. 2. 以上推荐的博文是以卖火车票为例

系统API函数实现多线程及线程同步

1.线程的创建 须包含头文件:#include <windows.h> HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ); lpThreadAttributes:指向SECURI

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

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

多线程之线程同步

多线程内容大致分两部分,其一是异步操作,可通过专用,线程池,Task,Parallel,PLINQ等,而这里又涉及工作线程与IO线程:其二是线程同步问题,鄙人现在学习与探究的是线程同步问题. 通过学习<CLR via C#>里面的内容,对线程同步形成了脉络较清晰的体系结构,在多线程中实现线程同步的是线程同步构造,这个构造分两大类,一个是基元构造,一个是混合构造.所谓基元则是在代码中使用最简单的构造.基元构造又分成两类,一个是用户模式,另一个是内核模式.而混合构造则是在内部会使用基元构造的用户模

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

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