JAVA 多线程实现(二)

2014-11-24 03:24:28 · 作者: · 浏览: 2
情况下,被阻塞的线程也会让出CPU资源,相当于调用yield()

调用yield()方法在某些操作系统上会引起线程上下文的切换,因此在实际使用中,在一秒钟之内不要调用
yield()超过5次以上。

控制并发
volatile关键字的意义--很多资深程序员都搞不清楚volatile关键字的意义
volatile用来控制基本变量
实际上在多线程环境下,每个线程的方法内部都会保存对实例变量的一个拷贝,比如

public class MyClass implements Runnable{

int value = 10;

public void run(){
while(value < 20){
// do something
}
}

public void changeAnotherValue(){
value = 50;
}

public static void main(String[] args){
//...
}

}
上面代码中的value就是来自类实例变量
当不使用volatile关键字的时候,有可能造成在run方法的外部改变了value的值的时候
run方法内部无法注意到这个值的改变

比如调用changeAnotherValue的时候,run根本不会注意到value值已经变了,因为run会在
自己方法的内部复制一个我们看不到的私有变量来保存value的值,使用这个值进行循环。

为了避免这个情况的发生,需要使用volatile来关键字来修饰变量,强制所有的方法来共享成员变量
的内存地址,不允许使用copy。

volatile int value = 10;

如果编程的时候没有写volatile关键字而程序实际上要运行在多线程的环境下,那么
可以使用
java -Djava.compiler=NONE YourClassName
来完成这一功能,改参数是关闭JIT的。 JIT(Just In Time)
JIT关闭之后,强制要求读取变量的共享内存。
这样所有的成员变量都将被认为是volatile的

实际上,没当一个线程进入,或者离开一个同步块的时候,它都要将自己的私有拷贝同实际
的成员变量的值进行一次同步。同步块遍布java.*类,所以开发人员根本不知道虚拟器其实
在悄悄做着这个同步。举个例子来说
System.out.println()方法内部就包含一个同步块,因此,使用此方法来打印一个volatile变量
那么这个变量会立即同实际引用同步,那么volatile关键字在这里也就失去了效用。

使用volatile关键字虚拟机会强制要求每个线程都去访问这个变量共享的内存空间,而不是在线程
内部保留一个私有的拷贝,这样做实际上是不效率的。一般成员变量,如果不是经常要做赋值操作的
不建议过多的使用volatile关键字。

使用synchronized关键字来修饰一个同步方法,这样该方法每次只能被一个线程所访问
package cn.bj.brook.test;

public class MainRun {

/**
* @param args
*/
int i = 0;
synchronized public void countI(){
i=i+1;
System.out.println("i="+i);
}
public static void main(String[] args) {
MainRun mr = new MainRun();
T1 t1 = new T1(mr);
T2 t2 = new T2(mr);
Thread th1 = new Thread(t1);
Thread th2 = new Thread(t2);
th1.start();
th2.start();
}

}

class T1 implements Runnable{

MainRun mr = null;
T1(MainRun mr){
this.mr = mr;
}
@Override
public void run() {
while(true){
mr.countI();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

class T2 implements Runnable{
MainRun mr = null;
T2(MainRun mr){
this.mr = mr;
}
@Override
public void run() {
while(true){
mr.countI();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
如果那个sychronized关键字不加,则会出现重复的变量值
实际上当两个以上不同线程进入同一个方法体内部的时候,这些线程都会复制方法的变量值到自己的
私有变量的拷贝中去。

为了防止脏读的发生,对于多线程的访问环境,应该同时对写和读的方法都加上synchronized关键字
修饰。synchronized关键字会为该方法在对象级别上上锁,即在同一时刻,某线程只能获取一把这样
的对象锁,来操作上锁的对象的方法。
使用sychronized关键字会阻塞线程的访问,是耗费系统资源的。不会造成脏读脏写的操作,不需要是用
synchronized关键字。

在实例方法前加synchronized关键字是在对象层级上的锁,应对某些静态方法,将synchronized关键字
加在static关键字前,来修饰静态方法,那么这个锁就是类级别的锁。使用类级别的锁也同样能够防止多个
线程在某一时刻进入同一个方法。

实际上,我们只要认为synchronized关键字就像一把钥匙,它所修饰的类或者对象提供锁,线程持有钥匙
才能访问相应级别的锁,否则就不能访问。而且,同一时刻,钥匙只能在一个人的手里。

实际上可以将synchronized关键字作为块修饰符来使用
synchronized(控制对象){
//块代码
//这部分代码一次只能被一个线程所访问
}

通过调整控制对象,来确定是对象级别的锁还是类级别的锁

package cn.bj.brook.test;

public class MainRun {

/**
* @param args
*/
int i = 0;
public void countI(){
synchronized(this){
i=i+1;
System.out.println("i="+i);
}
}
public stati