黑马程序员_java多线程

------- android培训、java培训、期待与您交流! ----------

进程与线程

在多任务系统中,每个独立执行的程序称为进程,也就是正在执行的程序。我们现在使用的操作系统一般都是多任务的,即能够同时执行多个应用程序,实际情况是操作系统负责对CPU等设备的资源进行分配和管理,虽然这些设备某一时刻只能做一件事情,但以非常小的时间间隔交替执行多个程序,就可以给人以同时执行多个程序的感觉。

一个进程中又可以包含一个或多个线程,一个线程就是一个程序内部的一条执行线索。如果要程序中实现多代码同时交替运行,就需要产生多个线程,并指定每个线程上所要运行的程序代码块,这就是多线程。


线程的实现

在java中要想实现多线程操作有两种手段,一种是继承Thread类,另一种就是实现Runnable接口。

继承Thread类

步骤:

1.定义类继承Thread。

2.复写Thread类中的run方法。

目的:将自定义代码存储在run方法中,让线程运行。

3.调用线程中的start方法,该方法有两个作用:启动线程并调用run方法。

class MyThread extends Thread
{
	private String name;
	public MyThread(String name)
	{
		this.name = name;
	}
	public void run()//覆写Thread类中的run()方法
	{
		for(int i=0;i<10;i++)
		{
			System.out.println(name + "运行,i = " + i);
		}
	}
}

public class ThreadDemo {
	public static void main(String[] args) {
		MyThread t1 = new MyThread("线程A");
		MyThread t2 = new MyThread("线程B");
		t1.start();//启动多线程
		t2.start();
	}
}
思考:在启动多线程时为什么必须通过start()方法启动,而不能直接调用run()方法呢?

我们可以参阅start()方法在Thread类中的定义。

代码:start()方法部分定义

public synchronized void start()

{

if(threadStatus !=0)

throw new IllegalThreadStateException();

...

start0();

...

}

private native void start0();

可以看出实际上调用的是start0()方法,此方法在声明处使用了native关键字,此关键字表示调用本机的操作系统函数,因为多线程的实现需要依靠底层操作系统支持。而且start()方法会去调用run()方法。


实现Runnable接口

步骤:

1.定义类实现Runnable接口。

2.覆盖Runnable接口中的run方法。

将线程要运行的代码存放在run方法中。

3.通过Thread类建立线程对象。

4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。

为什么要将Runnable接口的子类对象传递给Thread的构造函数?

因为自定义的run方法所属的对象是Runnable接口的子类对象。

所以要让线程去指定运行对象的run方法,就必须明确该run方法所属对象。

5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

实现方式和继承方式有什么区别?

实现方式好处:避免了单继承的局限性。

在定义线程时,建议使用实现方式。

继承Thread:线程代码存放在Thread子类run方法中。

实现Runnable:线程代码存放在接口的子类run方法中。


上图中如果Student类中的部分代码要用到多线程,同时自己继承Person类,但java中只支持单继承,所以此时我们需要采用实现Runnable接口的方式去创建多线程。

下面的案例便是使用实现Runnable接口的方式去创建多个线程,同时引出多线程安全问题。

/*
需求:简单的卖票程序。
多个窗口同时卖票。
*/
class Ticket implements Runnable
{
	private  int tick = 100;
	public void run()
	{
		while(true)
		{
			if(tick>0)
			{
				try{Thread.sleep(10);}catch(Exception e){}
				System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
			}
		}
	}
}

class  TicketDemo
{
	public static void main(String[] args) 
	{

		Ticket t = new Ticket();

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		t1.start();
		t2.start();
		t3.start();
		t4.start();		
	}
}
结果打印出0,-1,-2等错票。

多线程的运行出现了安全问题。

问题的原因:

当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。

解决办法:

对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

同步代码块可以有效的解决多线程出现的安全隐患。

synchronized(对象)
{
需要被同步的代码
}
对象如同锁。持有锁的线程可以在同步中执行。

没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。

改进后的代码:

class Ticket implements Runnable
{
	private  int tick = 1000;
	Object obj = new Object();
	public void run()
	{
		while(true)
		{
			synchronized(obj)
			{
				if(tick>0)
				{
					System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
				}
			}
		}
	}
}

class  TicketDemo2
{
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
同步的前提:

1.必须要有两个或者两个以上的线程。

2.必须是多个线程使用同一个锁。

必须保证同步中只能有一个线程在运行。

好处:解决了多线程的安全问题。

弊端:多个线程需要判断锁,较为消耗资源。


死锁

所谓死锁就是指两个线程都在等待对方先完成,造成了程序的停滞,一般程序的死锁就是在程序运行时出现的。

多个线程共享同一资源时需要进行同步,以保证资源操作的完成性,但是过多的同步就有可能产生死锁。

class Ticket implements Runnable
{
	private  int tick = 1000;
	Object obj = new Object();
	boolean flag = true;
	public  void run()
	{
		if(flag)
		{
			while(true)
			{
				synchronized(obj)
				{
					show();
				}
			}
		}
		else
			while(true)
				show();
	}
	public synchronized void show()
	{
		synchronized(obj)
		{
			if(tick>0)
			{
				try{Thread.sleep(10);}catch(Exception e){}
				System.out.println(Thread.currentThread().getName()+"....code : "+ tick--);
			}
		}
	}
}

class  DeadLockDemo
{
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		try{Thread.sleep(10);}catch(Exception e){}
		t.flag = false;
		t2.start();
	}
}

生产者及消费者

class Information//定义信息类
{
	String name = "unknown";
	String sex = "unknown";
	boolean isFull = false;//设置标志位
	public synchronized void put(String name,String sex)
	{
		if(isFull)
		{
			try
			{
				wait();//等待消费者取走
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}
		}		
		this.name = name;
		this.sex = sex;
		isFull = true;
		notify();//唤醒等待线程
	}
	public synchronized void get()
	{
		if(!isFull)
		{
			try
			{
				wait();//等待生产者生产
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}
		}		
		System.out.println(name + ":" + sex );
		isFull = false;
		notify();//唤醒等待线程
	}
}

class Producer implements Runnable//定义生产者线程
{
	Information info;
	public Producer(Information info)
	{
		this.info = info;
	}
	public void run()
	{
		int i = 0;
		while(true)
		{
			if(i == 0)
				info.put("zhangsan","male");
			else
				info.put("lisi","female");			
			i = (i+1)%2;
		}
	}
}

class Consumer implements Runnable//定义消费者线程
{
	Information info;
	public Consumer(Information info)
	{
		this.info = info;
	}
	public void run()
	{
		while(true)
		{
			info.get();
		}
	}
}

public class ThreadCommunation 
{
	public static void main(String[] args) 
	{
		Information info = new Information();
		new Thread(new Producer(info)).start();
		new Thread(new Consumer(info)).start();
	}
}
wait:告诉当前线程放弃监视器并进入睡眠状态知道其他线程进入同一监视器并调用notify为止。

notify:唤醒同一对象监视器中调用wait的第一个线程。

notifyAll:唤醒同一对象监视器中调用wait的所有线程,具有最高优先级的线程首先被唤醒并执行。

线程的等待和唤醒过程:



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值