Java多线程操作

基本概念
进程:每个独立运行的程序称为进程,即“一个正在运行的程序”
线程:一个进程可包含多个线程,线程即程序内部的一条执行路径。


Java中创建线程的两种方法:
1. 继承Thread类
2. 实现Runable接口


1)通过Thread类来创建线程
java.lang.Thread类(一个Thread类的对象代表一个线程)
一个代码被执行,一定是在某个线程上运行的,代码与线程密不可分,同一段代码可以与多个线程相关联,在多个线程上执行的可以使相同的一段代码。

方法说明:
static Thread currentThread() 返回对当前正在执行的线程对象的引用。

String getName() 返回该线程的名称。

		class TestThread
		{
			public void run()
			{
				while(true)
				{
					System.out.println(Thread.currentThread().getName()+" is running");
				}
			}
		}


		public class ThreadDemo1 {
			public static void main(String[] args) {
				/**
				 * public void run()如果该线程是使用独立的 Runnable 运行对象构造的
				 * 则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 
				 */
				new TestThread().run();
				while(true)
				{
					System.out.println("main thread is running");
				}
			}
		}
上面的代码运行后,屏幕不停的打印 main is running,而不是 main thread is running 。对代码修改如下:

		class TestThread2 extends Thread
		{
			public void run()
			{
				while(true)
				{
					System.out.println(Thread.currentThread().getName()+" is running");
				}
			}
		}

		public class ThreadDemo2 {
			public static void main(String[] args) {
				/**
				 * public void start()使该线程开始执行;
				 * Java 虚拟机调用该线程的 run 方法。 
				 */
				new TestThread2().start();//run();
				while(true)
				{
					System.out.println("main thread is running");
				}
			}
		}

这里,TestThread2继承了Thread类,程序调用了该类对象的start方法。运行显示,看到两个while循环交替运行。

Java单线程与多线程的比较:
1. 可见,在单线程中,main函数必须等到TestThread.run()函数返回后才能继续往下执行。
而在多线程中,main函数调用TestThread.start()方法启动了TestThread.run()函数后,main函数不等待TestThread.run函数返回就继续运行。

2. 要将一段代码在一个新的线程上运行,该代码应该在一个类的run函数中,并且run函数所在类是Thread类的子类。在子类的run函数中高调用想在新线程上运行的程序代码。

3. 启动一个新的线程,不是直接调用Thread子类对象的run方法,而是直接调用Thread子类对象的start(从Thread类中继承的)方法,Thread类对象的start方法将产生一个新的线程,并在该线程上运行该Thread类对象中的run方法。


(2)使用Runnable接口创建多线程 / 实现 Runnable 接口

构造器 public Thread(Runnable target)

Runnable接口:其只有一个run()方法。当使用上述构造器创建线程对象时,行为该方法传递一个实现
Runnable接口的类对象,这样创建的线程将调用那个实现了Runnable接口的类对象中的run()方法作为其运行代码。

		public class ThreadDemo3 {
			public static void main(String[] args) {
				/*
				 * Thread(Runnable target)
				 * 需要传递一个实现了Runnable接口的类对象
				 * Runnable接口中只定义了run()方法
				 */
				TestThread3 tt3 = new TestThread3();
				Thread t = new Thread(tt3);
				t.start();
				while(true){
					System.out.println("main thread is running");
				}
			}
		}

		class TestThread3 implements Runnable{
			
			/*
			 * 线程的代码段,当执行start时,线程从此开始执行
			 */
			public void run(){
				while(true)
				{
					System.out.println(Thread.currentThread().getName()+" is running");
				}	
			}
		}

例子:有4个售票亭准备出售某场比赛的100张票,使用多线程方式实现。

提示:多个线程去处理一个资源,一个资源只能对应一个对象 ---> 使用Runnable接口方式

		public class TicketTest {
			public static void main(String[] args) {
				TicketThread tt = new TicketThread();
				new Thread(tt).start();
				new Thread(tt).start();
				new Thread(tt).start();
				new Thread(tt).start();
			}
		}

		class TicketThread implements Runnable{
			
			private int tickets=100;
			@Override
			public void run() {
				for(int i=0;i<1000;i++){
					if(tickets>0){
						System.out.println(Thread.currentThread().getName()+" selles No." + tickets--);
					}
				}
			}	
		}

在上面的程序中,创建了是个线程,每个线程调用的是同一个TicketThread对象中的run方法,访问的是同一个对象中的变量(tickets)的实例,这个程序满足了我们的要求。

可见,实现Runnable接口相对于继承Thread类来说,有如下好处:

1) 适合多个相同程序代码的线程去处理同一资源的情况,数据与代码分离
2) 避免Java单继承带来的局限。
3) 有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的


多线程的同步

(1)线程的安全性问题:
即使是上面的实现方式中仍然是有安全隐患的,在run方法中故意增加延迟(调用sleep方法),便会显现。

Thread类有如下方法:
public static void sleep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

e.g 在上面的卖票例子的示例程序修改如下,便会出现线程同步问题:

			public void run() {
				for(int i=0;i<1000;i++){
					if(tickets>0){
						Thread.sleep(10);
						System.out.println(Thread.currentThread().getName()+" selles No." + tickets--);
					}
				}
			}
说明:线程的睡眠是可以被打断的,通过Thread.interrupt(),线程的睡眠被打断进入Runnable状态。

所谓“你写的类是线程安全的吗?”就是说你编写的那个类的同一个实例对象的方法在多个线程被调用,是否会出现类似上面的意外。

--->为解决上面的问题,就要考虑到 线程同步 的问题!


(2)同步代码块
在同一时刻,只能有一个线程可以进入同步代码块运行。

格式为:
synchronized(this) 
{
//需要同步的语句块
}


输出结果显示,修改后的程序是线程安全的。
--->关于这方面详细、通俗、专业的解释见 PDF p208

为什么用 this ? ---- 代码块与函数间的同步
不管我们编写的类的内部结构如何,类中的非静态方法始终都能访问到的一个对象就是这个对象本身,即 this 。
线程同步靠的是检查同一对象的标志位,只要让代码块与函数使用同一个监视器对象。

(3)同步方法/同步函数
可以对需要同步的方法定义前加上 synchronized 关键字。其后该方法调用只能够互斥的进行。

修改上述程序:
		class TicketThread implements Runnable {

			private int tickets = 100;

			@Override
			public void run() {
				sale();
			}

			synchronized void sale() {
				for (int i = 0; i < 1000; i++) {
					if (tickets > 0) {
						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()
								+ " selles No." + tickets--);
					}
				}
			}

		}

补充:JAVA 1.5 开始出现了同步锁功能
通过显示定义同步锁对象来实现同步,这种机制,同步锁应该使用Lock对象充当。

在实现线程安全控制中,通常使用ReentrantLock(可重入锁)。使用该对象可以显示地加锁和解锁。

具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

格式:
			public class X 
			{
				private final ReentrantLock lock = new ReentrantLock();
				//定义需要保证线程安全的方法
				public void  m(){
					//加锁
					lock.lock();
					try{
						//... method body
					}finally{
						//在finally释放锁
						lock.unlock();
					}
				}
			}

对上述程序进行修改:

		class TicketTest5 implements Runnable
		{
			private final ReentrantLock lock = new ReentrantLock();
			private int tickets = 100;
			@Override
			public void run() {
				for(int i=0;i<1000;i++){
					sale();
				}
			}
			
			public void sale(){
				lock.lock();
				if(tickets>0){
					try {
						Thread.sleep(1);
						System.out.println(Thread.currentThread().getName()+"卖出了第"+tickets-- +"张票");
					}catch(Exception e)
					{
						e.printStackTrace();
					}
					finally
					{
						lock.unlock();
					}
				}
			}
		}
		

(4)代码块与函数间的同步
--如何实现?线程同步靠的是检查同一对象的标志位,只要让代码块与函数使用同一个监视器对象,答案就应该是肯定的!

那么函数中用的监视器对象是哪个呢?----->不管我们编写的类的内部结构怎样,类中的非静态方法始终都能访问到一个对象就是这个对象本身,即 this ,看来,同步函数所用的监视器对象只能是 this 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值