Java基础——多线程的安全问题

本文通过实例探讨了多线程环境下线程安全问题,特别是打印字符串时的并发问题,详细介绍了如何使用同步机制(如synchronized关键字)解决此类问题,确保线程间的正确同步与数据一致性。

首先用一个例子来引入,多线程中常见的线程安全问题:


我们自定义一个线程类实现Runnable接口:

package com.flyingduck.thread;

public class PrintThread implements Runnable {

	private String str;

	public PrintThread(String str) {
		this.str = str;
	}

	@Override
	public void run() {
		while (true) {

			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			printString(str);
		}
	}

	private void printString(String str) {
		for (int i = 0; i < str.length(); i++) {
			System.out.print(str.charAt(i));
		}
		System.out.println();
	}

}
线程用于逐个字符打印str字符串;


package com.flyingduck.thread;

public class TraditionalThread {

	public static void main(String[] args) {

		PrintThread target1 = new PrintThread("AAA-BBB");
		PrintThread target2 = new PrintThread("CCC-DDD");

		new Thread(target1).start();
		new Thread(target2).start();
	}

}

我们启动了两个线程打印不同的字符串。按照正常来说,我们应该每次都会打印完整的“AAA-BBB”“CCC-DDD”字符串,但是结果却是:

AAA-BBBC
CC-DDD
ACCC-DDD
AA-BBB
CAAA-BBB
CC-DDD
AAA-BBB
CCC-DDD
ACCC-DDD
AA-BBB
CCC-DDDA
AA-BBB
CACC-DDDA
A-BBB
ACAA-BBB

之所以会发生这种情况是因为,在多个线程争夺CUP的使用权的时候发生了不安全的事情:当线程1取得CPU使用权打印到一部分时,线程2抢到了CPU的使用权,这时开始打印线程2中的数据,就出现了上面的结果。为了解决这样的问题,我们必须保证在执行类似于打印字符串这样的写操作时,一定要保证它能够执行完(类似于数据库中的一个事务),才让出CPU的使用权。


Java中使用线程同步解决这样的问题:

package com.flyingduck.thread;

public class PrintThread implements Runnable {

	private String str;

	private static Object lock = new Object();

	public PrintThread(String str) {
		this.str = str;
	}

	@Override
	public void run() {
		while (true) {

			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (lock) { //为什么不适用str作为锁呢?
				printString(str);
			}
		}
	}

	private void printString(String str) {
		for (int i = 0; i < str.length(); i++) {
			System.out.print(str.charAt(i));
		}
		System.out.println();
	}

}

这里将打印的操作放入同步代码块中(synchronized关键字);代码块需要一个对象作为同步锁。这里为什么不直接用str对象作为锁呢,而要用一个静态的对象。原因是:既然要起到锁的作用,就必须保证需要保持同步的线程只共享一个锁,虽然在类中咋一看,觉得str只有一个,但是仔细想一下发现,并不是这样的,因为每次创建runnable对象的时候都会传进来一个新的对象(“AAA-BBB”、“CCC-DDD”就充当了不同的两把锁,在这里同步就变得没有意义了)。因为静态的对象是属于类的,而不是一个对象的,所以它可以起到同步锁的作用。


另外的解决办法:

package com.flyingduck.thread;

public class PrintThread implements Runnable {

	private String str;

	private static Object lock = new Object();

	public PrintThread(String str) {
		this.str = str;
	}

	@Override
	public void run() {
		while (true) {

			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
//			synchronized (lock) { //为什么不适用str作为锁呢?
				printString(str);
//			}
		}
	}

	private synchronized void printString(String str) {
		for (int i = 0; i < str.length(); i++) {
			System.out.print(str.charAt(i));
		}
		System.out.println();
	}

}
将普通方法变成同步的可不可以呢? 当然不行,因为普通非静态的同步方法使用的同步锁对象是:this。大家仔细想想,我们创建了两个runnable的实例,当然他们的this就是他们各自的this了,这就变成和使用str对象作为同步锁一样的性质了。

当然我们可以将这个方法变成静态的:

package com.flyingduck.thread;

public class PrintThread implements Runnable {

	private String str;

	private static Object lock = new Object();

	public PrintThread(String str) {
		this.str = str;
	}

	@Override
	public void run() {
		while (true) {

			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
//			synchronized (lock) { //为什么不适用str作为锁呢?
				printString(str);
//			}
		}
	}

	private synchronized static void printString(String str) {
		for (int i = 0; i < str.length(); i++) {
			System.out.print(str.charAt(i));
		}
		System.out.println();
	}

}

这样就可以解决问题了。(原因不难想,是因为静态的方法使用的同步锁对象是类的字节码 也就是 class对象)






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值