Synchronized、Lock与哲学家进餐

线程同步和安全问题

要使多道程序能够并发执行,不同的线程间就不可避免地需要对一些共享资源进行操作。而在操作的过程中,可能发生一些隐患。如果只看一个线程执行的代码,它是被顺序执行的。但是如果观测所有线程的代码,会发现所有线程的代码并不是按照一个固定的顺序去执行的。

public class example {
	public static void main(String[] args) {
		examplethread test1 = new examplethread();
		examplethread test2 = new examplethread();
		test1.start();
		test2.start();
	}
}

public class examplethread extends Thread{
	
	public void run() {
		int i = 1000;
		while(i>0) {
			System.out.println(this.getId()+" "+i);
			i--;
		}
	}
}

上面这段代码并不能做到让按顺序交替输出,其运行结果片段如下:

在这里插入图片描述
当使用多个线程访问共享资源时,由于这种现象,可能导致我们访问到的共享资源并不是我们希望的那个结果,这就是出现了线程安全问题。
为了解决线程的安全问题,就需要引入一个同步机制,来规范不同线程对共享资源的访问,使得访问到的就是我们所希望的结果。
java中提供了synchronized和lock两种实现同步的方法。在Java中每个对象都有一个锁,多个线程要想访问该对象,需要持有该对象的锁才能访问。

Synchronized

Synchronized关键字是java的内置关键字,可以通过Synchronized来定义同步块或者同步方法来实现同步。
同步块:

synchronized(Object){

}

当某个线程执行到这样的synchronized代码块时,会得到对象Object的锁,从而使得这段Object对象相关的代码只能由持有锁的这个线程执行,以此来实现线程同步。
同步方法:
在方法前加synchronized关键字,该方法就被定义为同步方法。当有一个线程调用这个synchronized方法时,这个线程就获得了这个synchronized方法的对象的锁,其他线程也就不能再访问这个对象的synchronized方法。
必须将每个访问共享资源的方法都定义为synchronized方法。

Lock

Lock不是Java内置的关键字,而是一个接口,在java.util.concurrent.locks包下。
lock接口的定义如下:

public	interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

lock、trylock、方法都可以用来获取锁,不过trylock方法不会等待,不管是否获得锁都立即返回。如果获得锁,返回true,没获得返回false。
lock方法如果没有获得锁,则等待。因此通常lock方法要和异常处理一起使用。

Lock接口有唯一的一个实现类ReentrantLock,实例化一个ReentrantLock对象,该对象就代表该类的锁,调用该对象的Lock接口方法,就可以来获得或者释放这个类(的对象)的锁。

哲学家进餐

哲学家进餐是个经典的操作系统进程同步问题,问题描述如下:
五个哲学家围着一张桌子坐下,每两人之间有一支筷子,要想吃饭的话需要同时拿两只筷子,他们的生活方式是交替进行吃饭。
这个问题中,筷子就是临界资源,哲学家之间如果不沟通,同时拿起同侧的一支筷子就会发生死锁。
这个问题目前有三种主流解法:
服务生解法:引入一个服务生,当哲学家想要拿起筷子时,服务生要先判断是不是两支筷子都空闲,拿起来能就餐。当可以时才让哲学家拿筷子。
资源分级解法:为资源分配一个分级关系,按该关系分配资源,按该关系相反的关系释放资源。为每个筷子设置递增(递减)的等级,哲学家先拿左右两边筷子中级低的那支,再拿级高的那支。
Chandy/Misra解法。

用synchronized实现

下面这段代码分别用synchronized关键字和lock接口模仿服务生解法来解决哲学家进餐问题。

import java.util.*;
public class SynchronizedMethod {
	
	public volatile List<Integer> chopsticks = new ArrayList<Integer>();
	//创建哲学家列表
	List<LazyDogs> dogs = new ArrayList<LazyDogs>();
	
	public SynchronizedMethod() {
		
		int a = 6;
		//初始化筷子列表
		for(int i=0;i<5;i++) {
			chopsticks.add(a);
		}
//		for(int i=0;i<chopsticks.size();i++) {
//			System.out.println("编号"+i+"的筷子为"+chopsticks.get(i));
//		}
	}
	
}

import java.util.*;
public class LazyDogs extends Thread{
	
	private List<Integer> chopsticks;
	//哲学家的位置
	public int location;

	public LazyDogs(List<Integer> chopsticks) {
		//将筷子列表传递多来
		this.chopsticks = chopsticks;
	}
	
	public void run() {
//		System.out.println(location+"已启动");
		while(true) {
			Integer one = chopsticks.get(location);
			//获得一边的筷子的锁
			synchronized(one) {
				Integer two = chopsticks.get((location+1)%5);
			//获得另一边的筷子的锁
				synchronized(two) {
					//模仿服务生 检查如果拿起筷子是否能够吃饭
					if(chopsticks.get(location)==6&&chopsticks.get((location+1)%5)==6) {
						chopsticks.set(location, location);
						System.out.println(location+"拿起了筷子"+location);
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						chopsticks.set((location+1)%5, location);
						System.out.println(location+"拿起了筷子"+(location+1)%5);
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						System.out.println(location+"正在吃饭");
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						//吃完饭后放下筷子
						chopsticks.set(location, 6);
						System.out.println(location+"放下了筷子"+location);
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						chopsticks.set((location+1)%5, 6);
						System.out.println(location+"放下了筷子"+(location+1)%5);
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						break;
					}
				}
			}
		}
	}
}

public class test {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//创建筷子对象
		SynchronizedMethod sm = new SynchronizedMethod();
		//创建哲学家对象
		for(int i = 0;i<5;i++) {
			LazyDogs dog = new LazyDogs(sm.chopsticks);
			dog.location = i;
			sm.dogs.add(dog);
		}
		//启动线程
		for(int i=0;i<sm.dogs.size();i++) {
			sm.dogs.get(i).start();
		}
	}
}

结果如下:
在这里插入图片描述
可以看到,虽然没有发生死锁,但是每次只有一个哲学家进餐,并发程度比较低。
用synchronized关键字来获得锁,获得锁的范围只持续到那个代码块结束,结束后锁会被自动释放,因此使用时不够灵活。

用Lock实现

import java.util.concurrent.locks.*;
public class chopstick {
	//创建一个可重入锁对象
	public Lock lock = new ReentrantLock();
	//表明筷子是否被拿起 此时没有被拿起
	int flag = 1;
}
import java.util.ArrayList;
import java.util.List;
public class chopsticks {
	//创建筷子列表
	public volatile List<chopstick> cs = new ArrayList<chopstick>();
	//将筷子加入筷子列表
	public chopsticks() {
		for(int i =0;i<5;i++) {
			chopstick temp = new chopstick();
			cs.add(temp);
		}
	}
	
}

import java.util.ArrayList;
import java.util.List;

public class LockDogs extends Thread{
	
	private  List<chopstick> chopsticks = new ArrayList<chopstick>();
	public int location;
	//将筷子列表传给哲学家
	public LockDogs(List<chopstick> chopsticks) {
		this.chopsticks = chopsticks;
	}

	public void run() {
		while(true) {
			//获得左右两边筷子的锁
			chopsticks.get(location).lock.lock();
			chopsticks.get((location+1)%5).lock.lock();
			try {
				//模仿服务生 判断取筷子后是否能吃饭
				if(chopsticks.get(location).flag==1&&chopsticks.get((location+1)%5).flag==1) {
					//拿筷子 吃饭 放筷子
					chopsticks.get(location).flag = 0;
					System.out.println(location+"拿起了"+location);
					Thread.sleep(1000);
					chopsticks.get((location+1)%5).flag = 0;
					System.out.println(location+"拿起了"+(location+1)%5);
					Thread.sleep(1000);
					System.out.println(location+"正在吃饭");
					Thread.sleep(1000);
					chopsticks.get(location).flag = 1;
					System.out.println(location+"放下了"+location);
					Thread.sleep(1000);
					chopsticks.get((location+1)%5).flag = 1;
					System.out.println(location+"放下了"+(location+1)%5);
					Thread.sleep(1000);
					break;
				}
			}catch(Exception e) {

			}finally {
				//若此时两边筷子不全可用则释放锁 继续检查条件
				chopsticks.get(location).lock.unlock();
				chopsticks.get((location+1)%5).lock.unlock();
			}
		}
	}
}

结果如下:
在这里插入图片描述
可以看到,用Lock实现满足条件的哲学家可以同时吃饭,由于锁的释放是手动的,在使用时更加灵活,并发程度更好。

synochronized和Lock的内容还有很多,这只是简单的应用,背后的深入还有待了解。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值