13.JAVA中的线程(内含练习与代码解析)

多线程

 

作用:通过多并发来提高运行速率

并发:两个或多个事件在同一个时间段内发生

并行:两个或多个时间在同一时刻发生(同时发生)

线程与进程的理解

程序

由开发人员编写代码程序,是“死的”

进程

多个线程组成的,是“活的”,运行状态;线程就好比车间里的工人,一个进程可以包括多个线程;

线程

接收信息的线程、发送信息的线程;是进程中的一个执行单元;一个进程至少有一个线程;

关键点

1.由于cpu的处理能力非常强大,在人的视觉、感官来说,是觉得在同一时间上运行的(cpu会不定时让出时间片,给线程进行强占,不断的切换线程,执行不同的任务)

2.进程之间是不能共享同一个内存空间,每个进程都有他独自的内存空间;

3.实现数据共享是在线程之间实现的

主线程

任何一个JAVA程序启动时,一个线程立刻运行,它执行Main方法,这个线程称为程序的主线程.也就是说,任何程序都至少有一个线程,即主线程.

主线程的特殊之处在于:它是产生其它线程子线程的线程;通常它必须最后结束.因为它要执行其它子线程的关闭工作.

线程调度

分时调度

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

抢占式调度

优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

线程的创建

方式一:通过继承Thread类实现

简单,方便使用、容易获取线程的信息

构造方法:
public Thread():分配一个新的线程对象。
public Thread(String name):分配一个指定名字的新的线程对象。
public Thread(Runnable target):分配一个带有指定目标新的线程对象。
public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。
常用方法:
public String getName():获取当前线程名称。
public void start():导致此线程开始执行; Java虚拟机调用此线程的run方法。
public void run():此线程要执行的任务在此处定义代码。
public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
public static Thread currentThread():返回对当前正在执行的线程对象的引用。
 

run与start的区别;

设置线程名字以及调用方法;

public class ThreadMain {

	public static void main(String[] args) {
		// 1\创建线程类的对象
		MyThread thread = new MyThread("子线程");
		// 启动线程
		// thread.setName("子线程");
		thread.start();
		// thread.run();//错误!
		for(int i=1;i<=10;i++) {
			//
			String name = Thread.currentThread().getName();
			System.out.println(name+":"+i);
		}
		
	}

}
//第一种\创建线程的方式\通过继承线程类
public class MyThread extends Thread{	
	//
	public MyThread() {
		// TODO Auto-generated constructor stub
	}
	public MyThread(String name) {
		super(name);
	}

	//重写run方法,指定当前线程要完成的任务
	@Override
	public void run() {
		for(int i=1;i<=10;i++) {
			//
			String name = Thread.currentThread().getName();
			System.out.println(name+":"+i);
		}
	}

}

方式二:通过自定义类实现runnable接口,定义一个可重复利用的任务类

自定义任务,任务可重复利用、方便线程间共享数据;也可以使用匿名内部类的形式,用于执行一次性的任务操作

//第二种方式   定义一个任务类   
public class MyRun implements Runnable {

	@Override
	public void run() {
		for (int i = 1; i <= 10; i++) {
			//
			String name = Thread.currentThread().getName();
			System.out.println(name + ":" + i);
		}
	}

}
 

重复使用

public class RunnableMain {
	public static void main(String[] args) {
		 //创建一个任务类的对象
		 MyRun task = new MyRun();
		 // 使用线程类创建一个线程对象,提供一个任务对象
		 Thread thread = new Thread(task,"run线程");
		 System.out.println(thread.getId());
		 // 启动线程
		 thread.start();

	}
}

单次使用--匿名内部类

public class RunnableMain {
	public static void main(String[] args) {//使用匿名内部类来提供一次性的任务类对象
		Thread thread = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("去xxx地点拿个东西!");
			}
		},"机械人线程");
		thread.start();}

Thread   Runnable区别

Thread 比较难进行资源共享   方便调用API 以及 使用线程【简易】

Runnable用于   共享同一个资源 【同一个任务】   【任务和线程可以分离、可以复用】【解耦】

练习

分别使用Thread和Runnable实现火车票抢票操作:【100】(不同步的抢票案例)

两个子线程:

智行

携程

要求输出:xxx抢到了第x张票

Runnable实现

//抢票的线程任务类--Runnable实现
public class TicketTask implements Runnable {

	// 可以售卖的火车票数
	int nums;

	public TicketTask() {

	}

	public TicketTask(int nums) {
		super();
		this.nums = nums;
	}

	// 抢票
	@Override
	public void run() {
		// 循环抢票
		while (nums > 0) {
			//
			String name = Thread.currentThread().getName();
			//
			System.out.println(name + "抢到了第" + nums + "张票");
			//
			nums--;
		}
	}

}
	public static void main(String[] args) {
		// 创建两个线程进行抢票操作
		//
		TicketTask task = new TicketTask(100);
		//
		Thread xiecheng = new Thread(task,"携程");
		Thread zhixing = new Thread(task,"智行");
		//
		xiecheng.start();
		zhixing.start();
	}

Thread实现--定义同一个引用对象是关键

public class Ticket {
	int nums = 100;
}
public class TicketThread extends Thread {

	//
	static int nums=100;
	
	//Ticket ticket
	Ticket ticket;
	
	//定义同一个引用对象  ---  nums
	public TicketThread() {
	}

	public TicketThread(Ticket ticket,String name) {
		super(name);
		this.ticket = ticket;
	}

	// 抢票
	@Override
	public void run() {
		// 循环抢票
		while (nums > 0) {
			//
			String name = Thread.currentThread().getName();
			//
			System.out.println(name + "抢到了第" + ticket.nums + "张票");
			//
			ticket.nums--;
		}
	}
}
//测试
public static void main(String[] args) {
		// 创建两个线程进行抢票操作
		Ticket ticket = new Ticket();
		//
		TicketThread xiecheng = new TicketThread(ticket,"携程");
		TicketThread zhixing = new TicketThread(ticket,"智行");
		//
		xiecheng.start();
		zhixing.start();
	}

方式三:通过自定义类实现Callable接口,定义一个可返回数据、可以抛出异常的执行操作

1.实现Callable接口、注意指定返回值类型

--可返回结果,前面两种方式都不行;并且可以抛出异常,而runnable只能try catch

2.使用FutureTask封装Callable接口的对象

3.使用Thread类来注入FutureTask对象并创建线程

4.线程的结果可以通过FutureTask对象的get方法获取--可以获取返回值是与runnable不同之一

import java.util.concurrent.Callable;

//第三种方式  : 实现Callable接口的任务  返回结果的
public class MyCall implements Callable<Integer> {
	@Override
	public Integer call() throws Exception {
		int i = 1;
		for (; i <= 20; i++) {
			String name = Thread.currentThread().getName();
			System.out.println(name + ":" + i);
		}
		if(i>20) {
			throw new Exception("超过数值范围");
		}
		//返回结果
		return i;
	}

}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableMain {

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// call
		MyCall call = new MyCall();
		// 通过call对象构建一个任务类的对象
		FutureTask task = new FutureTask<>(call);
		// 创建线程
		Thread thread = new Thread(task,"call线程");//与runnable相似,但这种方法可以获取返回值
		//
		thread.start();
		//  获取task任务的结果
		System.out.println(task.get());
	}

}

三种创建方式区别与应用

Thread方式

(1)优点

使用上:简单易用,代码量较少。

访问线程的信息:比较方便(线程名字,线程ID)。

(2)缺点

耦合度:线程对象与任务的逻辑直接耦合(发生捆绑),不利用任务的复用

继承与扩展:因为线程类已经继承了Thread,所以无法再通过继承的方法来复用另一个类。

数据共享:在多个线程中,不易实现数据共享

Runnable方式

(1)优点

耦合度:任务与线程执行对象分离,轻松实现任务的复用。

继承与扩展:由于使用了接口,可以继承别的类,复用这个类的数据。数据共享:在多个线程中,直接可实现数据共享

(2)缺点

使用上:相对较难,代码量较多

访问线程的信息:没Thread方便(线程名字,线程ID)。

通常情况都使用Runnable方式,可以重复利用任务类对象、可以在线程间共享数据,并且可以继承其他类

当需要在线程中返回数据时,可以使用Callable方式

线程的生命周期

新建

创建了线程的对象,分配了内存空间

就绪

启动了线程,进入就绪状态,等待cpu的调用

运行

线程在运行中,执行run进行操作

阻塞

睡眠、等待、“假死状态”

 

Thread.sleep(); //引起当前线程阻塞
th1.join;//引起当前线程阻塞
th1.interrupt();//引起th1线程阻塞
th1.suspend();//引起th1线程阻塞
 

死亡

线程结束、出现异常

什么情况会导致线程死亡

(1)执行体执行完。

(2)线程发生异常,无法处理。

(3)调用该线程的stop()方法。

线程的控制

睡眠(延迟)

--Thread.sleep(1000);//ms毫秒;

--TimeUnit.SECONDS.sleep(1);

import java.util.concurrent.TimeUnit;
//睡眠
public class SleepMaiin {

	//睡眠会造成线程阻塞,阻塞结束之后进入就绪状态,等待cpu的调度
	public static void main(String[] args) throws InterruptedException {
		// 线程的睡眠
		for (int i = 1; i <= 10; i++) {
			System.out.println(i);
			// 睡眠
			// Thread.sleep(1000);
			//时间单位
			TimeUnit.SECONDS.sleep(1);
		}
	}
}
 

合并

让当前线程进入阻塞状态,直到调用join方法的线程完成之后,再回到当前线程操作

案例

//线程的合并
public class JoinMain {

	public static void main(String[] args) throws InterruptedException {
		// 场景:有一个和尚去打水,另一个和尚等水做饭
		// 1、创建一个线程,让一个和尚去打水
		heshang he = new heshang();
		he.start();
		// 合并挑水线程,让挑水完成之后再做饭
		// he.join();
		he.join(2000);
		//
		while (!he.isok) {
			System.out.println("预想成功,必先自工--练功--天外飞仙!");
			Thread.sleep(2000);
		}
		// 2、和尚下水做饭
		System.out.println("另一个和尚下水烧饭。");
	}

}

// 定义一个线程类
class heshang extends Thread {

	// 标志位--挑水是否成功(考虑到这个和尚不挑水回来的情况)
	boolean isok = false;

	// 打水过程
	@Override
	public void run() {
		System.out.println("和尚去挑水。。。。");
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("和尚挑水成功,已经回来了。");
		isok = true;
	}

}
 

让步

使得当前线程让出cpu,再回到就绪状态,和其他线程一起再去抢夺资源

线程优先级:1-10

//线程的让步
public class YeildThread extends Thread {
	
	//步数-计数
	int count=0;

	public YeildThread() {

	}

	public YeildThread(String name) {
		super(name);
	}
	
	//让步线程    不让步线程
	@Override
	public void run() {
		//
		String name = Thread.currentThread().getName();
		//当时当前是让步线程并且count整除100的时候,执行让步
		while(true) {
			System.out.printf("【%s】现在执行第%d步\n",name,count);
			//判断
			if(!name.equals("不让步线程")&&count%100==0) {
				System.out.printf("【%s】现在执行第%d步,执行让步,回到就绪状态!\n",name,count);
				this.yield();
				System.out.println("让步线程--------");
			}
			//自增
			count++;
			//延迟
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
}
public class YeildMain {

	public static void main(String[] args) {
		// 创建 让步线程  和不让步线程    优先级  1~10
		YeildThread rang = new YeildThread("让步线程1");
		YeildThread rang1 = new YeildThread("让步线程2");
		YeildThread rang2 = new YeildThread("让步线程3");
		rang.setPriority(1);
		rang1.setPriority(1);
		rang2.setPriority(1);
		YeildThread burang = new YeildThread("不让步线程");
		burang.setPriority(10);
		//
		rang.start();
		rang1.start();
		rang2.start();
		burang.start();
	}

}
 

挂起、唤醒

suspend挂起方法过时不推荐使用(偏死锁、容易造成死锁,所以不推荐使用)

resume唤醒方法过时不推荐使用

//线程的挂起与唤醒
//老师上课点名的线程
public class SupendThread extends Thread {

	// 老师给学生点名,中途,人有三急,先去厕所解决
	@Override
	public void run() {
		for (int i = 1; i <= 60; i++) {
			System.out.println("老师点名到第" + i + "位同学!");
			// 延时
			try {
				this.sleep(150);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
public class SupendMain {

	// 老师给学生点名,中途,人有三急,先去厕所解决
	public static void main(String[] args) throws InterruptedException {
		// 创建点名线程
		SupendThread thread = new SupendThread();
		// 启动点名线程
		thread.start();
		// 延时,模拟老师点名了一段时间
		Thread.sleep(2500);
		System.out.println("老师人有三急,先去厕所解决");
		// 突然来劲了
		thread.suspend();// 挂起点名线程
		// 模拟时间过程
		for (int i = 1; i <= 3; i++) {
			System.out.println("老师正在放大中。。。。。");
			Thread.sleep(1000);
		}
		// 完事,唤醒点名线程
		System.out.println("老师回来了,继续点名");
		thread.resume();
	}

}

中断

调用interrupt()会打断一下线程,由于受到sleep、wait或者 join方法的影响,程序会抛出InterruptedException程序继续往下执行。

//线程的中断
public class InterrupThread extends Thread {

	// 大雄打球的线程
	@Override
	public void run() {
		System.out.println("大雄正在篮球场打球。。。。");
		//  在sleep中,被打断,会出现InterruptedException
		try {
			this.sleep(10000);
		} catch (InterruptedException e) {
			System.out.println("大雄被赶走了。。。。");
			return;//捕捉并在这加return 直接停止进程,不会再往下执行
		}
		//
		System.out.println("大雄在篮球场继续打球。。。。");
		try {
			this.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("程序结束");
	}
}
public class InterrupMain {

	public static void main(String[] args) throws InterruptedException {
		// 大雄正在打球,胖虎来打断
		InterrupThread thread = new InterrupThread();
		thread.start();
		Thread.sleep(1000);
		System.out.println("胖虎来了,打断大雄。。。。");
		//
		thread.interrupt();
		//
		Thread.sleep(3000);
		System.out.println("胖虎觉得没意思,走了。。。。");
	}

}

停止

调用stop()会结束线程,终止线程的生命周期

//线程的结束
public class StopThread extends Thread {
	//
	boolean isok = true;
	// 大雄打球的线程
	@Override
	public void run() {
		while (isok) {
			System.out.println("大雄正在篮球场打球。。。。");
			try {
				this.sleep(1000);
			} catch (InterruptedException e) {
//				e.printStackTrace();
				System.out.println("大雄被赶走了。。。。");
				return;
			}
		}
	}

}
public class StopMain {

	public static void main(String[] args) throws InterruptedException {
		// 大雄正在打球,胖虎来打断
		StopThread thread = new StopThread();
		thread.start();
		Thread.sleep(2200);
		System.out.println("胖虎来了,打断大雄。。。。");
		//结束线程
		thread.stop();
		//
		Thread.sleep(3000);
		System.out.println("胖虎觉得没意思,走了。。。。");
	}

}

后台线程(守护线程)

主要做一些幕后工作,比如jvm的垃圾回收机制;后台线程和普通线程的创建方式是一样的,但是要使用thread.setDaemon(true);进行设置

注意:后台线程的生命周期主线程的生命周期是一致的、绑定的;当主线程死亡,则后台线程也跟着死亡;

//后台线程
public class GradedThread extends Thread {

	@Override
	public void run() {
		while(true) {
			System.out.println("检查系统性能,把操作命令归录到日志中。。。。。");
			//
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
}
 
public class GradedMain {

	public static void main(String[] args) throws InterruptedException {
		// 
		GradedThread hou  = new GradedThread();
		// 设置为后台线程      后台线程是和主线程绑定的  
		hou.setDaemon(true);
		hou.start();
		//
		Thread.sleep(5000);
		//
		System.out.println("main程序结束");
	}

}

线程的同步(要确保对象锁是一致的)

同步概念

确保代码执行的有序性、事务执行的完整性,数据共享的可靠性和正确性;在多线程并发的情况下,使得在同一个时间点只能有一个线程访问同步方法或同步代码块;

互斥概念

如果一个线程正在执行某个临界区的代码,就不允许其它线程插手进来。

临界区概念

为了线程安全,我们保障以下二条基础语句要得到完整的执行,方可释放cpu的资源,给别的线程参与执行相同代码;一旦进入临界区,在同一时间只有一个线程能够进入该区域;

whie(ticket>0){//设定一个保护范围,这一个保护范围即为临界区;
delay();
ticket--;}
 

同步方法

使用this作为当前同步方法的同步锁,只有获得此对象锁的线程才能执行同步方法中的操作;this是调用当前方法的对象的引用

同步方法实现的的同步抢票案例

//抢票的线程任务类
public class TicketTask implements Runnable {

	// 可以售卖的火车票数
	int nums;
	//
	boolean isrun = true;

	public TicketTask() {
	}
	public TicketTask(int nums) {
		super();
		this.nums = nums;
	}

	// 定义一个同步方法,确保在同一个时间片刻只有一个线程进来操作
	// 同步修饰的关键字synchronized  【同步方法】   同步锁【对象锁】
	public synchronized void saleTicket() {
		if (nums > 0) {
			//
			String name = Thread.currentThread().getName();
			//
			System.out.println(name + "抢到了第" + nums + "张票");
			//
			nums--;
		} else {
			// 停止抢票
			isrun = false;
		}
	}
}
public class TicketMain {
	//线程同步:同步锁要唯一,一致。多线程下资源共享时,资源要一致。
	public static void main(String[] args) {
		TicketTask task= new TicketTask(100);
		//
		Thread xiecheng = new Thread(task1, "携程");
		Thread zhixing = new Thread(task1, "智行");
		Thread feizhu = new Thread(task1, "飞猪");
		Thread jingdong = new Thread(task1, "京东");
		//
		xiecheng.start();
		zhixing.start();
		feizhu.start();
		jingdong.start();
	}
}

同步代码块

可以灵活定义同步修饰的代码区域,定义同步锁,也能实现同步代码块的嵌套

同步代码块实现的的同步抢票案例

//抢票的线程任务类
public class TicketTask implements Runnable {
	// 可以售卖的火车票数
	int nums;
	//
	boolean isrun = true;
	public TicketTask() {
	}
	public TicketTask(int nums) {
		super();
		this.nums = nums;
	}
	// 抢票
	@Override
	public void run() {
		// 循环抢票
		while (isrun) {
			// 售票
			//	saleTicket();	
			//同步代码块    使用同步锁  【对象锁】
			synchronized ("a") {
				if (nums > 0) {
					//
					String name = Thread.currentThread().getName();
					//
					System.out.println(name + "抢到了第" + nums + "张票");
					//
					nums--;
				} else {
					// 停止抢票
					isrun = false;
				}
			}
			// 延迟
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
public class TicketMain {
	//线程同步:同步锁要唯一,一致。多线程下资源共享时,资源要一致。
	public static void main(String[] args) {
		TicketTask task1= new TicketTask(100);
		//
		Thread xiecheng = new Thread(task1, "携程");
		Thread zhixing = new Thread(task1, "智行");
		Thread feizhu = new Thread(task1, "飞猪");
		Thread jingdong = new Thread(task1, "京东");
		//
		xiecheng.start();
		zhixing.start();
		feizhu.start();
		jingdong.start();
	}
}

练习

定义一个取款线程,创建两个线程取款800【同一个账户】

//账户类
public class Account {

	// 账户id
	String id;
	// 余额
	double wealthy;

	public Account(String id, double wealthy) {
		super();
		this.id = id;
		this.wealthy = wealthy;
	}

	public Account() {
		super();
	}

	@Override
	public String toString() {
		return "Account [id=" + id + ", wealthy=" + wealthy + "]";
	}

	public synchronized void drawMoney(double drawMoney) {//同步方法   对象锁 this
		// 1、获取线程名称 
		String name = Thread.currentThread().getName();
		// 2、判断余额是否足够
		if (drawMoney > wealthy) {
			System.out.println(name + "取款" + drawMoney + "钱,当前账户有" + wealthy + ",余额不足!");
		} else {// 足够
			wealthy = wealthy - drawMoney;
			System.out.println(name + "成功取款" + drawMoney + "钱,当前账户剩余有" + wealthy);
		}
		
	}
	
}
public class DrawThread extends Thread {

	// 账号
	Account account;
	// 取款金额
	double drawMoney;

	public DrawThread(Account account, double drawMoney, String name) {
		super(name);
		this.account = account;
		this.drawMoney = drawMoney;
	}

	public DrawThread() {
		super();
	}

	@Override
	public void run() {
		//
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//
		account.drawMoney(drawMoney);//方法二:同步方法
        
		//方法一:同步代码块
//		synchronized ("a") {
//			//1、获取线程名称
//			String name = this.getName();
//			//2、判断余额是否足够
//			if(drawMoney>account.wealthy) {
//				System.out.println(name+"取款"+drawMoney+"钱,当前账户有"+account.wealthy+",余额不足!");
//			}else {//足够
//				account.wealthy=account.wealthy-drawMoney;
//				System.out.println(name+"成功取款"+drawMoney+"钱,当前账户剩余有"+account.wealthy);
//			}
//		}
	}

}
public class DrawMain {
	public static void main(String[] args) {
		//
		Account account = new Account("aa002", 1000);
		// 
		DrawThread jia = new DrawThread(account, 800, "甲");
		DrawThread yi = new DrawThread(account, 800, "乙");
		//
		jia.start();
		yi.start();
	}

}

同步锁释放的时机

1、正常执行完同步代码

2、遇到return、break结合标签时

3、抛出异常、出现错误

4、当线程进行等待时,调用了wait()方法

案例

两个线程抢CAR,做不同的操作

public class LockThread1 extends Thread {
	@Override
	public void run() {
		//
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//
		// 同步代码块
		synchronized ("car") {
			// 1、获取线程名称
			String name = this.getName();
			System.out.println(name + "抢到了对象锁,执行泡妞任务");
			//
			try {
				this.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(name + "抢到了对象锁,执行泡妞任务");
			try {
				this.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			 return;//1.遇到return 释放
//			try {//2.抛出异常后释放
//				throw new Exception();
//			} catch (Exception e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			}
			// 中途
			System.out.println("中途遇到问题,被甩");

		}
	}
}
import javax.swing.plaf.synth.SynthSpinnerUI;
public class LockThread2 extends Thread {

	@Override
	public void run() {
		//
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//
		//同步代码块
		synchronized ("car") {
			//1、获取线程名称
			String name = this.getName();
			System.out.println(name+"抢到了汽车,执行买菜");
			//
			try {
				this.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(name+"抢到了汽车,执行买菜");
		}
	}
}
public class LockThreadMain {

	public static void main(String[] args) throws InterruptedException {
		// 
		LockThread1 thread1 = new LockThread1();
		thread1.start();
		Thread.sleep(1000);
		LockThread2 thread2 = new LockThread2();
		thread2.start();
	}
}

同步方法和同步代码块的区别

线程面试题

1.笔试题:对比线程的挂起suspend()、线程中断interrupt()以及线程等待wait()

2.sleep和wait方法的区别?

  • sleep():必须传入睡眠的时间毫秒值,和毫秒值加纳秒值
  • wait():可以不用指定时间,如果指定时间代表线程不会立马等待,而是指定时间过后再等待
  • sleep():在休眠指定时间后自动醒来,并且休眠时间不释放锁
  • wait():等待过程中不会自动醒来,而是调用notify()方法来唤醒,并且调用时里面释放锁
  • wait()方法必须是锁对象来调用,而且必须是在同步代码块中执行,否则会出现IllegalMonitorStateException异常

3.笔试题:suspend、interrupt、wait方法的区别

ReentrantLock可重入锁

可重入锁特点

(A) 上锁次数

[1] lock.lock() 可以上锁多次 [可重入]

[2] 你想完全解锁, 必须解够上锁的次数。

解锁次数 == 上锁次数

(B) 上锁与解锁

[1] 你可以在任意位置上锁, 也可以任意位置解锁。

[2] 但是 上锁与解锁的线程必须保证是同一个线程, 否则, 会发生线程处理状态异常。

 注意事项

[1] 防范异常的发生, 发现有如下的问题, 因为发生一个异常, 导致锁无法释放。

应该采取某个策略来防止类似的事情发生。

引入异常处理机制 try{ } catch(){ }finally{ }

将 lock.unlock(); 放入 finally 中

[2] 为了防止 Lock 引用被修改, 请将 Lock 定为 final 最终变量

[3] 可以嵌套上锁,可重入,上锁次数和解锁次数要对应

[4] 确保上锁时,都是在同一个线程中

区别(与同步方法/代码块)

1.同步方法-同步代码块是自动释放的   

ReentrantLock手动操作的【灵活】  

2.ReentrantLock【公平锁、非公平锁(默认)】//公平锁是根据排队时间来解锁;

案例

1.抢票--上锁与解锁

import java.util.concurrent.locks.ReentrantLock;

//抢票的线程任务类
public class TicketTask implements Runnable {

	//
	ReentrantLock lock = new ReentrantLock();

	// 可以售卖的火车票数
	int nums;
	//
	boolean isrun = true;

	public TicketTask() {

	}

	public TicketTask(int nums) {
		super();
		this.nums = nums;
	}

	// 抢票
	@Override
	public void run() {
		// 循环抢票
		while (isrun) {
			// 售票
			// saleTicket();
			// 同步代码块 使用同步锁 【对象锁】
			lock.lock();// 上锁
			if (nums > 0) {
				//
				String name = Thread.currentThread().getName();
				//
				System.out.println(name + "抢到了第" + nums + "张票");
				//
				nums--;
			} else {
				// 停止抢票
				isrun = false;
			}
			lock.unlock();// 解锁
			// 延迟
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
public class TicketMain {
	public static void main(String[] args) {
		TicketTask task1 = new TicketTask(100);
		Thread xiecheng = new Thread(task1, "携程");
		Thread zhixing = new Thread(task1, "智行");
		Thread feizhu = new Thread(task1, "飞猪");
		Thread jingdong = new Thread(task1, "京东");
		//
		xiecheng.start();
		zhixing.start();
		feizhu.start();
		jingdong.start();
	}
}

2.ReentrantLock的使用

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest {

	//注意:最好在trycatch中finally中对lock进行解锁,避免出现异常时这个锁没有释放
	//使用 ReentrantLock来作为同步锁时,要确保是同一个锁对象;使用final进行修饰
	final static ReentrantLock lock = new ReentrantLock();

	public static void main(String[] args) throws InterruptedException {
		//
		Thread t = new Thread(new Runnable() {
			@Override
			public void run() {
				test1();
			}
		});
		t.start();
		//
		Thread.sleep(500);
		//
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				test2();
			}
		});
		t1.start();
	}

	public static void test1() {
		try {
			lock.lock();
			//
			for (int i = 1; i <= 10; i++) {
				//
				System.out.println("线程1:" + (5 / (5 - i)));
				//
				try {
					Thread.sleep(800);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			//
			// lock.unlock();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			//
			lock.unlock();
		}
	}

	public static void test2() {
		lock.lock();
		//
		for (int i = 1; i <= 10; i++) {
			//
			System.out.println("线程2--执行test2方法-----");
			//
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//
		lock.unlock();
	}

}

死锁

两个或多个线程, 相互之间互持对方想获取的资源, 在没释放自身资源时之前, 又去试图获取其它线程持有的资源,而造成多个线程同时阻塞, 无法解除

比如:

A 线程持有 "1" 这个资源, 在没有释放 "1" 时, 又试图获取 "2"。

B 线程持有 "2" 这个资源, 在没有释放 "2" 时, 又试图获取 "1"。这样就形成死锁。为了尽量避免死锁的发生, 在持有一个资源的同时, 少点去获取其它资源;尽量避免 锁的嵌套;

线程间的通信(生产者和消费者模式)

线程通信:当线程在系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行,但Java也提供了一些机制来保证线程协调运行

API

wait();导致当前线程等待

notify();唤醒在此监视器对象的单个线程

notifyAll();唤醒在此监视器对象的所有线程

注意

1.使用wait和notify时需要同步锁修饰,同步修饰的对象锁要一致

2.明确调用wait和notify的对象一致,能够确保通信操作是对应的线程

案例

1.老师给学生点名,点到张三,突然想起张三有个奖状,忘记拿了;

//老师线程
public class TheacherThread extends Thread {
	@Override
	public void run() {
		for (int i = 1; i <= 60; i++) {
			System.out.println("老师点名到第" + i + "位同学!");
			// i==30 30为张三
			if (i == 30) {
				System.out.println("突然张三想起有个奖状忘记拿了老师顺便带只笔过来记录点名信息,等待张三搞定!");
				// 让当前老师点名线程进行等待
				synchronized (this) {
					try {
						this.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			// 延时
			try {
				this.sleep(150);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
public class TheacherMain {
	//注意:使用wait()和notify()要使用同步锁来监视,同步锁对象要一致    引用通知和等待方法的对象也要一致
	public static void main(String[] args) throws InterruptedException {
		//
		TheacherThread theacher = new TheacherThread();
		theacher.start();
		// 模拟时间
		Thread.sleep(31 * 150);//模拟 点名
		System.out.println("张三去拿东西!");
		Thread.sleep(1000);// 拿东西
		System.out.println("张三回来了!通知老师可以继续点名!");
		// 通知老师可以继续点名
		synchronized (theacher) {
			theacher.notify();
		}
	}
}

2.一个生产者,一个消费者,手机经销商【台数】【售卖手机】

//生产者
public class Producer extends Thread {

	// 提供货架---缓冲区的引用
	PhoneBuffer buffer;

	public PhoneBuffer getBuffer() {
		return buffer;
	}

	public void setBuffer(PhoneBuffer buffer) {
		this.buffer = buffer;
	}

	public Producer() {
		super();
	}

	public Producer(String name) {
		super(name);
	}

	@Override
	public void run() {
		while(true) {
			// 判断货架是否为空--空
			synchronized ("Producer") {//两个都在判断之前加同步锁--解决多个生产者,多个消费者
				if(buffer.isEmpty) {
					int proNum = (int) (Math.random()*1000+1);
					// 判断货架是否为空--不为空
					System.out.println(this.getName()+"生产了"+proNum+"台手机提供给经销商!");
					buffer.num=proNum;
					buffer.isEmpty = false;
					//通知消费者消费
                    buffer.doNotify();
				}else {
					// 判断货架是否为空--不为空
					//生产者等待
					buffer.doWait();
				}
			}
			//
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

}
//消费者
public class Comsumer extends Thread {

	// 提供货架---缓冲区的引用
	PhoneBuffer buffer;

	public PhoneBuffer getBuffer() {
		return buffer;
	}

	public void setBuffer(PhoneBuffer buffer) {
		this.buffer = buffer;
	}

	public Comsumer() {
		super();
	}

	public Comsumer(String name) {
		super(name);
	}

	@Override
	public void run() {
		while (true) {
			synchronized ("Comsumer") {//在判断之前加同步锁--解决一个生产者,多个消费者
				// 判断货架是否为空--空
				if (buffer.isEmpty) {
					// 消费者等待
					buffer.doWait();
				} else {
					// 判断货架是否为空--不为空
					System.out.println(this.getName() + "在经销商消费了" + buffer.num + "台手机!");
					buffer.isEmpty = true;
					// 通知生产者生产
					buffer.doNotify();
                    //buffer.doNotifyAll();//一个生产者,多个消费者
				}
			}
			//
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

}
//手机经销商 -- 货架  -- 缓冲区
public class PhoneBuffer {

	// 货架是否为空的标志
	static boolean isEmpty = true;
	// 手机的个数
	static int num;

	//等待操作
	public synchronized void doWait() {
		try {
			this.wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	//通知-唤醒
	public synchronized void doNotify() {
		this.notify();
	}
    public synchronized void doNotifyAll() {// 一个生产者,多个消费者--需要唤醒
		this.notifyAll();
	}
	public static void main(String[] args) {
		//
		PhoneBuffer buffer = new PhoneBuffer();
		//
		Comsumer c1 = new Comsumer("消费者1");c1.setBuffer(buffer);
//		Comsumer c2 = new Comsumer("消费者2");c2.setBuffer(buffer);
//		Comsumer c3 = new Comsumer("消费者3");c3.setBuffer(buffer);
		Producer p1 = new Producer("生产者1");p1.setBuffer(buffer);
//		Producer p2 = new Producer("生产者2");p2.setBuffer(buffer);
//		Producer p3 = new Producer("生产者3");p3.setBuffer(buffer);
		//
		c1.start();
//		c2.start();
//		c3.start();
		p1.start();
//		p2.start();
//		p3.start();
	}
	
}

线程池

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

与数据库连接池类似的是,线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable对象或Callable对象传给线程池,线程池就会启动一个线程来执行它们的 run()或call()方法,当run()或call()方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run(或call0方法。

线程池的应用需求

1.如果在开发当中大量的使用到线程。

2.而且这些线程的运行周期短,吞吐量不大(出入的数据)

频繁的创建线程,会比较耗费JVM的资源。因此,需要设计一种管理与复用线程的技术方案。

线程池的优点

1.开过的线程可以复用,不用频繁开启/销毁。

2.方便对线程的管理,提高线程的利用率。

JVM要创建大量的线程,因为每创建和销毁一个线程都会降低程序的运行效率的。比如:开辟内存空间, JVM栈等等。

线程池的创建

--核心线程池大小

--最大池的大小

--时间单位

--存活时间

线程池的工作流程、原理

判断核心线程是否满

 

判断任务队列是否满

 

判断总线程最大值是否满

 

饱和策略

当前线程池处于饱和状态,需要有一套策略来处理新提交的任务

策略默认为: AbortPolicy

imp
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class PoolDemo {

	//默认的饱和策略:RejectedExecutionException
	public static void main(String[] args) {
		// 创建一个任务队列:用于存放任务
		BlockingQueue workQueue = new ArrayBlockingQueue<>(1);
		// 创建线程池
		//饱和策略处理器:CallerRunsPolicy  当前的调用者来执行任务  DiscardOldestPolicy丢弃任务队列中最近的一个任务   DiscardPolicy不处理
		RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
		//                                           核心池大小 最大线程池大小 存活时间  时间单位  任务队列
		ExecutorService executor = new ThreadPoolExecutor(1, 1, 10, TimeUnit.SECONDS, workQueue,handler);
		// 使用线程池来执行任务
		executor.execute(new Runnable() {
			@Override
			public void run() {
				//
				System.out.println(Thread.currentThread().getName()+"去拿个快递");
				//
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});

		// 使用线程池来执行任务
		executor.execute(new Runnable() {
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName()+"去买个菜");
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
		
		// 使用线程池来执行任务
		executor.execute(new Runnable() {
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName()+"去买个面包");
			}
		});

		//关闭线程池
		executor.shutdown();
		
	}

}

JDK1.5提供的四种策略

AbortPolicy:直接抛出异常RejectedExecutionException。

CallerRunsPolicy:使用调用者所在线程来运行任务。

DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。

DiscardPolicy:不处理,丢弃掉。

当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。

Java 8改进的线程池

  在Java 5 以前,开发者必须手动实现自己的线程池;从Java 5开始,Java内建支持线程池。Java 5新增了一个Executors 工厂类来产生线程池,该工厂类包含如下几个静态工厂方法来创建线程池。

newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中。

newFixedThreadPool(int nThreads):创建一个可重用的、具有固定线程数的线程池。

newSingleThreadExecutor():创建一个只有单线程的线程池,它相当于调用newFixedThread Pool()方法时传入参数为1。

newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务。corePoolSize指池中所保存的线程数,即使线程是空闲的也被保存在线程池内。newSingleThreadScheduledExecutor():创建只有一个线程的线程池,它可以在指定延迟后执行线程任务。

ExecutorService newWorkStealingPool(int parallelism):创建持有足够的线程的线程池来支持给定的并行级别,该方法还会使用多个队列来减少竞争。

ExecutorService newWorkStealingPool():该方法是前一个方法的简化版本。如果当前机器有4个CPU,则目标并行级别被设置为4,也就是相当于为前一个方法传入4作为参数。

通过Executors工具类的API方法来创建线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolNew {

	public static void main(String[] args) {
		//通过Executors工具类的API方法来创建线程池
		ExecutorService executor = Executors.newFixedThreadPool(5);
		//
		executor.execute(new Runnable() {
			@Override
			public void run() {
				//
				System.out.println(Thread.currentThread().getName() + "去拿个快递");
			}
		});

		// 使用线程池来执行任务
		executor.execute(new Runnable() {
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + "去买个菜");
			}
		});

		// 使用线程池来执行任务
		executor.execute(new Runnable() {
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + "去买个面包");
			}
		});
		// 使用线程池来执行任务
		executor.execute(new Runnable() {
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + "去买个面包1");
			}
		});
		// 使用线程池来执行任务
		executor.execute(new Runnable() {
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + "去买个面包2");
			}
		});
		// 使用线程池来执行任务
		executor.execute(new Runnable() {
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + "去买个面包3");
			}
		});
		
		//
		executor.shutdown();
	}

}
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值