java多线程的一些感悟

举个例子吧, 比如说有一个仓库(资源池对象),仓库里存放了20把镰刀(资源池含有20个资源),现在有100个人(100个线程)想要到仓库里拿镰刀去割草(获取资源做相关的事情)。他们同时来到了仓库门前(并发),想要打开仓库门去拿镰刀,但是仓库每次只能进1个人,于是仓库管理员(JVM)就提供了一把锁(synchonized关键字),想要进去的人拿着这把锁把门锁上,然后后面的人就进不去了(线程互斥)。一个人拿完镰刀出来,就把锁给下一个人,下一个人重复前一个人的动作,锁仓库拿镰刀,直到20把镰刀都拿完了(资源用尽)。接下来,剩下的80个人他们不知道仓库里还有多少把镰刀,还是重复前面人的动作,锁住仓库,进去一看,镰刀没了,他们就出来,站在小角落里等其他人用完镰刀还回来(wait()方法)。好了,80个人都在等,终于有人割完草回来了,他回来把仓库锁住,把镰刀放进去,出来时他有两种方式来告诉在等的人说,有一个镰刀已经还回来了。一种方法是只告诉一个人(notify()),还一种是告诉所有人(notifyAll())。如果只告诉一个人,那个人就锁仓库拿镰刀,没有问题。 如果告诉所有人,每个人都依次进去拿镰刀,第一个人OK,第二个人就拿不到了,所以需要判断一下仓库里有没有镰刀,不能盲目去拿,不然会出事(抛出异常)。如果有人在拿了镰刀的时候在仓库里睡觉(sleep),外面的人也没办法,只有傻等等他出来。这就是sleepwait的区别。sleep占着茅坑不拉屎,我在里面不出来;wait比较好,看见没有我要的东西,我出来,我把锁给你们,你们自己进去看(sleep不释放锁,wait释放锁)。这是大集体时代,100个人只有一个仓库(有指向同一个对象的引用)。假设到了现代,每个人都有一个仓库,那么当一个人锁住自己的仓库的时候,其他人还是可以进自己的仓库(所有线程共享一个变量   VS 每个线程含有一个类的对象)。所以说如果每个线程初始化的时候都是new了一个资源池对象,那么你再怎么给资源上锁,还是对其它线程没影响。所以用synchronized 修饰方法实际上是给对象上锁。有几个对象就有几个仓库,大家互不干扰(当然如果每个线程都包含对同一个对象的引用,那还是有影响的,因为只有一个仓库嘛)。但是如果类里面有静态变量,就相当于给类上锁,也就是给类的所有实例对象上锁,这样就对其它线程产生了影响。所以写程序的时候要想清楚,是对同一个对象进行多线程操作,还是像socket一样来一个连接,我启动一个新线程,每个连接互不干扰。

多线程还有一个关键词volatile。当我们用这个修饰一个变量时,每次在读取这个变量的时候都会读取这个变量最新的值。最新是因为每个JVM线程可能会对这个变量保存一个本地副本,加了这个词,其它的线程能够看到除自己之外的线程对这个变量做了什么操作。其实synchronized关键词除了也有这个功能外,还有一个功能就是对对象上锁从而达到互斥(上面例子的锁仓库)。

以上是我在实现一个资源池时参考资料对常见的多线程的一些感悟。附上我写的一个简单模拟资源池的例子。

import java.util.Stack;


public class MyResource {
	public  int fooNumber = 0;
	private  int totalNumber = 20;
	private  volatile Stack<Integer> idleFooStack = new Stack<Integer>();
	
	public MyResource(){
		for(int i = 0; i < totalNumber; i++){
			idleFooStack.push(new Integer(0));
		}
	}
	
	@SuppressWarnings("static-access")
	public  Integer getResource(String tname, Thread t) throws InterruptedException{
		Integer i = null;
		synchronized(idleFooStack){
			if(idleFooStack.isEmpty()){
				while(true){
					System.out.println(tname + " is waiting");
					idleFooStack.wait();//说明线程是为了<span style="color:#ff0000;">idleFooStack</span>而等待,可以这么理解,notifyAll()之后,等待的线程接着往下执行,默认还是会得到锁
					if(!idleFooStack.isEmpty()){//因为是notifyAll(),如果不加此判断,会出现空栈pop()
						System.out.println(tname + " get a resource, sleep 5s");
						t.sleep(5000);//为了说明sleep跟wait的区别,在这种情况下,这个线程会一直持有这一段代码的锁,其它被唤醒的线程只能继续等待
						i = idleFooStack.pop();
						break;
					}
				}	
			}else{
				i = idleFooStack.pop();
			}
		}
		return i;
	}
	
	public void realease(Integer i){
		synchronized(idleFooStack){
			i = 0;
			idleFooStack.push(i);
			idleFooStack.notifyAll();//通知所有因为<span style="color:#ff0000;">idleFooStack</span>而等待的线程,当然也可以用notify(),只不过用notifyAll()需要更加小心,见getResource()处理逻辑
		}
	}
}
这是线程池例子。

public class TestThread extends Thread {
	public String name;
	public Integer resource;
	MyResource pool;
	public TestThread(MyResource pool, String name){
		this.name = name;
		this.pool = pool;
	}
	
	public void run(){
		try {
			resource = pool.getResource(name, this);
			Thread.sleep(5000);
			pool.realease(resource);
			System.out.println(name + " released a resource");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
这是线程例子

public class Test {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		MyResource resource = new MyResource();
		for(int i = 0; i < 50; i++){
			TestThread t = new TestThread(resource, "t" + i);
			t.start();
		}
	}
}

这是测试代码

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值