在单线程程序中,每次只能做一件事情,后面的事情需要等待起那么的事情完成后才可以进行,但是如果使用多线程程序,就会发生两个线程抢占资源的问题,所有多线程编程中需要放在这些资源访问的冲突。
一、线程安全
在实际开发中,使用多线程程序的情况很多,比如火车站售票系统,在这个系统中判断票数是否大于0,如果大于0则执行将该票出售给乘客的功能,但当两个线程同时访问这段代码时(假如只剩下一张票),第一个线程将票售出,与此同时第二个线程也已经执行完成判断是否有票的操作,并得到票数大于0的结论,于是也执行了售出操作,这样就会产生负数。所有在编写多线程程序时,应该考虑到线程安全问题。
通过以下实例来进行说明:
package test;
public class ThreadSafeTest implements Runnable{
int num = 10;
public void run() {
while(true) {
if(num>0) {
try {
Thread.sleep(100);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println("tickets"+num--);
}
}
}
public static void main(String args[]) {
ThreadSafeTest t = new ThreadSafeTest();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
/*
输出结果如下:
tickets10
tickets9
tickets8
tickets7
tickets6
tickets5
tickets4
tickets3
tickets2
tickets1
tickets0
tickets-1
tickets-2
*/
从上述例子的输出结果可以看出,最后打印的为负值了,这样就模拟了售票系统会遇见的问题。
二、线程同步机制
基本上所有解决多线程冲突的问题都是采用给定时间只允许一个线程访问共享资源,这时就需要给共享资源上一道锁。
1、同步块
在Java中提供了同步机制,可以有效地防止资源冲突。同步机制使用synchronized关键字,语法如下:
synchronized(Object){
}
修改上一个例子:
package test;
public class ThreadSafeTest implements Runnable{
int num = 10;
public void run() {
while(true) {
synchronized ("") {
if(num>0) {
try {
Thread.sleep(100);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println("tickets"+num--);
}
}
}
}
public static void main(String args[]) {
ThreadSafeTest t = new ThreadSafeTest();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
/*输出结果如下:
tickets10
tickets9
tickets8
tickets7
tickets6
tickets5
tickets4
tickets3
tickets2
tickets1
*/
可以看出使用synchronized后,打印结果没有出现负数。
2、同步方法
同步方法就是再方法前面修饰synchronized关键字,语法如下:
synchronized void f(){
}
当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能被执行。必须将每个能访问共享资源的方法修饰为synchronized,否则就会报错。