第一:同步
概念:当两个或两个以上线程同时访问共享资源是,他们需要以某种方式保证,每次只能有一个线程使用资源,实现这一目的的过程即是同步的过程(同步)。下面以一个例子来说明同步,请看。
实验目的:
理解Java多线程机制
- SomeBody 类
/**
* @class SomeBody
* @description ...SomeBody有一个sayhello()方法,用于Say Hello
* @date 2016年9月20日 下午10:50:07
*/
public class SomeBody {
private String tag="";
private static String tag_static="";
public void sayhello(String greeting) throws InterruptedException {
String start=greeting;
tag=greeting;
tag_static=greeting;
System.out.print("[ Hello " + greeting);
// 休眠1秒,可以当做程序正在处理其他事情
Thread.sleep(1000);
System.out.println("]");
String end=greeting;
System.out.println("Strat==end?:"+start.equals(end)+"\tTag==start==end?"+start.equals(tag)+"\tgat_static==end==start?"+start.equals(tag_static));
}
}
- SynTest 类
/**
* @class SynTest
* @description ...
* @date 2016年9月20日 下午10:53:40
*/
public class SynTest {
public static void main(String[] args) throws InterruptedException {
SomeBody son=new SomeBody();
Thread thr = new Thread(new RunImpl(son, "Java"));
Thread thr_ = new Thread(new RunImpl(son, "Python"));
// 主线程等待子线程结束才能结束
thr.join();
thr_.join();
}
}
/**
* @class RunImpl
* @description ... 实现Runnable接口,开辟一条线程
* @date 2016年9月20日 下午11:04:49
*/
class RunImpl implements Runnable{
Thread t;
SomeBody somebody;
String msg;
public RunImpl(SomeBody somebody,String greeting) {
this.somebody=somebody;
msg=greeting;
t=new Thread(this);
t.start();
}
@Override
public void run() {
try {
somebody.sayhello(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 输出结果
[ Hello Java[ Hello Python]
Strat==end?:true Tag==start==end?true gat_static==end==start?true
]
Strat==end?:true Tag==start==end?false gat_static==end==start?false
实验分析(实验结论)
1 多个线程可以同时访问某一个类的实例方法
2 程序每次运行的结果可能不尽相同,但是Strat==end?:true
确是始终一致,证明尽管多个线程可以访问同一实例方法,但是局部变量却始终与调用线程保持一致,即局部变量在多线程中是线程安全的
(不会因为同时有多个线程访问而影响其他线程调用方法的局部变量的值(各线程正确持有各自的变量没有相互干扰),即虽然可以同时执行实例方法,但是局部变量之间之间相互不影响)。
3 我们可能注意到了gat_static==end==start?false、Tag==start==end?false
这样的输出,产生这样输出的原因是在多线程访问中,static变量静态变量
、类变量并不能与调用线程时刻保持一致,在greeting=Python时却变成了greeting=Java,谁后执行就变成了谁(线程先后执行而不是程序代码写的先后,由调度算法决定),该实验证明了,静态变量(static)变量和类变量在多线程中非线程安全
,非线程安全即是线程之间会相互影响彼此的结果,并不像局部变量一样,各自持有各自的实例,互不影响,究其原因是因为静态变量只有一个实例,在内存中只存在这一个对象,各个线程之间共享,而类变量的线程安全问题,需要分情况讨论,如果每条线程持有的是不同的实例,则线程安全
,如果像上面程序那样,两条线程共同只有同一个son
实例,则类变量即是非线程安全
4 同时我们也看到多线程影响了程序期望的输出结果,原本我们是期望输出类似于[Hello Python] Same?true ..之类的结果,但是多个线程同时执行,影响了结果的输出。
通过实验明确需要解决的问题
1 如何确保在多线程程序中,某一个资源的(方法,变量等)每次只能被一个线程访问,而不受其他线程干扰呢?
2 如何保证静态变量的线程安全,使之能够被线程之间共享呢?首先来谈第二个问题,静态变量(Static变量)在多线程中是
非线程安全的
在程序中应该避免
或者谨慎
使用,使用可能会在以后的博文中讲到;第一个问题,如何在多线程中实现同步,实现某一资源同时只能被一条线程访问,访问结束后,另外的线程才能访问呢?请继续看。
Synchronized语句
使用
synchronized
语句修饰方法,对方法进行同步,将以上SomeBody 类改写如下
public class SomeBody {
private String tag="";
private static String tag_static="";
//添加synchronized修饰
public synchronized void sayhello(String greeting) throws InterruptedException {
String start=greeting;
tag=greeting;
tag_static=greeting;
System.out.print("[ Hello " + greeting);
// 休眠1秒,可以当做程序正在处理其他事情
Thread.sleep(1000);
System.out.println("]");
String end=greeting;
System.out.println("Strat==end?:"+start.equals(end)+"\tTag==start==end?"+start.equals(tag)+"\tgat_static==end==start?"+start.equals(tag_static));
}
}
- 再次运行程序结果如下:
[ Hello Java]
Strat==end?:true Tag==start==end?true gat_static==end==start?true
[ Hello Python]
Strat==end?:true Tag==start==end?true gat_static==end==start?true
结果分析
从输出的结果看,在使用
synchronized
关键字修饰了方法之后,线程同步了,只有等一条线程调用完毕sayhello方法之后,另一线程才能进入。非常重要:使用同步方法看似行之有效,但有一点,若果一条线程进入了实例的同步方法
之后,其他线程将不能访问任何该实例的同步方法,同步方法会锁住该实例的所有同步方法。而解决的办法即是使用synchronized 代码块
锁住对象的引用即可。
public void sayhello(String greeting) throws InterruptedException {
synchronized (this) {
String start = greeting;
tag = greeting;
tag_static = greeting;
System.out.print("[ Hello " + greeting);
// 休眠1秒,可以当做程序正在处理其他事情
Thread.sleep(1000);
System.out.println("]");
String end = greeting;
System.out.println("Strat==end?:" + start.equals(end)
+ "\tTag==start==end?" + start.equals(tag)
+ "\tgat_static==end==start?" + start.equals(tag_static));
}
}
*结论
通过以上例子说明了Java的同步机制和synchronized的使用,也通过例子简单的介绍了一下
线程安全
和非线程安全
。