黑马程序员--JAVA多线程

JAVA多线程


一、进程和线程的区别和关系。

多线程:就是指应用程序有多条执行路径。
 *   进程:正在运行的应用程序。
 *   线程:进程的执行单元,一条执行路径。
 *
 * 举例: 
 *   迅雷下载,360管理界面。
 *
 * 我们如何实现多线程程序呢?
 * 由于线程是依赖于进程存在,而进程是由操作系统创建的,并且java语言是不能直接调用操作系统的功能。
 * 所以,为了方便对多线程程序的时候,java就提供了线程的API对应的类。
 *
 * 线程类:Thread
 *
 * 通过查看API,我们到创建线程的方式有2种。
 * 方式1:继承Thread类。
 *   A:定义一个类继承Thread类。
 *   B:子类要重写Thread类的run()方法。
 *   C:让线程启动并执行。
 * 注意:启动线程并执行,是不能使用run()方法的。这个时候,必须使用另外的一个方法。
 * 这个方法名是start()。这个方法其实做了两件事情,第一,让线程启动。第二,自动调用run()方法。


现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。

比如在Windows系统中,一个运行的exe就是一个进程。

线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。

线程总是属于某个进程,进程中的多个线程共享进程的内存。

“同时”执行是人的感觉,在线程之间实际上轮换执行。


二、JAVA中的多线程


在Java中,“线程”指两件不同的事情:
1、java.lang.Thread类的一个实例;

2、线程的执行。

使用java.lang.Thread类或者java.lang Runnable接口编写代码来定义、实例化和启动新线程。

一个Thread类实例只是一个对象,像Java中的任何其他对象一样,具有变量和方法,生死于堆上。

Java中,每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行着。

一个Java应用总是从main()方法开始运行,mian()方法运行在一个线程内,它被称为主线程。

一旦创建一个新的线程,就产生一个新的调用栈。

线程总体分两类:用户线程和守候线程。


三、Thread多线程的方法:


public final void setName(String name)
public final void getName(String name)
public void start()
public static void sleep(long millis)
public static void yield()


以下是以售卖电影票为例子,举例代码体现。

package cn.itcast_03;

public class RunnableTest {
	public static void main(String[] args) {
		TicketRunnable tr = new TicketRunnable();

		Thread t1 = new Thread(tr);
		Thread t2 = new Thread(tr);
		Thread t3 = new Thread(tr);
		Thread t4 = new Thread(tr);

		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		t4.setName("窗口4");

		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}


package cn.itcast_03;

public class TicketRunnable implements Runnable {

	private int tickets = 200;

	@Override
	public void run() {
		while (true) {
			if (tickets > 0) {
				System.out.println(Thread.currentThread().getName() + "正在出售第"
						+ (tickets--) + "张票");
			}
		}
	}

}


四、线程中的锁

以上代码虽然实现了模拟售票的内容,但是存在着一定的问题,就是会出现卖出负数票的情况。

那么,产生的原因是什么呢?
 *   线程的随机性和延迟性,导致了线程访问共享数据出现了问题。
 * 怎么解决呢?
 *   在多线程程序中,一般来说,不会是所有的代码都有问题,所以,我们只需要找到那些可能出问题的代码,
 *   我把可能出问题的代码, 跟包起来,看做是一个整体,只有这个整体完毕,别人才能继续访问。
 *   为了安全,把这个整体包起来的代码加个锁。给锁起来。
 *
 * 怎么找?(多线程出问题的判断条件)
 *   A:看有没有共享数据
 *   B:看对共享数据的操作是不是多条语句
 *   C:看是不是在多线程程序中
 *
 *   找到后,就把同时满足这三个条件的代码给锁起来。
 *
 * 怎么锁?
 *   java提供了一种锁机制方式:
 *    synchronized(锁对象)
 *    {
 *     需要被锁的代码;
 *    }
 *   锁对象:怎么做呢?反正不知道,所以,我们就用Object类的实例。
 *   
 *   注意:多个线程必须使用同一把锁。

代码如下:

package cn.itcast_05;

public class RunnableTest {
	public static void main(String[] args) {
		TicketRunnable tr = new TicketRunnable();

		Thread t1 = new Thread(tr);
		Thread t2 = new Thread(tr);
		Thread t3 = new Thread(tr);
		Thread t4 = new Thread(tr);

		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		t4.setName("窗口4");

		t2.start();
		t3.start();
		t4.start();
		t1.start();
	}
}
public class TicketRunnable implements Runnable {

	private int tickets = 100;
	private Object obj = new Object();

	@Override
	public void run() {
		while (true) {
			//tickets=1
			//t1,t2,t3,t4都来了
			//假设t1抢到,看到是开的状态
			synchronized (obj) {
				//锁对象的状态:开,关
				//t1进来了,给外界了一个关的状态。
				if (tickets > 0) {
					try {
						//t1睡了
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "正在出售第"
							+ (tickets--) + "张票");
				}
			} //t1把状态修改为开
		}
	}

}


五、多线程中的错误输出解决

我们先分析问题什么有这个问题,然后再解决问题。
 * 由于线程的随机性产生的问题。
 *
 * 然后我们在回到上午给大家的那个总结:
 *   A:是否有共享数据
 *   B:是否有多条语句操作共享数据
 *   C:是否在多线程环境中
 *
 * 出问题的原因我们知道了,那么怎么解决呢? 
 *   用同步解决。
 * 我们把setStudent给加同步了,但是,还是有问题。原因是需要对多个线程都要加同步。
 * 我给两个操作都加同步了,还是出问题,这一次的原因是:两种操作的锁对象不一致。
 * 当我们把所有的操作都加同步,并且锁用同一个以后,我们的数据就没有问题了。
 *
 * 这个时候,我们看到了一个不好的现象,就是同一个数据一大片一大片的输出,原因就是每一次获取到CPU的执行权,就足够输出很多次数据。
 * 我想,能不能输出一个林青霞,然后输出一个刘意,然后再输出一个林青霞,再输出一个刘意...
 *
 * 如何能够做到这个效果呢?
 *
 * 做这个效果前,我们先分析一个问题,问题是:我们的程序其实是有一点点逻辑问题的。
 * 如果没有设置数据,就直接获取数据了。这应该不符合逻辑。
 * 正常逻辑是:
 *   针对输出:
 *    判断是否有数据,如果有就输出。否则,就等待设置数据。
 *   针对设置:
 *    判断是否有数据,如果有就等待输出数据,否则,就“输出”(写错了,应该是否则就设置数据)。
 * 等待唤醒机制:
 *   抓人小游戏。
 *
 * Object类中:
 *   wait() 让线程处于等待状态
 *   notify() 唤醒等待的线程

public class StudentTest {
	public static void main(String[] args) {
		Student s = new Student();// 资源类

		SetStudent ss = new SetStudent(s);
		GetStudent gs = new GetStudent(s);

		Thread t1 = new Thread(ss);
		Thread t2 = new Thread(gs);

		t1.start();
		t2.start();
	}
}
package cn.itcast_10;

public class Student {
	String name;
	int age;
	// flag作为数据标识存在,true表示有数据,false表示没有数据。
	boolean flag = false;
}
package cn.itcast_10;

public class SetStudent implements Runnable {
	private Student s;

	public SetStudent(Student s) {
		this.s = s;
	}

	@Override
	public void run() {
		// t1过来了
		int x = 0;
		while (true) {
			synchronized (s) {
				//如果有数据,就等待。
				if(s.flag){
					try {
						s.wait();//t1等待了,
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
				if (x % 2 == 0) {
					s.name = "林青霞"; // t2抢到;
					s.age = 26;
				} else {
					s.name = "刘意";// t2抢到;
					s.age = 29;
				}
				
				//修改标记
				s.flag = true;
				s.notify();//唤醒了t2,这个时候,可能是t1继续,或者t2抢到。
			}
			x++;// x=1,t2抢到;x=2,t2抢到;
		}
	}

}

package cn.itcast_10;

public class GetStudent implements Runnable {
	private Student s;

	public GetStudent(Student s) {
		this.s = s;
	}

	@Override
	public void run() {
		while (true) {
			synchronized (s) {
				if(!s.flag){
					try {
						s.wait();//t2等待了。wait()的线程,被唤醒后,继续执行。wait()方法出现后,对应的线程就释放了锁对象
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
				System.out.println(s.name + "***" + s.age);
				
				//修改标记
				s.flag = false;
				s.notify(); //t1被唤醒,但是不一定会立马执行。
			}
		}
	}

}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值