上一篇对多线程的基本用法进行了简单的小结,这一篇着重说一下多线程同步问题
使用多线程,由于多线程并发执行的特点,所以大大的提高了程序的运行效率,但是,当多个线程去访问同一个资源时,也会出现一些安全问题
就拿我们之前所说过的售票案例。
/**
* 模拟窗口售票
*
* @author Shawn·Zhang
*/
public class Example01 {
public static void main(String[] args) {
TicketWindow tw = new TicketWindow();
new Thread(tw, "售票口一").start();
new Thread(tw, "售票口二").start();
new Thread(tw, "售票口三").start();
new Thread(tw, "售票口四").start();
}
}
class TicketWindow implements Runnable {
private int tickets = 10;
public void run() {
while (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在发售第"
+ tickets-- + "张票");
}
}
}
测试结果:
在售票程序while循环中添加了sleep()方法,,这样也就是模拟了售票过程中,线程的延迟。
由于有延迟,当票号减为1时,假设线程1此时出售1号票,对票号进行判断之后,进入while循环,在售票之前通过sleep()方法让线程休眠,这时线程二会进行售票,由于此时此时票号仍为1,因此线程2也会进入循环,同理,四个线程都会进入循环,休眠结束后,四个线程都会卖票,这样就相当于将票号减了四次,结果中也就出现了0、-1、-2这样的现象
所以为了线程的安全问题,我们引入同步代码块以及同步方法
同步代码块
synchronized(lock){
操作共享资源代码块
}
利用java的同步机制,可以保证在多个线程使用同一个资源时,用于处理共享资源的代码任何时候都只有一个线程进行访问
优化后的代码:
/**
* 模拟窗口售票
* @author Shawn·Zhang
*/
public class Example01 {
public static void main(String[] args) {
TicketWindow tw = new TicketWindow();
new Thread(tw,"售票口一").start();
new Thread(tw,"售票口二").start();
new Thread(tw,"售票口三").start();
new Thread(tw,"售票口四").start();
}
}
class TicketWindow implements Runnable {
private int tickets = 10;
Object lock = new Object();
@Override
public void run() {
while (true) {
synchronized (lock) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(tickets>0){
System.out.println(Thread.currentThread().getName() + "正在发售第"
+ tickets-- + "张票");
}else{
break;
}
}
}
}
}
测试结果:
从测试结果可以看出,没有出现不正确的票。这是因为售票的代码实现了同步,之前出现的线程安全问题得以解决。
同步方法
与同步代码块同理,作用相同,只是书写格式有区别,在这里也写出来,以供学习
synchronized 返回值类型 方法名(){}
以售票案例为例,将上述的代码修改后,如下:
/**
* 模拟窗口售票
* @author Shawn·Zhang
*/
public class Example01 {
public static void main(String[] args) {
TicketWindow tw = new TicketWindow();
new Thread(tw,"售票口一").start();
new Thread(tw,"售票口二").start();
new Thread(tw,"售票口三").start();
new Thread(tw,"售票口四").start();
}
}
class TicketWindow implements Runnable {
private int tickets = 10;
@Override
public void run() {
while (true) {
saleTickets();//调用售票方法
if(tickets<=0){
break;
}
}
}
private synchronized void saleTickets(){
if(tickets>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在发售第"
+ tickets-- + "张票");
}
}
}
结果可以参考同步代码块的测试结果。
死锁
在同步问题上,需要注意死锁问题,就是两个线程在运行的过程中,都在等待对方的锁,这样就会使得程序停滞
在开发中,要避免死锁的情况
public class Example06 {
public static void main(String[] args) {
Thread t1 = new Thread(new DeadLock(true));
Thread t2 = new Thread(new DeadLock(false));
t1.start();
t2.start();
}
}
class DeadLock implements Runnable{
private boolean flag;//定义boolean类型的变量flag
static Object locka = new Object();//定义锁对象locka
static Object lockb = new Object();//定义锁对象lockb
public DeadLock(boolean flag) {//有参构造方法
this.flag = flag;
}
@Override
public void run() {
if(flag){
while(true){
synchronized(locka){//锁对象上的同步代码块
System.out.println("if locka");
synchronized (lockb) {//锁对象上的同步代码块
System.out.println("if lockb");
}
}
}
}else{
while(true){
synchronized(lockb){//锁对象上的同步代码块
System.out.println("else lockb");
synchronized (locka) {//锁对象上的同步代码块
System.out.println("else locka");
}
}
}
}
}
}
测试结果会出现
死锁现象