Java知多少(62)线程同步

当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用。达到此目的的过程叫做同步(synchronization)。像你所看到的,Java为此提供了独特的,语言水平上的支持。

同步的关键是管程(也叫信号量semaphore)的概念。管程是一个互斥独占锁定的对象,或称互斥体(mutex)。在给定的时间,仅有一个线程可以获得管程。当一个线程需要锁定,它必须进入管程。所有其他的试图进入已经锁定的管程的线程必须挂起直到第一个线程退出管程。这些其他的线程被称为等待管程。一个拥有管程的线程如果愿意的话可以再次进入相同的管程。

如果你用其他语言例如C或C++时用到过同步,你会知道它用起来有一点诡异。这是因为很多语言它们自己不支持同步。相反,对同步线程,程序必须利用操作系统源语。幸运的是Java通过语言元素实现同步,大多数的与同步相关的复杂性都被消除。

你可以用两种方法同步化代码。两者都包括synchronized关键字的运用,下面分别说明这两种方法。

使用同步方法

Java中同步是简单的,因为所有对象都有它们与之对应的隐式管程。进入某一对象的管程,就是调用被synchronized关键字修饰的方法。当一个线程在一个同步方法内部,所有试图调用该方法(或其他同步方法)的同实例的其他线程必须等待。为了退出管程,并放弃对对象的控制权给其他等待的线程,拥有管程的线程仅需从同步方法中返回。

为理解同步的必要性,让我们从一个应该使用同步却没有用的简单例子开始。下面的程序有三个简单类。首先是Callme,它有一个简单的方法call( )。call( )方法有一个名为msg的String参数。该方法试图在方括号内打印msg 字符串。有趣的事是在调用call( ) 打印左括号和msg字符串后,调用Thread.sleep(1000),该方法使当前线程暂停1秒。

下一个类的构造函数Caller,引用了Callme的一个实例以及一个String,它们被分别存在target 和 msg 中。构造函数也创建了一个调用该对象的run( )方法的新线程。该线程立即启动。Caller类的run( )方法通过参数msg字符串调用Callme实例target的call( ) 方法。最后,Synch类由创建Callme的一个简单实例和Caller的三个具有不同消息字符串的实例开始。

Callme的同一实例传给每个Caller实例。

 1 // This program is not synchronized.
 2 class Callme {
 3     void call(String msg) {
 4         System.out.print("[" + msg);
 5         try {
 6             Thread.sleep(1000);
 7         } catch(InterruptedException e) {
 8             System.out.println("Interrupted");
 9        }
10        System.out.println("]");
11     }
12 }
13
14 class Caller implements Runnable {
15     String msg;
16     Callme target;
17     Thread t;
18     public Caller(Callme targ, String s) {
19         target = targ;
20         msg = s;
21         t = new Thread(this);
22         t.start();
23     }
24     public void run() {
25         target.call(msg);
26     }
27 }
28
29 class Synch {
30     public static void main(String args[]) {
31         Callme target = new Callme();
32         Caller ob1 = new Caller(target, "Hello");
33         Caller ob2 = new Caller(target, "Synchronized");
34         Caller ob3 = new Caller(target, "World");
35         // wait for threads to end
36         try {
37           ob1.t.join();
38           ob2.t.join();
39           ob3.t.join();
40        } catch(InterruptedException e) {
41           System.out.println("Interrupted");
42        }
43     }
44 }

该程序的输出如下:

Hello[Synchronized[World]
]
]

在本例中,通过调用sleep( ),call( )方法允许执行转换到另一个线程。该结果是三个消息字符串的混合输出。该程序中,没有阻止三个线程同时调用同一对象的同一方法的方法存在。这是一种竞争,因为三个线程争着完成方法。例题用sleep( )使该影响重复和明显。在大多数情况,竞争是更为复杂和不可预知的,因为你不能确定何时上下文转换会发生。这使程序时而运行正常时而出错。

为达到上例所想达到的目的,必须有权连续的使用call( )。也就是说,在某一时刻,必须限制只有一个线程可以支配它。为此,你只需在call( ) 定义前加上关键字synchronized,如下:

1 class Callme {
2     synchronized void call(String msg) {
3         ...

这防止了在一个线程使用call( )时其他线程进入call( )。在synchronized加到call( )前面以后,程序输出如下:

[Hello]
[Synchronized]
[World]

任何时候在多线程情况下,你有一个方法或多个方法操纵对象的内部状态,都必须用synchronized 关键字来防止状态出现竞争。记住,一旦线程进入实例的同步方法,没有其他线程可以进入相同实例的同步方法。然而,该实例的其他不同步方法却仍然可以被调用。

同步语句

尽管在创建的类的内部创建同步方法是获得同步的简单和有效的方法,但它并非在任何时候都有效。这其中的原因,请跟着思考。假设你想获得不为多线程访问设计的类对象的同步访问,也就是,该类没有用到synchronized方法。而且,该类不是你自己,而是第三方创建的,你不能获得它的源代码。这样,你不能在相关方法前加synchronized修饰符。怎样才能使该类的一个对象同步化呢?很幸运,解决方法很简单:你只需将对这个类定义的方法的调用放入一个synchronized块内就可以了。

下面是synchronized语句的普通形式:

1 synchronized(object) {
2     // statements to be synchronized
3 }

其中,object是被同步对象的引用。如果你想要同步的只是一个语句,那么不需要花括号。一个同步块确保对object成员方法的调用仅在当前线程成功进入object管程后发生。

下面是前面程序的修改版本,在run( )方法内用了同步块:

 1 // This program uses a synchronized block.
 2 class Callme {
 3     void call(String msg) {
 4         System.out.print("[" + msg);
 5         try {
 6             Thread.sleep(1000);
 7         } catch (InterruptedException e) {
 8             System.out.println("Interrupted");
 9         }
10         System.out.println("]");
11     }
12 }
13
14 class Caller implements Runnable {
15     String msg;
16     Callme target;
17     Thread t;
18     public Caller(Callme targ, String s) {
19         target = targ;
20         msg = s;
21         t = new Thread(this);
22         t.start();
23     }
24
25     // synchronize calls to call()
26     public void run() {
27         synchronized(target) { // synchronized block
28             target.call(msg);
29         }
30     }
31 }
32
33 class Synch1 {
34     public static void main(String args[]) {
35         Callme target = new Callme();
36         Caller ob1 = new Caller(target, "Hello");
37         Caller ob2 = new Caller(target, "Synchronized");
38         Caller ob3 = new Caller(target, "World");
39
40         // wait for threads to end
41         try {
42             ob1.t.join();
43             ob2.t.join();
44             ob3.t.join();
45         } catch(InterruptedException e) {
46             System.out.println("Interrupted");
47         }
48     }
49 }

这里,call( )方法没有被synchronized修饰。而synchronized是在Caller类的run( )方法中声明的。这可以得到上例中同样正确的结果,因为每个线程运行前都等待先前的一个线程结束。

系列文章:

Java知多少(上)

Java知多少(39)interface接口

Java知多少(40)接口和抽象类的区别

Java知多少(41)泛型详解

Java知多少(42)泛型通配符和类型参数的范围

Java知多少(43)异常处理基础

Java知多少(44)异常类型

Java知多少(45)未被捕获的异常

Java知多少(46)try和catch的使用

Java知多少(47)多重catch语句的使用

Java知多少(48)try语句的嵌套

Java知多少(49)throw:异常的抛出

Java知多少(50)Java throws子句

Java知多少(51)finally

Java知多少(52)内置异常

Java知多少(53)使用Java创建自己的异常子类

Java知多少(54)断言详解

Java知多少(55)线程

Java知多少(56)线程模型

Java知多少(57)主线程

Java知多少(58)线程Runnable接口和Thread类详解

Java知多少(59)创建多线程

Java知多少(60)isAlive()和join()的使用

Java知多少(61)线程优先级

时间: 2024-10-17 14:10:17

Java知多少(62)线程同步的相关文章

java SE学习之线程同步(详细介绍)

       java程序中可以允许存在多个线程,但在处理多线程问题时,必须注意这样一个问题:               当两个或多个线程同时访问同一个变量,并且一些线程需要修改这个变量时,那么这个程序是该如何执行?              也就是先访问这个变量还是先修改这个变量.              在学习线程的这段时间里,我也一直被这个问题所困扰!但是今天终于算是搞明白了.              于是将这些好的列子一一列举出来,分享一下. (1)什么是线程同步 ?      

JAVA并发编程4_线程同步之volatile关键字

上一篇博客JAVA并发编程3_线程同步之synchronized关键字中讲解了JAVA中保证线程同步的关键字synchronized,其实JAVA里面还有个较弱的同步机制volatile.volatile关键字是JAVA中的轻量级的同步机制,用来将变量的更新操作同步到其他线程.从内存可见性的角度来说,写入volatile变量相当于退出同步代码块,读取volatile变量相当于进入同步代码块. 旧的内存模型:保证读写volatile都直接发生在main memory中. 在新的内存模型下(1.5)

Java多线程与并发——线程同步

1.多线程共享数据 在多线程的操作中,多个线程有可能同时处理同一个资源,这就是多线程中的共享数据. 2.线程同步 解决数据共享问题,必须使用同步,所谓同步就是指多个线程在同一时间段内只能有一个线程执行指定代码,其他线程要等待此线程完成之后才可以继续执行. 线程进行同步,有以下两种方法: (1)同步代码块 synchronized(要同步的对象){ 要同步的操作; } (2)同步方法 public synchronized void method(){ 要同步的操作; } (3)Lock 3.同步

JAVA并发编程3_线程同步之synchronized关键字

在上一篇博客里讲解了JAVA的线程的内存模型,见:JAVA并发编程2_线程安全&内存模型,接着上一篇提到的问题解决多线程共享资源的情况下的线程安全问题. 不安全线程分析 public class Test implements Runnable { private int i = 0; private int getNext() { return i++; } @Override public void run() { // synchronized while (true) { synchro

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

11.6-全栈Java笔记:什么是线程同步

 同步问题的提出 现实生活中,我们会遇到"同一个资源,多个人都想使用". 比如:教室里,只有一台电脑,多个人都想使用.天然的解决办法就是,在电脑旁边,大家排队.前人使用完后,后人再使用. 线程同步的概念 处理多线程问题时,多个线程同时访问同一个对象,并且一个线程还想修改这个对象. 这时候,我们就需要用到"线程同步". 线程同步其实就是一种等待机制,多个线程需要同时访问同一个对象,则线程进入这个对象的等待池(wait pool)形成队列,等待前面的线程使用完毕后,下一

I学霸官方免费教程四十 :Java基础教程之线程同步

线程的同步 指当多个线程使用同一对象中被同步的资源时,要根据"先来后到"的顺序使用.举个例子:现在只有一台电脑,现在有两个人A和B想玩游戏,一个人C想写代码,一个人D想听音乐.此时A.B.C三个人要抢这台电脑,谁先抢到谁用,用完了后面两个人在接着抢,谁抢到谁用.而D则不用,在另外三个人中任意一个人正在使用的时候,都可以播放音乐给他听:由此可以看出玩游戏和写代码的功能(方法)是要有"先来后到"的顺序的,而听音乐这个功能不需要.所以玩游戏和写代码的方法就是需要被同步的,

JAVA 并发编程-传统线程同步通信技术(四)

首先介绍几个概念: wait()方法 wait()方法使得当前线程必须要等待,等到另外一个线程调用notify()或者notifyAll()方法. 当前的线程必须拥有当前对象的monitor,也即lock,就是锁. 线程调用wait()方法,释放它对锁的拥有权,然后等待另外的线程来通知它(通知的方式是notify()或者notifyAll()方法),这样它才能重新获得锁的拥有权和恢复执行. 要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或s

11.7-全栈Java笔记:如何实现线程同步

由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问时的冲突问题. 由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized 方法和 synchronized 块:  synchronized方法 通过在方法声明中加入 synchronized

java中多线程的线程同步死锁问题

/* *定义一个多线程 */ package com.thread; public class TicketThread2 implements Runnable { //定义1000张票 public static int ticket = 100; Object obj = new Object(); // public boolean flag = false; // public boolean exit = false; @Override public void run() { //