黑马程序员——Java 多线程

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------



Java 多线程

1.多线程概述

多线程概述

进程:

正在运行的程序,是系统进行资源分配和调用的独立单位。

每一个进程都有它自己的内存空间和系统资源。

线程:

是进程中的单个顺序控制流,是一条执行路径

一个进程如果只有一条执行路径,则称为单线程程序。

一个进程如果有多条执行路径,则称为多线程程序。

举例

扫雷游戏,迅雷下载等

 

多线程有什么意义?

       多线程的作用不是提高了执行速度,而是为了提高应用程序的使用率.而多线程却给了我们一个错觉:让我们觉得多个线程是并发执行的,其实不是.因为多个线程共享同一个进程的资源(堆内存和方法区),但是栈内存是独立的,一个线程一个栈.所以他们仍然是在抢CPU的资源执行,一个时间点上只有一个线程执行.而且谁抢到,这个不一定,所以造成了线程运行的随机性.

 

并发和并行?

       区别:并发是物理上同时发生,指在某一个时间点同时运行多个程序.

              并行是逻辑上同时发生,指在某一个时间内同时运行多个程序.

 

Java程序运行原理

java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。

思考:jvm虚拟机的启动是单线程的还是多线程的? 多线程(至少启动了垃圾回收线程和主线程).

 

2.多线程实现方案

两种方案:继承Thread类和实现Runnable接口

1继承Thread类, 重写 Thread 类的 run 方法,分配并启动该子类的实例.

public class MyThread extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.err.println(i);
		}
	}
}
public static void main(String[] args) {
	MyThread my = new MyThread();
	my.start();
}

2实现Runnable接口. 声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。

public class MyRunnableClass implements Runnable {
	public void run() {
		// 由于现实接口的方式不能直接使用Thread类了,但是可以间接使用.
		for (int i = 0; i < 100; i++) {
			System.err.println(Thread.currentThread().getName()+":"+i);
		}
	}
}
public static void main(String[] args) {
	MyRunnableClass target = new MyRunnableClass();
	Thread t = new Thread(target);
	Thread t2 = new Thread(target);
	t.start();
	t2.start();
}

在这个两种方案中都重写了run()方法,为什么呢? 因为不是类中所有的代码都需要被现实执行的.而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run方法来包含那些被线程执行的代码.

public class MyThread extends Thread{
	@Override
	public void run() {
		// 自己写代码
		// System.out.println("好好学习,天天向上");
		// 一般来说,被线程执行的代码肯定是比较耗时的。所以我们用循环改进
		for (int x = 0; x < 200; x++) {
			System.out.println(x);
		}
	}
}

Thread 类剖析   

Thread类也实现了Runnable接口,因此实现了接口中run方法.

当生产一个线程对象时,如果没有为其指定线程名称,那么线程对象的名字将使用如下形式:Thread-number,该number是自动增加的数字,并被所有的Thread对象所共享,因为它是一个static的成员变量.下面是从Thread类抽取的部分代码:

public class Thread implements Runnable {
	private char name[];
	private Runnable target;
	private static int threadInitNumber;
	private static synchronized int nextThreadNum() {
		return threadInitNumber++;
	}
	public Thread() {
		init(null, null, "Thread-" + nextThreadNum(), 0);
	}
	private void init(ThreadGroup g, Runnable target, String name,
			long stackSize) {
		// ...代码
		this.name = name.toCharArray();
		// ...代码
	}
}

当使用第一种方案(继承Thread的方式)来生成线程对象时,我们需要重写run()方法,因为Thread类的run()方法此时什么事情也不做。

当使用第二种方案(实现Runnable接口的方式)来生成线程对象时,我们需要实现Runnable接口的run()方法,然后使用new Thread(new MyRunnableClass())来生成线程对象(MyRunnableClass已经实现了Runnable接口),这时的线程对象的run()方法会调用MyRunnableClass的run()方法。该方案的好处:

1.可以避免由于Java单继承带来的局限性。

2.适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。

public class MyThread extends Thread{
	int number = 10;
	@Override
	public void run() {
		// 自己写代码
	}
}
public class MyThreadDemo {
	public static void main(String[] args) {
		//两个对象,两份成员变量
		MyThread my = new MyThread();
		MyThread my2 = new MyThread();
		my.start();
		my2.start();
	}
}

public class MyRunnableClass implements Runnable {
	int number = 10;
	public void run() {
		// 由于现实接口的方式不能直接使用Thread类了,但是可以间接使用.
		for (int i = 0; i < 100; i++) {
			System.err.println(Thread.currentThread().getName()+":"+i);
		}
	}
}
public class MyRunnableDemo {
	public static void main(String[] args) {
		//只有一份成员变量
		MyRunnableClass target = new MyRunnableClass();

		Thread t = new Thread(target,"张三");
		Thread t2 = new Thread(target,"李四");
		t.start();
		t2.start();
	}
}

run()和start()的区别?

run():仅仅是封装被线程执行的代码,直接调用是普通方法,

start():首先启动了线程,然后再由jvm去调用该线程的run()方法.


3.线程调度和线程控制

 

 

线程有两种调度模型:

1分时调度模型

     所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片;

2抢占式调度模型

     优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个了,优先级高的线程获取的 CPU 时间片相对多一些.

     Java使用的是抢占式调度模型.那么如何设置和获取线程优先级?

     public final void setPriority(int newPriority)      更改线程的优先级

     public final int getPriority()    返回线程的优先级,线程默认优先级 5.

public class ThreadPriority extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.err.println(getName()+":"+i);
		}
	}
}
public static void main(String[] args) {
		ThreadPriority tp1 = new ThreadPriority();
		ThreadPriority tp2 = new ThreadPriority();
		ThreadPriority tp3 = new ThreadPriority();
		//线程优先级 min=1 nom = 5 max = 10
		int priority = tp1.getPriority();
		System.err.println(priority);
		tp1.setPriority(10);//最高优先级
		tp3.setPriority(1);//最低优先级
		tp1.start();
		tp2.start();
		tp3.start();
}

线程控制

上面已经知道了线程的调度,接下来我们就可以使用如下方法对象线程进行控制.

线程休眠(暂停执行)

public static void sleep(long millis)

try {
	Thread.sleep(100);
	System.err.println(getName()+":"+new Date());
} catch (InterruptedException e) {
	e.printStackTrace();
}

线程加入, 等待该线程终止

public final void join()

public class ThreadJion extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.err.println(getName()+":"+new Date());
		}
	}
}
public static void main(String[] args) {
		ThreadJion tj1 = new ThreadJion();
		ThreadJion tj2 = new ThreadJion();
		ThreadJion tj3 = new ThreadJion();
		tj1.setName("李渊");
		tj2.setName("李世民");
		tj3.setName("李元霸");
		tj1.start();
		try {
			tj1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		// tj1执行完了之后,tj2,tj3才开始抢cpu资源
		tj2.start();
		tj3.start();
	}

线程礼让, 暂停当前正在执行的线程对象,并执行其他线程

public static void yield()

public class ThreadYield extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 30; i++) {
			System.err.println(getName()+":"+i);
		}
	}
}
public static void main(String[] args) {

	ThreadYield ty1 = new ThreadYield();
	ThreadYield ty2 = new ThreadYield();

	ty1.setName("张三");
	ty2.setName("张三老婆");

	ty1.start();
	Thread.yield();
	ty2.start();

}

public final void setDaemon(boolean on): 将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出.该方法必须在启动线程前调用。

public class ThreadDaemon extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			System.err.println(getName()+":"+i);
		}
	}
}
public static void main(String[] args) {
	ThreadDaemon td1 = new ThreadDaemon();
	ThreadDaemon td2 = new ThreadDaemon();
	td1.setName("关羽");
	td2.setName("张飞");
	// 设置守护线程,必须在线程启动之前调用
	td1.setDaemon(true);
	td2.setDaemon(true);
	// 线程
	td1.start();
	td2.start();
	// 设置main主线程的名称
	Thread.currentThread().setName("刘备");
	for (int i = 0; i < 5; i++) {
		System.err.println(Thread.currentThread().getName() + ":" + i);
	}
}

当线程”刘备”执行完成之后,线程”关羽”,”张飞”也随之结束.


中断线程

public final void stop()[已过时]

该方法具有固有的不安全性。用Thread.stop 来终止线程将释放它已经锁定的所有监视器.

public void interrupt():中断线程,如果当前线程没有中断它自己.

如果线程在调用Object 类的wait()、wait(long)或wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int)方法过程中受阻,则其中断状态将被清除,它还将收到一个InterruptedException.

public class ThreadStop extends Thread {
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.err.println("开始执行>>"+new Date());
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			System.err.println("线程终止了...");
		}
		System.err.println("结束执行<<"+new Date());
	}
}

stop方法终止线程:

public class ThreadStopDemo {
	@SuppressWarnings("deprecation")
	public static void main(String[] args) {
		ThreadStop ts = new ThreadStop();
		ts.start();
		// 超过3秒,就结束线程.
		try {
			Thread.sleep(3000);
			ts.stop();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

interrupt方法终止线程:

public class ThreadStopDemo2 {
	public static void main(String[] args) {
		ThreadStop ts = new ThreadStop();
		ts.start();
		// 超过3秒,就结束线程.
		try {
			Thread.sleep(3000);
			ts.interrupt();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

程序运行输出:

开始执行>>Tue Aug 25 17:35:12

线程终止了...

结束执行<<Tue Aug 25 17:35:15


4.线程生命周期



线程的生命周期可以分为四个状态:

1.创建状态:

  当用new操作符创建一个新的线程对象时,该线程处于创建状态。

  处于创建状态的线程只是一个空的线程对象,系统不为它分配资源。

2.可运行状态:

  执行线程的start()方法将为线程分配必须的系统资源,安排其运行,并调用线程体——run()方法,这样就使得该线程处于可运行状态(Runnable)。

  这一状态并不是运行中状态(Running),因为线程也许实际上并未真正运行。

3.不可运行状态:

  当发生下列事件时,处于运行状态的线程会转入到不可运行状态:

  调用了sleep()方法;

  线程调用wait()方法等待特定条件的满足;

  线程IO输入/输出阻塞。

  返回可运行状态;

  处于睡眠状态的线程在指定的时间过去后;

  如果线程在等待某一条件,另一个对象必须通过notify()或notifyAll()方法通知等待线程条件的改变;

  如果线程是因为输入输出阻塞,等待输入输出完成。

4.消亡状态:

  当线程的run()方法执行结束后,该线程自然消亡。

 

5.线程同步

线程同步:线程同步是解决线程安全问题的实现.

线程同步的前提

       1多个线程

       2多个线程使用的是同一个锁对象

线程同步的好处

       同步的出现解决了多线程的安全问题。

线程同步的弊端

       当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

 

同步代码块

格式:

       synchronized(对象){需要同步的代码;}

同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。


public class SaleTicket implements Runnable {
	private int tickets = 100; // 是多线程环境,有共享数据,有多条语句操作
	Object obj = new Object(); // 锁对象
	public void run() {
		while (true) {
			synchronized(obj){ //同步代码块,obj是所对象
				if (tickets > 0) {
					try {
						Thread.sleep(50);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.err.println(Thread.currentThread().getName() + " 正在卖  "	+ (tickets--) + " 张票.");
				}
			}
		}
	}
}

同步方法与同步方法的锁对象

就是把同步关键字加到方法上,锁对象就是this

public class SaleTicket implements Runnable{
	private int tickets = 100;
	private int x = 0;
	
	//同步代码块
	public void run() {
		while(tickets>0){
			if(x%2==0){
				synchronized(this){ // 锁对象
					if(tickets>0){
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.err.println(Thread.currentThread().getName()+"正在售"+(tickets--)+"张票..");
					}
				}
			}else{	
				sellTicket(); // 同步方法
			}
			x++;
		}
	}
	//同步方法的锁对象this
	private synchronized void sellTicket() {
		if(tickets>0){
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.err.println(Thread.currentThread().getName()+"正在售"+(tickets--)+"张票..");
		}
	}
}

静态同步方法:

public class SaleTicket implements Runnable{
	private static int tickets = 100;
	private int x = 0;
	
	//同步代码块
	public void run() {
		while(tickets>0){
			if(x%2==0){
				synchronized(SaleTicket.class){ // Class对象或类对象
					if(tickets>0){
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.err.println(Thread.currentThread().getName()+"正在售第"+(tickets--)+"张票..");
					}
				}
			}else{	
				sellTicket(); // 静态同步方法
			}
			x++;
		}
	}
	//static同步方法
	private static synchronized void sellTicket() {
		if(tickets>0){
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.err.println(Thread.currentThread().getName()+"正在售第"+(tickets--)+"张票..");
		}
	}
}

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock.

public interface Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作。

void lock() 获取锁

void unlock()释放锁

public class ReentrantLock  extends Object implements Lock, Serializable

一个可重入的互斥锁Lock,它具有与使用synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
public class SellTicket implements Runnable {
	private int tickets = 100;
	private Lock lock = new ReentrantLock();
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			try {
				lock.lock();
				if(tickets > 0){
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.err.println(Thread.currentThread().getName()+"正在售第"+(tickets--)+"张票.");
				}
			}finally{
				lock.unlock(); // 确保最终执行.
			}
		}
	}
}

6.死锁

死锁:是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象.

同步代码块的嵌套案例:

public class MyLock {
	//两把锁对象
	public static final Object lockA = new Object();
	public static final Object lockB = new Object();
}

两把锁对象

/*
 * 死锁
 */
public class DieLock extends Thread {
	private boolean flag;

	public DieLock(boolean flag) {
		this.flag = flag;
	}
	// 同步嵌套,相互等待
	@Override
	public void run() {
		if (flag) {
			synchronized (MyLock.lockA) {
				System.err.println("IF objA");
				synchronized (MyLock.lockB) {
					System.err.println("IF objB");
				}
			}
		} else {
			synchronized (MyLock.lockB) {
				System.err.println("else objB");
				synchronized (MyLock.lockA) {
					System.err.println("else objA");
				}
			}
		}
	}
}

测试结果:

/*
 * 同步弊端:
 * 	A:效率低,
 * 	B:容易产生死锁
 * 死锁:
 * 	是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象.
 * 举例 
 * 	生产者和消费者
 */
public class DieLockDemo {

	public static void main(String[] args) {
		DieLock dl1 = new DieLock(true);
		DieLock dl2 = new DieLock(false);
		dl1.start();
		dl2.start();
	}
}

运行结果:出现互相等待的结果。

else objB

IF objA


7.线程间通信

线程间通信:不同种类的线程针对同一个资源的操作.

举例: 通过设置线程(生产者)和获取线程(消费者)针对同一个学生对象进行操作.

/*
 * 通过设置线程(生产者)和获取线程(消费者)针对同一个学生对象进行操作.
 * 资源类:Student
 * 设置学生数据:SetThread(生产者)
 * 获取学生数据:GetThread(消费者)
 * 测试类:StudentDemo
 */

public class StudentDemo {
	public static void main(String[] args) {
		//创建资源
		Student s = new Student(); // 确保是同一个Student对象
		//设置和获取的类
		SetThread st = new SetThread(s);
		GetThread gt = new GetThread(s);
		// 线程类
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(gt);
		//启动线程
		t2.start();
		t1.start(); //null---27
	}
}

public class Student {
	String name ;
	int age;
}

public class SetThread implements Runnable {
	private Student s;
	public SetThread(Student s) {
		this.s = s;
	}
	@Override
	public void run() {
		while(true){//给出不同的值
			if(x % 2 == 0){
				s.name = "林青霞";
				s.age = 27;
			}else{
				s.name = "林正英";
				s.age = 55;
			}
			x++;
		}
	}
}
public class GetThread implements Runnable {
	private Student s;
	public GetThread(Student s) {
		this.s = s;
	}
	@Override
	public void run() {
		// Student s = new Student();//另一个对象,不是同一个对象
		while(true){
			System.err.println(s.name + "---" + s.age);
		}
	}
}

程序运行结果:问题: 同一个数据出现多次,姓名和年龄不匹配。

林青霞---55

林正英---55

林正英---27

林正英---55

林青霞---55

林正英---55

林青霞---27

林青霞---55

 

当出现线程安全问题时,必须满足三个条件:

A: 是否是多线程  是

B:是否存在共享数据  是

C:是否有多条语句操作共享数据 是

解决方式:1. 通过等待唤醒机制实现数据依次出现.

2.把同步代码块改进为同步方法实现.

设置数据类:

public class SetThread implements Runnable {
	private Student s;
	public SetThread(Student s) {
		this.s = s;
	}
	private int x = 0;
	@Override
	public void run() {
		while(true){
			synchronized (s) { //必须是同一把锁
				if(x % 2 == 0){
					s.name = "林青霞";
					s.age = 27;
				}else{
					s.name = "林正英";
					s.age = 55;
				}
				x++;
			}
		}
	}
}

获取数据类:

public class GetThread implements Runnable {
	private Student s;
	public GetThread(Student s) {
		this.s = s;
	}
	@Override
	public void run() {
		while(true){
			synchronized (s) { //必须是同一把锁
				System.err.println(s.name + "---" + s.age);
			}
		}
	}
}

运行结果: 这样的做法是数据太分散了,不是一个一个的一次出现.

林正英---55

林正英---55

林青霞---27

林青霞---27

林青霞---27

林青霞---27

林正英---55

林正英---55

林正英---55

 

这个线程安全是解决了,但是一样存在着问题:

1.    如果消费者先抢到CPU的执行权,就会去消费数据,但是现在的数据是默认值。没有意义。

2.    如果生产者先抢到CPU的执行权,就会产生数据,但是呢,它产生完数据后,还继续拥有执行权,它就继续产生数据。应该等待消费者把数据消费掉,然后再生产。

那就应该是:

A:生产者: 先看是否有数据,有就等待,没有就生产.
B:消费者 : 先看是否有数据,有就消费,没有就等待.

 

为了处理这类问题,java提供了一种机制:等待唤醒机制

/*
 * 等待唤醒机制:Object类提供
 * wait(),notify() 单个线程
 * notifyAll() 所有线程
 * 为什么这些方法定义在Object类中呢?
 ****这些方法的调用必须通过锁对象调用. 刚刚使用的锁对象是任意对象.
 */

public class StudentDemo {
	public static void main(String[] args) {
		//创建资源
		Student s = new Student(); 
		//设置和获取的类
		SetThread st = new SetThread(s);
		GetThread gt = new GetThread(s);
		// 线程类
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(gt);
		//启动线程 
		t1.start(); 
		t2.start();  
	}
}
public class Student {
	String name ;
	int age;
	boolean flag ;
}
public class SetThread implements Runnable {
	private Student s;
	public SetThread(Student s) {
		this.s = s;
	}
	private int x = 0;
	@Override
	public void run() {
		while(true){
			synchronized (s) { //必须是同一把锁
				// 判断有没有
				if(s.flag){ //true有就等待
					try {
						s.wait(); // 释放锁,t2就会抢到
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				if(x % 2 == 0){
					s.name = "林青霞";
					s.age = 27;
				}else{
					s.name = "林正英";
					s.age = 55;
				}
				x++;
				
				// 修改flag
				s.flag = true;
				s.notify(); // 唤醒--> t1 or t2,并不表示立马可以执行,还得抢CPU执行权
			}
		}
	}
}

public class GetThread implements Runnable {
	private Student s;
	public GetThread(Student s) {
		this.s = s;
	}
	@Override
	public void run() {
		while(true){
			synchronized (s) { //必须是同一把锁
				if(!s.flag){ // 没有就等待,默认!s.flag=true.等待
					try {
						s.wait(); // 释放锁,会在此处醒来
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.err.println(s.name + "---" + s.age);
				
				// 修改flag
				s.flag = false;
				s.notify(); // 唤醒 --> t1 or t2
			}
		}
	}
}

程序输出结果:

林青霞---27

林正英---55

林青霞---27

林正英---55

林青霞---27

林正英---55

 

生产者和消费者之等待唤醒机制代码优化:

/*
 * 主体
 */
public class Student {
	private String name;
	private int age;
	private boolean flag;

	public synchronized void set(String name, int age) {
		if (this.flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		// 设置数据
		this.name = name;
		this.age = age;
		// 修改flag
		this.flag = true;
		// 唤醒
		this.notify();
	}

	public synchronized void get() {
		// 如果没有数据就等待
		if (!this.flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		// 获取数据
		System.err.println(this.name + "---" + this.age);
		// 修改标记
		this.flag = false;
		// 唤醒
		this.notify();
	}
}

SetThread生产线程类:

public class SetThread implements Runnable {
	private Student s;
	public SetThread(Student s) {
		this.s = s;
	}
	private int x = 0;
	@Override
	public void run() {
		while (true) {
			if (x % 2 == 0) {
				s.set("林青霞", 27);
			} else {
				s.set("林正英", 55);
			}
			x++;
		}
	}
}

GetThread消费线程类:

public class GetThread implements Runnable {
	private Student s;
	public GetThread(Student s) {
		this.s = s;
	}
	@Override
	public void run() {
		while (true) {
			s.get(); // 数据源自己来做
		}
	}
}

测试Demo

/*
 * 优化后的代码:
 * 		私有化了成员变量,把设置和获取的操作封装并加上同步功能。
 * 		设置和获取的线程中只需要调用方法即可。
 */
public class StudentDemo {
	public static void main(String[] args) {
		//创建资源
		Student s = new Student(); 
		//设置和获取的类
		SetThread st = new SetThread(s);
		GetThread gt = new GetThread(s);
		// 线程类
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(gt);
		//启动线程 
		t1.start(); 
		t2.start();  
	}
}

程序1中的锁对象是student,程序2中的锁对象是this.


8.线程组和线程池

1. 线程组

Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。默认情况下,所有的线程都属于主线程组。

public final ThreadGroup getThreadGroup()

我们也可以给线程设置分组

Thread(ThreadGroup group,Runnable target, String name)

 

实例:

1 .获得线程所在的线程组

private static void method1() {
	// TODO Auto-generated method stub
	MyRunnable my =  new MyRunnable();
	Thread t1 = new Thread(my,"林青霞");
	Thread t2 = new Thread(my,"Louis");
	//获得线程组
	ThreadGroup tg1 = t1.getThreadGroup();
	ThreadGroup tg2 = t2.getThreadGroup();
	System.err.println(tg1.getName()); // 线程所属线程组名称 ,默认是 main
	System.err.println(tg2.getName()); // 线程所属线程组名称,默认是 main
	//当前线程的线程组也是main
	System.err.println(Thread.currentThread().getThreadGroup().getName());
	// 默认情况下,所有的都属于同一个线程组
	t1.start();
	t2.start();
}

2 .设置新的线程组和管理组中的线程

private static void method2() {
	ThreadGroup tg = new ThreadGroup("线程组ABC");
	MyRunnable my =  new MyRunnable();
	Thread t1 = new Thread(tg,my,"林青霞"); // 指定线程组
	Thread t2 = new Thread(tg,my,"Louis");  // 指定线程组
	ThreadGroup tg1 = t1.getThreadGroup();
	ThreadGroup tg2 = t2.getThreadGroup();
	System.err.println(tg1.getName()); // 线程所属线程组名称 
	System.err.println(tg2.getName()); // 线程所属线程组名称
	// 设置后台线程
	tg.setDaemon(true); // 改组中所有线程都是守护线程
	// 中断所有线程
	tg.interrupt();
	//销毁所有线程
	tg.destroy();
}

public class MyRunnable implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.err.println(i);
		}
	}
}
public class ThreadGroupDemo {

	public static void main(String[] args) {
		// 获得线程组
		method1();
		// 修改线程组 ,新建线程组
		method2();
	}
}

2.线程池

程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。

线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池.

 

JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法:

public static ExecutorServicenewCachedThreadPool()

public static ExecutorServicenewFixedThreadPool(int nThreads)

public static ExecutorServicenewSingleThreadExecutor()

这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法:

Future<?> submit(Runnable task)

<T> Future<T> submit(Callable<T>task)

 

示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * 线程池:
 * A:创建一个线程池,控制要创建几个线程对象。
 * 		public static ExecutorService newFixedThreadPool(int nThreads)
 * B:线程池的线程
 * 		可以执行Runnable对象或者Callable对象代表的线程
 * C:调用方法
 * 		Future<?> submit(Runnable task)
 * 		<T> Future<T> submit(Callable<T> task)
 * D:结束线程池
 * 		shutdown
 */
public class ExecutorsDemo {
	public static void main(String[] args) {
		//创建一个线程池对象,控制要创建几个线程对象
		ExecutorService pool = Executors.newFixedThreadPool(2);
		//可以执行Runnable对象或Callable对象代表的线程
		pool.submit(new MyRunnable());
		pool.submit(new MyRunnable());
		//关闭线程池
		pool.shutdown();
	}
}

线程池中的线程实现Runnable或Cellable:

public class MyRunnable implements Runnable {
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 100; i++) {
			System.err.println(Thread.currentThread().getName()+":"+i);
		}
	}
}



3.多线程实现第三种方式

实现Callable接口,

public interface Callable<V>

Callable接口包含一个不带任何参数的call方法。

Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。

import java.util.concurrent.Callable;
/*
 * public interface Callable<V> 线程的第三种实现方式
 */
public class MyCallable implements Callable<Object> {
	@Override
	public Object call() throws Exception {
		for (int i = 0; i < 100; i++) {
			System.err.println(Thread.currentThread().getName()+" : "+ i);
		}
		return null;
	}
}

CallableDemo类:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * 线程池:
 * A:创建一个线程池,控制要创建几个线程对象。
 * 		public static ExecutorService newFixedThreadPool(int nThreads)
 * B:线程池的线程
 * 		可以执行Runnable对象或者Callable对象代表的线程
 * C:调用方法
 * 		Future<?> submit(Runnable task)
 * 		<T> Future<T> submit(Callable<T> task) 
 * D:结束线程池
 * 		shutdown
 */
public class CallableDemo {
	public static void main(String[] args) {
		//创建一个线程池对象,控制要创建几个线程对象
		ExecutorService pool = Executors.newFixedThreadPool(2);
		//可以执行Runnable对象或Callable对象代表的线程
		pool.submit(new MyCallable()); // 依赖于线程池实现,不能单独实现有局限性。
		pool.submit(new MyCallable());
		//关闭线程池
		pool.shutdown();
	}
}

该方式依赖于线程池实现,不能单独实现有局限性。

4.匿名内部类方式使用多线程

/*
 * 匿名内部类实现多线程
 */
public class ThreadDemo {
	public static void main(String[] args) {
		
		// 1.
		new Thread() { // 一次性使用
			public void run() {
				for (int i = 0; i < 100; i++)
					System.err.println(Thread.currentThread().getName() + "--"+ i);
			};
		}.start();
		// 2.
		new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 100; i++)
					System.err.println(Thread.currentThread().getName() + "--"+ i);
			}
		}) {
			
		}.start();
		
		// 3. (run()){run()}
		new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 100; i++)
					System.err.println("hello --"+ i);
			}
		}) {
			@Override
			public void run() {
				for (int i = 0; i < 100; i++)
					System.err.println("world --"+ i); // 运行{run()方法}
			}
		}.start();

	}
}

在开发中会经常使用到匿名内部类方式实现多线程。

9.定时器的使用

 

定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能Timer

publicTimer()  创建一个新计时器。相关的线程不作为守护程序运行。

publicvoid schedule(TimerTask task, long delay)

安排在指定延迟后执行指定的任务。

参数

task - 所要安排的任务。

delay -执行任务前的延迟时间,单位是毫秒。

publicvoid schedule(TimerTask task,long delay,long period)

安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。以近似固定的时间间隔(由指定的周期分隔)进行后续执行。

参数

task - 所要安排的任务。

delay -执行任务前的延迟时间,单位是毫秒。

period- 执行各后续任务之间的时间间隔,单位是毫秒。

 

TimerTask

publicabstract class TimerTaskextends Object implements Runnable

由 Timer 安排为一次执行或重复执行的任务。

publicabstract void run()

此计时器任务要执行的操作。

指定者:接口 Runnable 中的 run。

publicboolean cancel()

取消此计时器任务。如果任务安排为一次执行且还未运行,或者尚未安排,则永远不会运行。

 

开发中使用的定时器框架:Quartz是一个完全由java编写的开源调度框架。

 

示例1:执行定时任务

 

/*
 * 定时器 : Timer 和  TimerTask定时器任务
 * Timer: 定时
 * TimerTask:任务
 */
public class TimerDemo {

	public static void main(String[] args) {
		// 创建定时器对象
		Timer t = new Timer();
		// 1秒后执行任务
		// t.schedule(new MyTask(), 1000);

		t.schedule(new MyTask(t), 1000);
	}
}

class MyTask extends TimerTask {
	private Timer t;

	public MyTask(Timer t) {
		this.t = t;
	}

	@Override
	public void run() {
		System.err.println("崩,爆炸了..");
		t.cancel(); // 任务执行完之后,关闭定时器
	}
}

示例2:周期性执行定时任务

publicvoid schedule(TimerTask task,long delay,long period)

delay -执行任务前的延迟时间,单位是毫秒。

period - 执行各后续任务之间的时间间隔,单位是毫秒。

import java.util.Timer;
import java.util.TimerTask;

/*
 * 定时器 : Timer 和  TimerTask定时器任务
 * Timer: 定时
 * TimerTask:任务
 * 连续任务
 * public void schedule(TimerTask task,long delay,long period)
 * delay:延迟
 * period:周期
 */
public class TimerDemo {

	public static void main(String[] args) {
		// 创建定时器对象
		Timer t = new Timer();
		// 1秒后执行,3秒后继续执行相同的动作
		t.schedule(new MyTask(t), 1000, 3000);
	}
}
class MyTask extends TimerTask {
	private Timer t;

	public MyTask(Timer t) {
		this.t = t;
	}
	@Override
	public void run() {
		System.err.println("崩,爆炸了..");
		// t.cancel();
	}
}


10.多线程状态图

 

1.线程的生命周期





2.多线程同步状态




常见的情况:

1.     新建——就绪——运行——死亡

2.    新建——就绪——运行——就绪——运行——死亡

3.    新建——就绪——运行——其他阻塞——就绪——运行——死亡

4.    新建——就绪——运行——同步阻塞——就绪——运行——死亡

5.    新建——就绪——运行——等待阻塞——同步阻塞——就绪——运行——死亡

 

 

总结:

(1)多线程:一个应用程序有多条执行路径

       进程:正在执行的应用程序

       线程:进程的执行单元,执行路径

       单线程:一个应用程序只有一条执行路径

       多线程:一个应用程序有多条执行路径

      

       多进程的意义?

              提高CPU的使用率

       多线程的意义?

              提高应用程序的使用率

(2)Java程序的运行原理及JVM的启动是多线程的吗?

       A:Java命令去启动JVM,JVM会启动一个进程,该进程会启动一个主线程。

       B:JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。

(3)多线程的实现方案

       A:继承Thread类

       B:实现Runnable接口

(4)线程的调度和优先级问题

       A:线程的调度

              a:分时调度

              b:抢占式调度 (Java采用的是该调度方式)

       B:获取和设置线程优先级

              a:默认是5

              b:范围是1-10

(5)线程的控制(常见方法)

       A:休眠线程

       B:加入线程

       C:礼让线程

       D:后台线程

       E:终止线程

(6)线程的生命周期

       A:新建

       B:就绪

       C:运行

       D:阻塞

       E:死亡

(7)电影院卖票程序的实现

       A:继承Thread类

       B:实现Runnable接口

(8)电影院卖票程序出问题

       A:为了更符合真实的场景,加入了休眠100毫秒。

       B:卖票问题

              a:同票多次

              b:负数票

(9)多线程安全问题的原因(也是我们以后判断一个程序是否有线程安全问题的依据)

       A:是否有多线程环境

       B:是否有共享数据

       C:是否有多条语句操作共享数据

(10)同步解决线程安全问题

       A:同步代码块

              synchronized(对象){

                     需要被同步的代码;

              }

             

              这里的锁对象可以是任意对象。

       B:同步方法

              把同步加在方法上。

             

              这里的锁对象是this

             

       C:静态同步方法

              把同步加在方法上。

             

              这里的锁对象是当前类的字节码文件对象(反射再讲字节码文件对象)

(11)回顾以前的线程安全的类

       A:StringBuffer

       B:Vector

       C:Hashtable

       D:如何把一个线程不安全的集合类变成一个线程安全的集合类

              用Collections工具类的方法即可。

(12)JDK5以后的针对线程的锁定操作和释放操作

       Lock锁

(13)死锁问题的描述和代码体现

(14)生产者和消费者多线程体现(线程间通信问题)

       以学生作为资源来实现的

       资源类:Student

       设置数据类:SetThread(生产者)

       获取数据类:GetThread(消费者)

       测试类:StudentDemo

       代码:

              A:最基本的版本,只有一个数据。

              B:改进版本,给出了不同的数据,并加入了同步机制

              C:等待唤醒机制改进该程序,让数据能够实现依次的出现

                     wait()

                     notify()

                     notifyAll() (多生产多消费)

              D:等待唤醒机制的代码优化。把数据及操作都写在了资源类中

(15)线程组

(16)线程池

(17)多线程实现的第三种方案

(18)多线程的面试题

1.多线程有几种实现方案,分别是哪几种?

两种,继承Thread类和实现Runnable接口

扩展一种,实现Callable接口.这个方式得和线程池结合.

2.同步有几种方式,分别是什么?

两种.同步代码块和同步方法.

3.启动一个线程是run()还是start()?他们的区别?

start()

run():封装了被线程执行的代码,直接调用仅和普通方法的调用一样.

start():启动线程,并由JVM自动调用run()方法.

4.sleep()和wait()方法的区别?

sleep():必须指定时间;不释放锁.

wait():可以指定时间,也可以不指定时间;释放锁.

5.为什么wait(),notify(),notifyAll()等方法都是定义在Object类中?

因为这些方法的调用都是依赖于锁对象的,而同步代码块的锁对象是任意锁,而Object代表任意对象,所以,定义在Object类中才合理.

6.线程生命周期

新建——就绪——运行——死亡

新建——就绪——运行——就绪——运行——死亡

新建——就绪——运行——其他阻塞——就绪——运行——死亡

新建——就绪——运行——同步阻塞——就绪——运行——死亡

新建——就绪——运行——等待阻塞——同步阻塞——就绪——运行——死亡

 
















------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值