java多线程实现Runnable接口的线程安全问题
·
一、案例:模拟火车站售票,开启三个窗口售票
- 出现的问题:卖票过程中,出现了重票、错票 ==>也就是出现了线程的安全问题
- 问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
- 解决思路:当一个线程(一个窗口)在操作ticket(火车票)的时候,其他线程不能参与进来。直到这个线程(这个窗口)操作完ticket(火车票)时,其他线程才可以开始操作ticket(火车票)。这种情况即使这个线程(这个窗口)出现了阻塞,也不能被改变。
- 解决办法:在Java中,我们通过同步机制,来解决线程的安全问题。
方式一:同步代码块
方式二:同步方法
方式三:Lock锁(JDK5.0新增) - 同步的方式:
好处:解决了线程的安全问题。
局限性:操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
例如下方代码就会出现重票、漏票的情况
class Window implements Runnable{
// 定义共有100张票
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while(true){
// 如果还有票,就售票
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket --;
} else {
break;
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
// 创建售票窗口对象
Window window = new Window();
// 创建Thread对象
Thread t1 = new Thread(window );
Thread t2 = new Thread(window );
Thread t3 = new Thread(window );
// 给线程设置线程名称
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
// 开启线程,并调用run()方法
t1.start();
t2.start();
t3.start();
}
}
二、同步代码块的方法
synchronized(同步监视器){
// 需要被同步的代码
}
- 操作共享数据的代码,即为需要被同步的代码。 ==>不能包含代码多了,也不能包含代码少了。
- 共享数据:多个线程共同操作的变量。比如:ticket(火车票)就是共享数据。
- 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
class Window implements Runnable{
// 定义共有100张票
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while(true){
// 此时的this:唯一的Window的对象 // 方式二:synchronized (obj) {
synchronized (this){
// 如果还有票,就售票
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket --;
} else {
break;
}
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
// 创建售票窗口对象
Window window = new Window();
// 创建Thread对象
Thread t1 = new Thread(window );
Thread t2 = new Thread(window );
Thread t3 = new Thread(window );
// 给线程设置线程名称
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
// 开启线程,并调用run()方法
t1.start();
t2.start();
t3.start();
}
}
三、同步方法的方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明成同步的。
- 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
- 非静态的同步方法,同步监视器是:this
- 静态的同步方法,同步监视器是:当前类本身
class Window implements Runnable {
// 定义共有100张票
private int ticket = 100;
@Override
public void run() {
while (true) {
// 调用同步方法锁,卖票方法
show();
}
}
// 相当于同步监视器:this(synchronized (this))
private synchronized void show(){
// 如果还有票,就售票
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket --;
}
}
}
public class WindowTest {
public static void main(String[] args) {
// 创建售票窗口对象
Window window = new Window();
// 创建Thread对象
Thread t1 = new Thread(window );
Thread t2 = new Thread(window );
Thread t3 = new Thread(window );
// 给线程设置线程名称
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
// 开启线程,并调用run()方法
t1.start();
t2.start();
t3.start();
}
}
四、Lock锁的方法
如果同步代码有异常,要将 unlock() 写入finally语句块
import java.util.concurrent.locks.ReentrantLock;
class Window implements Runnable{
// 定义共有100张票
private int ticket = 100;
// 1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try{
// 2.调用锁定方法lock()
lock.lock();
// 如果还有票,就售票
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
ticket --;
}else{
break;
}
}finally {
// 3.调用解锁方法:unlock()
// 如果同步代码有异常,要将unlock()写入finally语句块
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
// 创建售票窗口对象
Window window = new Window();
// 创建Thread对象
Thread t1 = new Thread(window );
Thread t2 = new Thread(window );
Thread t3 = new Thread(window );
// 给线程设置线程名称
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
// 开启线程,并调用run()方法
t1.start();
t2.start();
t3.start();
}
}
五、synchronized 与 Lock的异同?
- 相同点:二者都可以解决线程安全问题
- 不同点:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器;Lock需要手动的启动同步( lock() ),同时结束同步也需要手动的实现( unlock() )
- 建议优先使用顺序:Lock ==>同步代码块(已经进入了方法体,分配了相应资源) => 同步方法(在方法体之外)
更多推荐
所有评论(0)