工作需要,简单查看,很多东西日后钻研!先放在这
==================================================
synchronized 的语法:
synchronized 可以作为一个method的修饰符,也可以一段代码里出现,先说在代码里出:
其语法是
synchronized(xx) {
//your code
}
这个xx需要是一个Object,只要是一个Object就行,如:
String s="haha";
synchronized(s) {
//your code
}
不是Object不行,如:
int n=3;
synchronized(n) {
//your code
}
是不可以的,有autoboxing也不行。
如果你理解this的含义,那么
synchronized(this) {
//your code
}
也很好理解,它需要一个Object,而this是一个特殊的Object,当然可以这样用。
再说synchronized 修饰 method的情况,如果synchronized修饰一个非static的method,
如:
public synchronized void aMethod() {
//some code
}
相当于:
public void aMethod() {
synchronized(this) {
//some code
}
}
修饰一个 static的method,
如:
public static synchronized void aMethod() {
//some code
}
相当于:
public static synchronized void aMethod() {
synchronized(XX.class) {
//some code
}
}
XX是这个方法所在的类,XX.class 也是一个Object,类型是Class而已,在一个
ClassLoader里,它是唯一的,就是独一无二的object
总之synchronized的语法可以统一为: synchronized(a var) { do something }
synchronized 的语义:
这是我自己的理解,
synchronized(xx) {
//your code
}
的语义是,在xx这个Object的“授权”、“名义”、 “面子”下,执行 your code。要注
意的是,xx只能授权给一个人(线程),当xx授权给某个人执行后,就不能再授权给别人了
。 当那个人执行完那段代码后,xx才能继续授权给其它人执行,可以理解为,别人在xx的
授权下,执行完这段代码后,把这个权利又还给xx了。 当xx不能授权给一个人时,这个人
必须等在这里,知道xx可以授权给它。 (上面说的人都是线程)
synchronized 的作用:
synchronized是用在多线程环境中的,作用简单的说,就是不允许 “某些” 线程 同时执
行到一段代码里。 这个 “某些”线程 怎么界定? 是由那个xx object决定的,就是当两
个线程执行到 synchronized的时候,需要同一个Object授权时,这两个线程不能同时执行
到需要授权的代码。
极端情况是 系统你所有的线程都不能执行到这段代码里,那么你就选一个极端唯一的
object作为xx,一般选Class object,如:
synchronized(String.class) {
}
==================对synchronized(this)的一些理解======================
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
五、以上规则对其它对象锁同样适用.
举例说明:
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
package ths;
public class Thread1 implements Runnable {
public void run() {
synchronized(this) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
}
}
}
public static void main(String[] args) {
Thread1 t1 = new Thread1();
Thread ta = new Thread(t1, "A");
Thread tb = new Thread(t1, "B");
ta.start();
tb.start();
}
}
结果:
A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
package ths;
public class Thread2 {
public void m4t1() {
synchronized(this) {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
public void m4t2() {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
public static void main(String[] args) {
final Thread2 myt2 = new Thread2();
Thread t1 = new Thread(
new Runnable() {
public void run() {
myt2.m4t1();
}
}, "t1"
);
Thread t2 = new Thread(
new Runnable() {
public void run() {
myt2.m4t2();
}
}, "t2"
);
t1.start();
t2.start();
}
}
结果:
t1 : 4
t2 : 4
t1 : 3
t2 : 3
t1 : 2
t2 : 2
t1 : 1
t2 : 1
t1 : 0
t2 : 0
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
//修改Thread2.m4t2()方法:
public void m4t2() {
synchronized(this) {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
结果:
t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0
四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
//修改Thread2.m4t2()方法如下:
public synchronized void m4t2() {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
结果:
t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0
五、以上规则对其它对象锁同样适用:
package ths;
public class Thread3 {
class Inner {
private void m4t1() {
int i = 5;
while(i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : Inner.m4t1()=" + i);
try {
Thread.sleep(500);
} catch(InterruptedException ie) {
}
}
}
private void m4t2() {
int i = 5;
while(i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i);
try {
Thread.sleep(500);
} catch(InterruptedException ie) {
}
}
}
}
private void m4t1(Inner inner) {
synchronized(inner) { //使用对象锁
inner.m4t1();
}
}
private void m4t2(Inner inner) {
inner.m4t2();
}
public static void main(String[] args) {
final Thread3 myt3 = new Thread3();
final Inner inner = myt3.new Inner();
Thread t1 = new Thread(
new Runnable() {
public void run() {
myt3.m4t1(inner);
}
}, "t1"
);
Thread t2 = new Thread(
new Runnable() {
public void run() {
myt3.m4t2(inner);
}
}, "t2"
);
t1.start();
t2.start();
}
}
结果:
尽管线程t1获得了对Inner的对象锁,但由于线程t2访问的是同一个Inner中的非同步部分。所以两个线程互不干扰。
t1 : Inner.m4t1()=4
t2 : Inner.m4t2()=4
t1 : Inner.m4t1()=3
t2 : Inner.m4t2()=3
t1 : Inner.m4t1()=2
t2 : Inner.m4t2()=2
t1 : Inner.m4t1()=1
t2 : Inner.m4t2()=1
t1 : Inner.m4t1()=0
t2 : Inner.m4t2()=0
现在在Inner.m4t2()前面加上synchronized:
private synchronized void m4t2() {
int i = 5;
while(i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i);
try {
Thread.sleep(500);
} catch(InterruptedException ie) {
}
}
}
结果:
尽管线程t1与t2访问了同一个Inner对象中两个毫不相关的部分,但因为t1先获得了对Inner的对象锁,所以t2对Inner.m4t2()的访问也被阻塞,因为m4t2()是Inner中的一个同步方法。
t1 : Inner.m4t1()=4
t1 : Inner.m4t1()=3
t1 : Inner.m4t1()=2
t1 : Inner.m4t1()=1
t1 : Inner.m4t1()=0
t2 : Inner.m4t2()=4
t2 : Inner.m4t2()=3
t2 : Inner.m4t2()=2
t2 : Inner.m4t2()=1
t2 : Inner.m4t2()=0
==============
关于java中的synchronized关键字的理解==============
搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序
●首先是synchronized关键字的作用域可以有两种:
・某个对象实例内
synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线 程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)
・某个类的范围
synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法
●synchronized关键字是不能继承的
●要注意的事项
・无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问
・每个对象只有一个锁(lock)与之相关联
・实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制
●对共享资源的同步访问更加安全的技巧
・定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。
・如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。 这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了。
=====================
Java线程及同步(synchronized)样例代码=======================
import java.io.*;
import java.util.*;
import java.text.SimpleDateFormat;
public class TestThread extends Thread
{
private static Integer threadCounterLock; //用于同步,防止数据被写乱
private static int threadCount; //本类的线程计数器
static
{
threadCount = 0;
threadCounterLock = new Integer(0);
}
public TestThread()
{
super();
}
public synchronized static void incThreadCount()
{
threadCount++;
System.out.println("thread count after enter: " + threadCount);
}
public synchronized static void decThreadCount()
{
threadCount--;
System.out.println("thread count after leave: " + threadCount);
}
public void run()
{
synchronized(threadCounterLock) //同步
{
threadCount++;
System.out.println("thread count after enter: " + threadCount);
}
//incThreadCount(); //和上面的语句块是等价的
final long nSleepMilliSecs = 1000; //循环中的休眠时间
long nCurrentTime = System.currentTimeMillis();
long nEndTime = nCurrentTime + 30000; //运行截止时间
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try
{
while (nCurrentTime < nEndTime)
{
nCurrentTime = System.currentTimeMillis();
System.out.println("Thread " + this.hashCode() + ", current time: " + simpleDateFormat.format(new Date(nCurrentTime)));
try
{
sleep(nSleepMilliSecs);
}
catch(InterruptedException ex)
{
ex.printStackTrace();
}
}
}
finally
{
synchronized(threadCounterLock) //同步
{
threadCount--;
System.out.println("thread count after leave: " + threadCount);
}
//decThreadCount(); //和上面的语句块是等价的
}
}
public static void main(String[] args)
{
TestThread[] testThread = new TestThread[2];
for (int i=0; i<testThread.length; i++)
{
testThread[i] = new TestThread();
testThread[i].start();
}
}
}
同步就是简单的说我用的时候你不能用,大家用的要是一样的就这样!
比如说有只苹果很好吃,我拉起来咬一口,放下,你再拉起咬一口,这就同步了,要是大家一起咬,呵呵,那就结婚吧,婚礼上常能看到这个,也不怕咬着嘴,嘻嘻嘻!
举个例子,现在有个类,类中有一个私有成员一个苹果,两个方法一个看,一个吃。
现在不同步,我一“看”,哈哈一个苹果,我“吃”四分之一了
你一“看”,哈哈一个苹果,也“吃”四分之一了。
可能的情况就是都是看到一个苹果但我的吃方法用在你的之前,所以可能你只能吃到3/4的1/4也就是3/16个而不是1/4个苹果了。
现在加入同步锁,我在吃的时候你看被锁,等吃完了,你再看,啊3/4个苹果,吃1/3好了了,就这样!
===========================synchronized的一个简单例子================================
public class TextThread
{
/**
* @param args
*/
public static void main(String[] args)
{
// TODO 自动生成方法存根
TxtThread tt = new TxtThread();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
}
}
class TxtThread implements Runnable
{
int num = 100;
String str = new String();
public void run()
{
while (true)
{
synchronized(str)
{
if (num>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
e.getMessage();
}
System.out.println(Thread.currentThread().getName()+ "this is "+ num--);
}
}
}
}
}
上面的例子中为了制造一个时间差,也就是出错的机会,使用了Thread.sleep(10)
Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。到底如何?――还得对synchronized关键字的作用进行深入了解才可定论。
总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
在进一步阐述之前,我们需要明确几点:
A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。
B.每个对象只有一个锁(lock)与之相关联。
C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
接着来讨论synchronized用到不同地方对代码产生的影响:
假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。
1. 把synchronized当作函数修饰符时,示例代码如下:
Public synchronized void methodAAA()
{
//….
}
这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。
上边的示例代码等同于如下代码:
public void methodAAA()
{
synchronized (this) // (1)
{
//…..
}
}
(1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(
2.同步块,示例代码如下:
public void method3(SomeObject so)
{
synchronized(so)
{
//…..
}
}
这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:
class Foo implements Runnable
{
private byte[] lock = new byte[0]; // 特殊的instance变量
Public void methodA()
{
synchronized(lock) { //… }
}
//…..
}
注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
3.将synchronized作用于static 函数,示例代码如下:
Class Foo
{
public synchronized static void methodAAA() // 同步的static 函数
{
//….
}
public void methodBBB()
{
synchronized(Foo.class) // class literal(类名称字面常量)
}
}
代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。
记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。
可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。
小结如下:
搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。
还有一些技巧可以让我们对共享资源的同步访问更加安全:
1. 定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。
2. 如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了。
==================
搞懂java中的synchronized关键字====================
2006年12月27日 上午 10:00 | 作者:littlebat
实际上,我关于java的基础知识的90%以上都来自Thinking in Java。对于其中的synchronized关键字,当时就是浏览一下,大概知道意思,也没有细看。后来一直没有用过这个关键字。昨天看Thinking in Patterns with Java中的Observer模式,看了其中的Observable类的源码,发现里面几乎所有的方法都用了synchronized关键字(不是全部),其中个别用了synchronized(this){}的区块。于是,我发觉有必要好好理解一下这个关键字了。
我再次看了侯捷译的Thinking in Java第二版中文版,得到有关synchronized的如下信息:
1、synchronized关键字的作用域有二种:
1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象;
3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
4、疑问:
我知道了有static区块(其中的加载时机好像也不清楚,需要学习一下,原来看Thinking in Java好像是说: static区块加载的时机是类的一个对象实例创建或类的一个static方法被访问,但是我今天早上看了javaeye.com上面的一个关于“<static块到底什么时候执行?-eway -JavaEye技术社区>”的文章后又有点迷糊了:),也许,需要认真学习一下JVM的内在机制才是理解这类问题最根本的途径),也有synchronized区块,那么,有synchronized static 区块吗?意思是问:有没有一个类作用域内的synchronized区块?
==============ThreadLocal与synchronized ====================
Java良好的支持多线程。使用java,我们可以很轻松的编程一个多线程程序。但是使用多线程可能会引起并发访问的问题。synchronized和ThreadLocal都是用来解决多线程并发访问的问题。大家可能对synchronized较为熟悉,而对ThreadLocal就要陌生得多了。
并发问题。当一个对象被两个线程同时访问时,可能有一个线程会得到不可预期的结果。
一个简单的java类Studnet
代码
- public class Student {
- private int age=0;
-
- public int getAge() {
- return this.age;
-
- }
-
- public void setAge(int age) {
- this.age = age;
- }
- }
一个多线程类ThreadDemo.
这个类有一个Student的私有变量,在run方法中,它随机产生一个整数。然后设置到student变量中,从student中读取设置后的值。然后睡眠5秒钟,最后再次读student的age值。
代码
- public class ThreadDemo implements Runnable{
- Student student = new Student();
- public static void main(String[] agrs) {
- ThreadDemo td = new ThreadDemo();
- Thread t1 = new Thread(td,"a");
- Thread t2 = new Thread(td,"b");
- t1.start();
- t2.start();
-
- }
-
-
-
- public void run() {
- accessStudent();
- }
-
- public void accessStudent() {
- String currentThreadName = Thread.currentThread().getName();
- System.out.println(currentThreadName+" is running!");
-
- Random random = new Random();
- int age = random.nextInt(100);
- System.out.println("thread "+currentThreadName +" set age to:"+age);
-
- this.student.setAge(age);
- System.out.println("thread "+currentThreadName+" first read age is:"+this.student.getAge());
- try {
- Thread.sleep(5000);
- }
- catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());
-
- }
-
- }
运行这个程序,屏幕输出如下:
a is running!
b is running!
thread b set age to:33
thread b first read age is:33
thread a set age to:81
thread a first read age is:81
thread b second read age is:81
thread a second read age is:81
需要注意的是,线程a在同一个方法中,第一次读取student的age值与第二次读取值不一致。这就是出现了并发问题。
synchronized
上面的例子,我们模似了一个并发问题。Java提供了同步机制来解决并发问题。synchonzied关键字可以用来同步变量,方法,甚至同步一个代码块。
使用了同步后,一个线程正在访问同步对象时,另外一个线程必须等待。
Synchronized同步方法
现在我们可以对accessStudent方法实施同步。
public synchronized void accessStudent()
再次运行程序,屏幕输出如下:
a is running!
thread a set age to:49
thread a first read age is:49
thread a second read age is:49
b is running!
thread b set age to:17
thread b first read age is:17
thread b second read age is:17
加上了同步后,线程b必须等待线程a执行完毕后,线程b才开始执行。
对方法进行同步的代价是非常昂贵的。特别是当被同步的方法执行一个冗长的操作。这个方法执行会花费很长的时间,对这样的方法进行同步可能会使系统性能成数量级的下降。
Synchronized同步块
在accessStudent方法中,我们真实需要保护的是student变量,所以我们可以进行一个更细粒度的加锁。我们仅仅对student相关的代码块进行同步。
代码
- synchronized(this) {
- Random random = new Random();
- int age = random.nextInt(100);
- System.out.println("thread "+currentThreadName +" set age to:"+age);
-
- this.student.setAge(age);
-
- System.out.println("thread "+currentThreadName+" first read age is:"+this.student.getAge());
- try {
- Thread.sleep(5000);
- }
- catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- }
运行方法后,屏幕输出:
a is running!
thread a set age to:18
thread a first read age is:18
b is running!
thread a second read age is:18
thread b set age to:62
thread b first read age is:62
thread b second read age is:62
需要特别注意这个输出结果。
这个执行过程比上面的方法同步要快得多了。
只有对student进行访问的代码是同步的,而其它与部份代码却是异步的了。而student的值并没有被错误的修改。如果是在一个真实的系统中,accessStudent方法的操作又比较耗时的情况下。使用同步的速度几乎与没有同步一样快。
使用同步锁
稍微把上面的例子改一下,在ThreadDemo中有一个私有变量count,。
private int count=0;
在accessStudent()中, 线程每访问一次,count都自加一次, 用来记数线程访问的次数。
代码
- try {
- this.count++;
- Thread.sleep(5000);
- }catch(InterruptedException ex) {
- ex.printStackTrace();
- }
为了模拟线程,所以让它每次自加后都睡眠5秒。
accessStuden()方法的完整代码如下:
代码
- String currentThreadName = Thread.currentThread().getName();
- System.out.println(currentThreadName+" is running!");
- try {
- this.count++;
- Thread.sleep(5000);
- }catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- System.out.println("thread "+currentThreadName+" read count:"+this.count);
-
-
- synchronized(this) {
- Random random = new Random();
- int age = random.nextInt(100);
- System.out.println("thread "+currentThreadName +" set age to:"+age);
-
- this.student.setAge(age);
-
- System.out.println("thread "+currentThreadName+" first read age is:"+this.student.getAge());
- try {
- Thread.sleep(5000);
- }
- catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- }
- System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());
运行程序后,屏幕输出:
a is running!
b is running!
thread a read count:2
thread a set age to:49
thread a first read age is:49
thread b read count:2
thread a second read age is:49
thread b set age to:7
thread b first read age is:7
thread b second read age is:7
我们仍然对student对象以synchronized(this)操作进行同步。
我们需要在两个线程中共享count失败。
所以仍然需要对count的访问进行同步操作。
代码
- synchronized(this) {
- try {
- this.count++;
- Thread.sleep(5000);
- }catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- }
- System.out.println("thread "+currentThreadName+" read count:"+this.count);
-
-
- synchronized(this) {
- Random random = new Random();
- int age = random.nextInt(100);
- System.out.println("thread "+currentThreadName +" set age to:"+age);
-
- this.student.setAge(age);
-
- System.out.println("thread "+currentThreadName+" first read age is:"+this.student.getAge());
- try {
- Thread.sleep(5000);
- }
- catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- }
- System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());
- long endTime = System.currentTimeMillis();
- long spendTime = endTime - startTime;
- System.out.println("花费时间:"+spendTime +"毫秒");
程序运行后,屏幕输出
a is running!
b is running!
thread a read count:1
thread a set age to:97
thread a first read age is:97
thread a second read age is:97
花费时间:10015毫秒
thread b read count:2
thread b set age to:47
thread b first read age is:47
thread b second read age is:47
花费时间:20124毫秒
我们在同一个方法中,多次使用synchronized(this)进行加锁。有可能会导致太多额外的等待。
应该使用不同的对象锁进行同步。
设置两个锁对象,分别用于student和count的访问加锁。
代码
- private Object studentLock = new Object();
- private Object countLock = new Object();
-
- accessStudent()方法如下:
- long startTime = System.currentTimeMillis();
- String currentThreadName = Thread.currentThread().getName();
- System.out.println(currentThreadName+" is running!");
-
-
- synchronized(countLock) {
- try {
- this.count++;
- Thread.sleep(5000);
- }catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- }
- System.out.println("thread "+currentThreadName+" read count:"+this.count);
-
-
- synchronized(studentLock) {
- Random random = new Random();
- int age = random.nextInt(100);
- System.out.println("thread "+currentThreadName +" set age to:"+age);
-
- this.student.setAge(age);
-
- System.out.println("thread "+currentThreadName+" first read age is:"+this.student.getAge());
- try {
- Thread.sleep(5000);
- }
- catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- }
- System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());
- long endTime = System.currentTimeMillis();
- long spendTime = endTime - startTime;
- System.out.println("花费时间:"+spendTime +"毫秒");
这样对count和student加上了两把不同的锁。
运行程序后,屏幕输出:
a is running!
b is running!
thread a read count:1
thread a set age to:48
thread a first read age is:48
thread a second read age is:48
花费时间:10016毫秒
thread b read count:2
thread b set age to:68
thread b first read age is:68
thread b second read age is:68
花费时间:20046毫秒
与两次使用synchronized(this)相比,使用不同的对象锁,在性能上可以得到更大的提升。
由此可见synchronized是实现java的同步机制。同步机制是为了实现同步多线程对相同资源的并发访问控制。保证多线程之间的通信。
可见,同步的主要目的是保证多线程间的数据共享。同步会带来巨大的性能开销,所以同步操作应该是细粒度的。如果同步使用得当,带来的性能开销是微不足道的。使用同步真正的风险是复杂性和可能破坏资源安全,而不是性能。
ThreadLocal
由上面可以知道,使用同步是非常复杂的。并且同步会带来性能的降低。Java提供了另外的一种方式,通过ThreadLocal可以很容易的编写多线程程序。从字面上理解,很容易会把ThreadLocal误解为一个线程的本地变量。其它ThreadLocal并不是代表当前线程,ThreadLocal其实是采用哈希表的方式来为每个线程都提供一个变量的副本。从而保证各个线程间数据安全。每个线程的数据不会被另外线程访问和破坏。
我们把第一个例子用ThreadLocal来实现,但是我们需要些许改变。
Student并不是一个私有变量了,而是需要封装在一个ThreadLocal对象中去。调用ThreadLocal的set方法,ThreadLocal会为每一个线程都保持一份Student变量的副本。所以对student的读取操作都是通过ThreadLocal来进行的。
代码
- protected Student getStudent() {
- Student student = (Student)studentLocal.get();
- if(student == null) {
- student = new Student();
- studentLocal.set(student);
- }
- return student;
- }
-
- protected void setStudent(Student student) {
- studentLocal.set(student);
- }
accessStudent()方法需要做一些改变。通过调用getStudent()方法来获得当前线程的Student变量,如果当前线程不存在一个Student变量,getStudent方法会创建一个新的Student变量,并设置在当前线程中。
Student student = getStudent();
student.setAge(age);
accessStudent()方法中无需要任何同步代码。
完整的代码清单如下:
TreadLocalDemo.java
代码
- public class TreadLocalDemo implements Runnable {
- private final static ThreadLocal studentLocal = new ThreadLocal();
-
- public static void main(String[] agrs) {
- TreadLocalDemo td = new TreadLocalDemo();
- Thread t1 = new Thread(td,"a");
- Thread t2 = new Thread(td,"b");
-
- t1.start();
- t2.start();
-
-
-
- }
-
-
-
-
- public void run() {
- accessStudent();
- }
-
- public void accessStudent() {
-
- String currentThreadName = Thread.currentThread().getName();
- System.out.println(currentThreadName+" is running!");
- Random random = new Random();
- int age = random.nextInt(100);
- System.out.println("thread "+currentThreadName +" set age to:"+age);
- Student student = getStudent();
- student.setAge(age);
- System.out.println("thread "+currentThreadName+" first read age is:"+student.getAge());
- try {
- Thread.sleep(5000);
- }
- catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- System.out.println("thread "+currentThreadName +" second read age is:"+student.getAge());
-
- }
-
- protected Student getStudent() {
- Student student = (Student)studentLocal.get();
- if(student == null) {
- student = new Student();
- studentLocal.set(student);
- }
- return student;
- }
-
- protected void setStudent(Student student) {
- studentLocal.set(student);
- }
- }
运行程序后,屏幕输出:
b is running!
thread b set age to:0
thread b first read age is:0
a is running!
thread a set age to:17
thread a first read age is:17
thread b second read age is:0
thread a second read age is:17
可见,使用ThreadLocal后,我们不需要任何同步代码,却能够保证我们线程间数据的安全。
而且,ThreadLocal的使用也非常的简单。
我们仅仅需要使用它提供的两个方法
void set(Object obj) 设置当前线程的变量的副本的值。
Object get() 返回当前线程的变量副本
另外ThreadLocal还有一个protected的initialValue()方法。返回变量副本在当前线程的初始值。默认为null
ThreadLocal是怎么做到为每个线程都维护一个变量的副本的呢?
我们可以猜测到ThreadLocal的一个简单实现
代码
- public class ThreadLocal
- {
- private Map values = Collections.synchronizedMap(new HashMap());
- public Object get()
- {
- Thread curThread = Thread.currentThread();
- Object o = values.get(curThread);
- if (o == null && !values.containsKey(curThread))
- {
- o = initialValue();
- values.put(curThread, o);
- }
- return o;
- }
-
- public void set(Object newValue)
- {
- values.put(Thread.currentThread(), newValue);
- }
-
- public Object initialValue()
- {
- return null;
- }
- }
由此可见,ThreadLocal通过一个Map来为每个线程都持有一个变量副本。这个map以当前线程为key。与synchronized相比,ThreadLocal是以空间换时间的策略来实现多线程程序。
Synchronized还是ThreadLocal?
ThreadLocal以空间换取时间,提供了一种非常简便的多线程实现方式。因为多个线程并发访问无需进行等待,所以使用ThreadLocal会获得更大的性能。虽然使用ThreadLocal会带来更多的内存开销,但这点开销是微不足道的。因为保存在ThreadLocal中的对象,通常都是比较小的对象。另外使用ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。
ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。
=================Java 多线程程序设计要点(synchronized..) =====================
多线程程序设计要点:
1.多线程中有主内存和工作内存之分, 在JVM中,有一个主内存,专门负责所有线程共享数据;而每个线程都有他自己私有的工作内存, 主内存和工作内存分贝在JVM的stack区和heap区。
2.线程的状态有'Ready', 'Running', 'Sleeping', 'Blocked', 和 'Waiting'几个状态,
'Ready' 表示线程正在等待CPU分配允许运行的时间。
3.线程运行次序并不是按照我们创建他们时的顺序来运行的,CPU处理线程的顺序是不确定的,如果需要确定,那么必须手工介入,使用setPriority()方法设置优先级。
4.我们无从知道一个线程什么时候运行,两个或多个线程在访问同一个资源时,需要synchronized
5. 每个线程会注册自己,实际某处存在着对它的引用,因此,垃圾回收机制对它就“束手无策”了。
6. Daemon线程区别一般线程之处是:主程序一旦结束,Daemon线程就会结束。
7. 一个对象中的所有synchronized方法都共享一把锁,这把锁能够防止多个方法对通用内存同时进行的写操作。synchronized static方法可在一个类范围内被相互间锁定起来。
8. 对于访问某个关键共享资源的所有方法,都必须把它们设为synchronized,否则就不能正常工作。
9. 假设已知一个方法不会造成冲突,最明智的方法是不要使用synchronized,能提高些性能。
10. 如果一个\"同步"方法修改了一个变量,而我们的方法要用到这个变量(可能是只读),最好将自己的这个方法也设为 synchronized。
11. synchronized不能继承, 父类的方法是synchronized,那么其子类重载方法中就不会继承“同步”。
12. 线程堵塞Blocked有几个原因造成:
(1)线程在等候一些IO操作
(2)线程试图调用另外一个对象的“同步”方法,但那个对象处于锁定状态,暂时无法使用。
13.原子型操作(atomic), 对原始型变量(primitive)的操作是原子型的atomic. 意味着这些操作是线程安全的, 但是大部分情况下,我们并不能正确使用,来看看 i = i + 1 , i是int型,属于原始型变量:
(1)从主内存中读取i值到本地内存.
(2)将值从本地内存装载到线程工作拷贝中.
(3)装载变量1.
(4)将i 加 1.
(5)将结果给变量i.
(6)将i保存到线程本地工作拷贝中.
(7)写回主内存.
注意原子型操作只限于第1步到第2步的读取以及第6到第7步的写, i的值还是可能被同时执行i=i+1的多线程中断打扰(在第4步)。
double 和long 变量是非原子型的(non-atomic)。数组是object 非原子型。
14. 由于13条的原因,我们解决办法是:
class xxx extends Thread{
//i会被经常修改
private int i;
public synchronized int read(){ return i;}
public synchronized void update(){ i = i + 1;}
..........
}
15. Volatile变量, volatile变量表示保证它必须是与主内存保持一致,它实际是"变量的同步", 也就是说对于volatile变量的操作是原子型的,如用在long 或 double变量前。
16. 使用yield()会自动放弃CPU,有时比sleep更能提升性能。
17. sleep()和wait()的区别是:wait()方法被调用时会解除锁定,但是我们能使用它的地方只是在一个同步的方法或代码块内。
18. 通过制造缩小同步范围,尽可能的实现代码块同步,wait(毫秒数)可在指定的毫秒数可退出wait;对于wait()需要被notisfy()或notifyAll()踢醒。
19. 构造两个线程之间实时通信的方法分几步:
(1). 创建一个PipedWriter和一个PipedReader和它们之间的管道;
PipedReader in = new PipedReader(new PipedWriter())
(2). 在需要发送信息的线程开始之前,将外部的PipedWriter导向给其内部的Writer实例out
(3). 在需要接受信息的线程开始之前,将外部的PipedReader导向给其内部的Reader实例in
(4). 这样放入out的所有东西度可从in中提取出来。
20. synchronized带来的问题除性能有所下降外,最大的缺点是会带来死锁DeadLock,只有通过谨慎设计来防止死锁,其他毫无办法,这也是线程难以驯服的一个原因。不要再使用stop() suspend() resume()和destory()方法
21. 在大量线程被堵塞时,最高优先级的线程先运行。但是不表示低级别线程不会运行,运行概率小而已。
22. 线程组的主要优点是:使用单个命令可完成对整个线程组的操作。很少需要用到线程组。
23. 从以下几个方面提升多线程的性能:
检查所有可能Block的地方,尽可能的多的使用sleep或yield()以及wait();
尽可能延长sleep(毫秒数)的时间;
运行的线程不用超过100个,不能太多;
不同平台linux或windows以及不同JVM运行性能差别很大。
========================================================================
更多文章:
http://scholar.ilib.cn/A-xdjsj-xby200301007.html
posted on 2007-10-16 09:57
lk 阅读(776)
评论(0) 编辑 收藏 所属分类:
j2se