Java多线程总结

1.基本概念

程序(program)是对数据描述与操作的代码的集合,是应用程序执行的脚本。
进程(process)是程序的一次执行过程,是系统运行程序的基本单位。程序是静态的,进程是动态的。系统运行一个程序即是一个进程从创建、运行到消亡的过程。
多任务(multi task)在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每个任务对应一个进程。
线程(thread):比进程更小的运行单位,是程序中单个顺序的流控制。一个进程中可以包含多个线程。
简单来讲,线程是一个独立的执行流,是进程内部的一个独立执行单元,相当于一个子程序

2.创建进程

可以通过以下两种方式自定义线程类:
创建 java.lang.Thread 类的子类,重写该类的 run方法
创建 java.lang.Runnable接 口的实现类,实现接口中的 run方法

2.1 继承Thread 类

先了解一下Thread类的构造方法
Java多线程总结

public class SummaryThread extends Thread{
	//调用父类的构造方法Thread(String name)来设置名字
	public SummaryThread(String name) {
		super(name);
	}
	@Override
	public void run() {
		for(int i=0;i<10;i++){
			System.out.println(getName()+"正在打印第"+i+"次");
		}
	}
	public static void main(String[] args) {
		SummaryThread thread1 = new SummaryThread("线程1");
		SummaryThread thread2 = new SummaryThread("线程2");
		thread1.start();//这里不要写成run()方法,不然就是一个普通的方法
		thread2.start();//线程方法是start()!!
	}

}

2.2 实现Runnable接口

Runnable 接口中只有一个未实现的 run 方法,实现该接口的类必须重写该方法。
Runnable 接口与 Thread 类之间的区别
Runnable 接口必须实现 run 方法,而 Thread 类中的run 方法是一个空方法,可以不重写
Runnable 接口的实现类并不是真正的线程类,只是线程运行的目标类。要想以线程的方式执行 run 方法,必须依靠 Thread 类
Runnable 接口适合于资源的共享
改写上述代码

public class SummaryThread implements Runnable{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<10;i++){
			System.out.println(Thread.currentThread().getName()+" 第i次执行");
		}
	}
	public static void main(String[] args) {
		SummaryThread st = new SummaryThread();
		Thread thread1 = new Thread(st, "线程1");
		Thread thread2 = new Thread(st, "线程2");
		thread1.start();
		thread2.start();
	}
}

3. 线程的生命周期

线程有5个生命周期:
1.新建(new)
2.可执行(Runnable)
3.运行(Running)
4.阻塞(Blocking)
5.死亡(Deading)
Java多线程总结

3.1 新建状态(New)

当创建了一个Thread对象时,该对象就处于“新建状态”
没有启动,因此无法运行

3.2 可执行状态(Runnable)

其他线程调用了处于新建状态线程的start方法,该线程对象将转换到“可执行状态”
线程拥有获得CPU控制权的机会,处在等待调度阶段。

3.3 运行状态(Running)

处在“可执行状态”的线程对象一旦获得了 CPU 控制权,就会转换到“执行状态”
在“执行状态”下,线程状态占用 CPU 时间片段,执行run 方法中的代码
处在“执行状态”下的线程可以调用 yield 方法,该方法用于主动出让 CPU 控制权。线程对象出让控制权后回到“可执行状态”,重新等待调度。

3.4 阻塞状态(Blocking)

线程在“执行状态”下由于受某种条件的影响会*出让CPU控制权,进入“阻塞状态”。
进入阻塞状态的三种情况
调用sleep方法
调用join方法
执行I/O操作

3.5 死亡状态(Dead)

处于“执行状态”的线程一旦从run方法返回(无论是正常退出还是抛出异常),就会进入“死亡状态”。
已经“死亡”的线程不能重新运行,否则会抛出IllegalThreadStateException
可以使用 Thread 类的 isAlive 方法判断线程是否活着

4.线程同步

4.1 数据共享

问题:通过多线程解决小朋友分苹果的问题:一共有5个苹果,2个小朋友同时拿苹果,每次拿一个,拿完为止
如果使用继承Thread类的方法,我们会发现以下结果
代码

public class GetApple extends Thread {
	private int appleCount=5;
	public GetApple(String name) {
		super(name);
	}
	@Override
	public void run() {
		while(appleCount>0){
			System.out.println(Thread.currentThread().getName()+"拿走一个苹果,还剩下"+(--appleCount));
		}
	}
	public static void main(String[] args) {
		GetApple g1 = new GetApple("小明");
		GetApple g2 = new GetApple("小强");
		g1.start();
		g2.start();
	}

}

Java多线程总结
小明和小强两个线程并没有共享苹果的数量,它们各自都有0个苹果。
为了数据共享,我们需要使用Runnable接口

public class GetApple implements Runnable{
	private int appleCount=5;
	@Override
	public void run() {
		while(appleCount>0){
			System.out.println(Thread.currentThread().getName()+"拿走一个苹果,还剩下"+(--appleCount));
		}
	}
	public static void main(String[] args) {
		GetApple g = new GetApple();
		Thread thread1 = new Thread(g,"小明");
		Thread thread2 = new Thread(g,"小强");
		thread1.start();
		thread2.start();
	}

}

Java多线程总结
虽然这一次数量是正确的了,但是显示还是有问题。最后为什么是小明拿走一个苹果还剩2个苹果呢?
Java多线程总结
从上图我们可以看出,当小明进程还在执行的时候,小强进程已经修改了好几次苹果的数量导致最后输出出现问题。

4.2 线程同步与互斥

为了防止共享对象在并发访问时出现错误,Java中提供了“synchronized”关键字。
synchronized关键字
确保共享对象在同一时刻只能被一个线程访问,这种处理机制称为“线程同步”或“线程互斥”。Java中的“线程同步”基于“对象锁”的概念
Java多线程总结
将之前的代码修改

public class GetApple implements Runnable{
	private int appleCount=5;
	public synchronized void getApple(){
		if(appleCount>0)
			System.out.println(Thread.currentThread().getName()+"拿走一个苹果,还剩下"+(--appleCount));

	}
	@Override
	public void run() {
		while(appleCount>0){
			getApple();
		}
	}
	public static void main(String[] args) {
		GetApple g = new GetApple();
		Thread thread1 = new Thread(g,"小明");
		Thread thread2 = new Thread(g,"小强");
		thread1.start();
		thread2.start();
	}
}

5 进程通信

当一个线程正在使用同步方法时,其他线程就不能使用这个同步方法,而有时涉及一些特殊情况:
当一个人在一个售票窗口排队买电影票,如果没有票了,就需要补充票的数量才能购买
当一个线程使用的同步方法中用到某个变量,而此变量又需要其他线程修改后才能符合本线程的需要,那么可以在同步方法中使用 wait() 方法

wait()方法:
中断方法的执行,使本线程等待,暂时让出 cpu 的使用权,并允许其他线程使用这个同步方法。
notify()方法:
唤醒由于使用这个同步方法而处于等待线程的 某一个结束等待
notifyall()方法:
唤醒所有由于使用这个同步方法而处于等待的线程结束等待

package testThread;
class Ticket{
	int count;
	int num=0;
	boolean avaliable=false;
	public Ticket(int count) {
		this.count = count;
	}
	public synchronized void sell(){
		if(!avaliable)//可以买票
			try {
				 wait();
			} catch (Exception e) {
				e.printStackTrace();
			}
		System.out.println("售出第["+num+"]张票");
		avaliable = false;
		notify();
	}
	public synchronized void put(){
		if(avaliable)//可以存票
			try {
				 wait();
			} catch (Exception e) {
				e.printStackTrace();
			}
		System.out.println("存入第["+(num++)+"]张票");
		avaliable = true;//
		notify();
	}
}
class Produce extends Thread{
	Ticket t=null;
	public Produce(Ticket t) {
		this.t = t;
	}
	@Override
	public void run() {
		while(t.num<=t.count)
			t.put();
	}
}
class Consumer extends Thread{
	Ticket t=null;
	public Consumer(Ticket t) {
		this.t = t;
	}
	@Override
	public void run() {
		while(t.num<=t.count)
			t.sell();
	}
}
public class WaitAndNotify{

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Ticket t = new Ticket(10);
		Produce p = new Produce(t);
		Consumer c = new Consumer(t);
		p.start();
		c.start();
	}

}