多线程
作用:通过多并发来提高运行速率
并发:两个或多个事件在同一个时间段内发生
并行:两个或多个时间在同一时刻发生(同时发生)
线程与进程的理解
程序
由开发人员编写代码程序,是“死的”
进程
由多个线程组成的,是“活的”,运行状态;线程就好比车间里的工人,一个进程可以包括多个线程;
线程
接收信息的线程、发送信息的线程;是进程中的一个执行单元;一个进程至少有一个线程;
关键点
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();
}
}