Java多线程系列-------多线程的内存分析及多线程的另一种表达方式

首先在我分析多线程执行过程中的内存分配时,我想说明一下。当我们创建一个线程时,我们内存会问我们的线程分配一个栈,这个栈是线程私有的。换句话说,我们之前执行的main方法实际上是主线程的入口,当我们的jvm编译执行到这句话时,相当于告诉jvm可以开始主线程的执行了。那么我来讲解一下多线程的执行规则。

	
		Demo d1 = new Demo("A");//创建之后线程就被创建了
		Demo d2 = new Demo("B");
		//运行start方法会做两件事,开启线程,运行run方法
		d1.start();
		d2.start();

针对这四行代码,Demo继承了Thread类并重写了它的run方法。首先我们会创建A,B线程的私有栈。当我们执行到d1.start()时,实际上我们就开启了A线程。但是是否会执行run()方法里面的代码块。我们不清楚,调度权交给cpu。同理d2.start()时,开启了B线程。实际上A,B线程以及主线程是在不同的栈内进行执行操作,以及分配相应的变量的空间的。

接下来我们来看一个例子。关于售票的问题。

假设一个窗口有20张票,交给四个窗口来出售,模拟这样一情景。

首先我们知道没一张票都是不同的,任意两个窗口不可以卖同一张票 。而且四个窗口的卖票工作是同时执行的。四个窗口就像四个线程同时执行一样,但是要保证出售的票不重复,怎么做呢。

先看下面的代码

package com.zzu.my.thread.test.thread;

class SellTicket extends Thread{
	private  int tickets = 20;//初始化有20张票
	//买票这个动作被多个窗口执行也就是被多个线程执行,要将这些代码中定义到线程任务中
	//run方法中
	@Override
	public void run(){
		while(true){
			if(tickets>0){
				System.out.println("窗口: "+Thread.currentThread().getName()+"出售了第"+tickets--+"张票");
			}else{
				break;
			}
		}
	}
	
}
/**
 * 模拟售票窗口
 * @author Administrator
 *
 */
public class TicketDemo {

	public static void main(String[] args) {
		SellTicket t1 = new SellTicket();
		SellTicket t2 = new SellTicket();
		SellTicket t3 = new SellTicket();
		SellTicket t4 = new SellTicket();
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}

}

private  int tickets = 20;//初始化有20张票

注意这个地方,如果我执行程序,会发现我实际上卖了80张票。原因是什么?很简单,我们的四个线程实际上在四个栈中工作,我们创建一个线程的同时,我们的ticket变量也创建了一次。实际上四个线程分别对他们的四个tickets变量在进行操作,也就是卖了80张票。

那么有人会说,我给ticket加上static如何,解决了票是多线程共有的问题。但是还会有一个同步的问题,我留作下次再说。

现在我说一下,如果我不加static,是否可以实现题目要求呢。当然可以,换句话说,我们实际上是对一个tickets进行操作的。我只要在程序中只创建一个tickets就足够了。下面来看一下多线程的第二种写法,Runnable。

Runnable是一个接口,我们需要实现它的run()方法

public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();

那么我们再来看Thread的构造方法,

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

我们只需要将实现了Runnable接口的对象传进去就可以了。这样做实际上我们只创建了一个tickets变量,这个变量是多线程所共有的。这样也符合我们面向对象中的封装思想,将资源和操作资源的方法(线程)分离。

代码如下

package com.zzu.my.thread.test.runnable;
/**
 * 创建多线程的第二种方法<br>
 * 定义一个类实现Runnable<br>
 * 解耦: 将线程对象和具体的任务进行解耦合<br>
 * @author Administrator
 *
 */
/**
 * 定义一个类实现Runnable接口<br>
 * 覆盖Runnable接口中的run()方法,将线程要运行的任务代码存储到该方法中。<br>
 * 通过Thread类创建对象,并将实现了Runnable接口的对象作为Thread类的构造函数进行传递<br>
 * 调用Thread类的start方法开启线程<br>
 * @author Administrator
 *
 */
class Ticket implements Runnable{
	private int tickets = 20;
	public void run() {
		while(true){
			synchronized (Ticket.class) {
			if(tickets>0){
				try {
					Thread.sleep(100);//让线程到这个地方停止一段时间
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("窗口: "+Thread.currentThread().getName()+"出售了第"+tickets--+"张票");
			}else{
				break;
			}
		}
		}
	}
	
	
}
//将资源和线程进行分离
public class TicketDemo {

	public static void main(String[] args) {
	Ticket ticket = new Ticket();
		//创建Thread 对象
	//创建4个对象执行的是一个对象内的run()方法
		Thread t1 = new Thread(ticket);
		Thread t2 = new Thread(ticket);
		Thread t3 = new Thread(ticket);
		Thread t4 = new Thread(ticket);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}

}

当然,这两种写法都没有考虑到线程的同步问题。

换句话说,多线程的执行是随机的,一个线程在执行过程中可能会因为各种情况被cpu剥夺执行权,这个时候如果run()方法里面涉及到共享变量,就会出现参数的错误。比如我在执行到要执行tickets自减一的时候,cpu剥夺了执行权。交给其他线程,这个时候相当于tickets在另一个线程中要减一。这就相当于同一张票卖了两次。实际上我们应当保证run()方法内部代码的原子性,那么怎么实现呢。我会在我的下一篇博客中写出来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值