线程 Thread
进程包含多个线程 至少有一个线程 线程一定属于某一个进程
java多线程语言 主线程+垃圾回收线程(守护线程)
逻辑
CPU 的每一个时间片段 分配给每一个线程 同一时间片段 只能处理某一个线程的任务 多个线程快速的切换
多核处理器 提升了CPU每一个时间片段的使用效率
分配原则:
分时调度 平均分配给每个线程
抢占式调度 根据线程优先级 提高获取CPU使用权的概率(不靠谱)
安全性
【多个线程】 同时【更新】【同一个数据】 极大概率出现数据异常 称为线程安全问题
数据更新速度 赶不上 线程切换速度
创建线程
方案一:extends Thread
继承自Thread的类 变为线程类 (1.本身就是线程对象 2.内部特定方法run()为线程任务)
小线程默认名称:Thread-0 Thread-1 。。。
//1.继承自Thread 成为线程类
public class Tickets extends Thread {
//2.编写线程任务的run()
String name;
//@Override
public void run() {
// TODO Auto-generated method stub
//super.run();
//System.out.println("我是小线程任务");
Thread thread = Thread.currentThread();//获取当前执行的线程对象
name = thread.getName();
for(int i =0;i<100;i++){
System.out.println(name+"买到"+(i+1)+"张票");
}
//show();
}
//public void show(){
//}
}
//测试类中 创建线程对象 启动线程
//3.创建线程对象
Tickets t = new Tickets();//绑定了线程任务的线程对象
t.setName("甲");
//4.调用线程启动方法 start() 启动线程任务
//t.run(); //并没有开启小线程 依然是单线程
t.start();
Tickets t1 = new Tickets();
t1.setName("乙");
t1.start();
public class User{
String name;
public User(String name){
this.name = name;
}
public String toString(){
return name;
}
}
User u1 = new User("张三");
syso(u1.toString());
User u2 = new User("李四");
syso(u2.toString());
线程优先级 priority属性
MAX_PRIORITY 10
MIN_PRIORITY 1
NORM_PRIORITY 5 默认线程优先级都为5
Tickets t = new Tickets();//绑定了线程任务的线程对象
t.setPriority(Thread.MIN_PRIORITY); //将t线程优先级降低到1
System.out.println(t.getPriority()); //1
练习
要求1.使用两个线程完成 甲 买20只狗 乙买30只猫 穿插展示购买记录
要求2.使用一个线程任务完成上述任务
案例
1.一个人买100张票
2.两个人 各自买各自的100张票
3.两个人 共同抢100张票
public class Ticket extends Thread{
static int num = 100;//继承写法中 如果需要某属性所有线程共享 则使用static
public void run(){
String name = Thread.currentThread().getName();
while(num>0){
syso(name+"抢到第"+(101-num)+"张票");
num--;
}
}
}
//测试类中
Ticket t1 = new Ticket();
t1.setName("张三");
t1.start();
Ticket t2 = new Ticket();
t2.setName("李四");
t2.start();
//以上代码 实现了两个人共同抢100张票 但是 属于 多个线程同时更新同一个数据 ==》线程安全问题
方案二:implements Runnable
实现自接口 只是实现线程任务run() 没有线程对象 需要额外绑定线程对象 才能启动
1.实现自接口 比 继承自父类 自由度更大
2.可以实现线程任务和线程对象的分离 就可以使用线程池
3.前提 线程任务只创建一次 全局变量 自动所有线程对象共享 run()内的变量 每个线程独有
编写一个类 实现Runnable 中的 run() 主要表述线程任务 本身不是线程对象 不能直接start()
public class Tickets implements Runnable{
public void run(){
String name = Thread.currentThread().getName();
syso(name);
}
}
//测试类中
Tickets t = new Tickets();//只是线程任务 需要额外提供线程对象
//t.start();//报错
Thread t1 = new Thread(t,"甲");//创建线程对象 绑定任务t 并给该线程对象命名为甲
t1.start();//启动线程任务
//可以直接在测试类中 使用局部内部类技术 编写线程任务(Runnable的实现类)
Runnable r = new Runnable() {
//int num =0;//所有线程对象 共享
@Override
public void run() {
// TODO Auto-generated method stub
int num = 100; //每个线程有自己的num属性
String name = Thread.currentThread().getName();
while(num>0) {
System.out.println(name+"买到第"+(101-num)+"张票");
num--;
}
}
};
//创建线程对象 绑定线程任务ticket
Thread t0 = new Thread(r,"甲");
Thread t1 = new Thread(r,"乙");
t0.start();
t1.start();
//测试类中
Runnable r = new Runnable(){
int num = 100; //因为只创建了唯一一个任务对象r 因此成员变量就是将来所有线程共享
public void run(){
//int num = 100;//将来每一个线程对象 都有属于自己的run()内的资源
String name = Thread.currentThread().getName();
while(num>0){
syso(name+"抢到第"+(101-num)+"张票");
num--;
}
}
}
Thread t1 = new Thread(r,"张三");
Thread t2 = new Thread(r,"李四");
t1.start();
t2.start();
//以上代码 实现了两个人共同抢100张票 但是 属于 多个线程同时更新同一个数据 ==》线程安全问题
线程池
1.线程数量固定的线程池 当申请超出了线程数量时 则任务等待 使用完线程将归还线程池
有一定的限流作用
ExecutorService es = Executors.newFixedThreadPool(int); //参数为初始线程数量
pool-1-thread-1 pool-1-thread-2 ...
2.不固定数量的线程池 线程池只负责创建和销毁线程
ExecutorService es = Executors.newCachedThreadPool();
pool-1-thread-1 pool-1-thread-2 ...
Runnable r = new Runnable() {
//int num = 10;
@Override
public void run() {
// TODO Auto-generated method stub
int num = 10;
String name = Thread.currentThread().getName();
// //pool-1-thread-1
// while(num>0) {
// System.out.println("从窗口"+name.substring(name.lastIndexOf("-")+1)+"买到第"+(11-num)+"票");
// num--;
// }
System.out.println(name + "获得了线程 开始工作");
try {
// 会让出CPU
Thread.sleep(new Random().nextInt(5000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(name + "释放了线程");
}
};
Thread t0 = new Thread(r,"甲");
t0.start();
Thread t1 = new Thread(r,"乙");
t1.start();
// //创建只有两个线程对象的线程池Executors
// ExecutorService es = Executors.newFixedThreadPool(2);
//不控制线程数量 随时创建线程 回收线程 删除线程
ExecutorService es = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
es.submit(r);// 获取线程对象 同时开启线程任务
}
es.shutdown();// 关闭线程池
方案三:implements Callable<E>
只能使用线程池提供线程对象
案例
每个线程任务为 返回该线程的名字
Callable<String> cs = new Callable<String>() {
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
String name = Thread.currentThread().getName();
System.out.println(name);
//Thread.sleep(3000);
return name;
}
};
//必须通过线程池提供线程对象
ExecutorService es = Executors.newCachedThreadPool();
//for(int i =0;i<5;i++) {
Future<String> future1 = es.submit(cs);
Future<String> future2 = es.submit(cs);
Future<String> future3 = es.submit(cs);
Future<String> future4 = es.submit(cs);
Future<String> future5 = es.submit(cs);
System.out.println(future1.get());//获取当前线程任务的返回值
System.out.println(future2.get());
System.out.println(future3.get());
System.out.println(future4.get());
System.out.println(future5.get());
Future<String> fs = new Future<String>[5];
for(int i =0;i<fs.length;i++){
fs[i] = es.submit(cs);
}
for(int i =0;i<fs.length;i++){
syso(fs[i].get());
}
//}
es.shutdown();
总结
继承:
1.受到了单继承原则的约束
2.线程任务和线程对象绑定 线程对象不可重复使用
3.本身就是一个线程对象 因此不能使用线程池
接口:
1.不受到继承原则约束
2.实现了线程任务和线程对象的分离 可以使用线程池
3.Runnable 内部实现run() 没有返回值 可以直接new Thread()创建线程绑定任务 也可以使用线程池
4.Callable 内部实现带返回值的call() 只能使用线程池提供线程对象
练习
1.商店有10只猫 10只狗 如果张三购买 只买猫 每次买一只 如果李四购买 只买狗 每次买一只 使用多线程实现购买过程 共用一个线程任务
2.三个线程任务 分别返回一个整数 一个统计100以内偶数个数 一个统计5的倍数个数 一个统计15的倍数的个数
pool-1-thread-3
生命周期
1.新生状态---》绑定线程
2.就绪状态---》start()开启了线程 但是预备获取CPU
3.运行状态--》获得了CPU时间片段 执行任务
4.阻塞状态--》各种情况的让出CPU---》如果休眠结束 回到就绪状态
5.杀死线程--》不推荐使用以下方法 可以通过return提前结束线程 或者任务执行完毕自动结束线程
// t1.destroy();
// t1.resume();
// t1.stop();
练习
变形:商店有10只猫 10只狗 如果张三购买 只买猫 每次买一只 如果李四购买 只买狗 每次买一只
先买到10只则所有交易停止 使用多线程实现购买过程
线程安全
多个线程 共同更新 同一个数据(数据的更新速度 赶不上 CPU在各线程中的切换速度)
锁的逻辑:每个线程遇见同步代码块 就会去获取锁对象 如果存在 则【带着锁】进入作用域 执行内部代码 此时 其他线程也在获取CPU的时候会去【查看锁是否存在】 发现不存在 则只能【阻塞】在代码块外部 内部线程执行完作用域内部的代码后 退出代码块同时【归还锁对象】 其他线程 继续争抢CPU 争抢锁
对象锁的概念
1.必须是引用数据类型 且真正的有对象 User u = new User(); Integer i = 10; Boolean bool = new Boolean(true);
2.所有线程 必须使用同一把锁对象 否则不能正确判定是否有线程正在执行
3.一般推荐单独创建一个Object o = new Object(); this占用资源过大 不推荐
方案1:同步代码块
大括号中的代码 变为 单线程形式
Runnable r = new Runnable() {
Object o = new Object();
// String name = "";
Integer num = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while (num > 0) {
try {
Thread.sleep(10);// 如果把休眠写在同步代码块内部 则会 抱着锁睡觉
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 当内部有线程执行时 其他线程被阻塞在此处
synchronized (o) {
// 同步代码块 内部的代码 同时只会有一个线程进入执行
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "买到第" + (101 - num) + "张票");
num--;
}
}
// 当内部线程执行完毕 退出同步代码块 会归还锁对象
}
}
};
Thread t0 = new Thread(r, "张三");
Thread t1 = new Thread(r, "李四");
t0.start();
t1.start();
练习
三个人共同买100张票 买完为止
三个人各自买100张票 只要有一人买到第100张票则 所有人都停止买票
方案2:同步方法
在设计方法时 添加synchronized关键字 ===》方法体变为单线程业务
public ... main(String[] args){
Runnable r = new Runnable() {
Object o = new Object();
// String name = "";
Integer num = 100;
@Override
public void run() {
xxx
xxx
//其他线程被阻塞在此处
show();
xxx
xxx
}
public synchronized void show(){
//内部代码变为单线程业务
}
};
}
方案3:方法中的同步代码块
任何方法中都可以添加同步代码块结构
public void show(){
//外部代码可以多线程
//其他线程阻塞在此
synchronized (锁对象) {
//内部业务变为单线程业务
}
//外部代码依然多线程
}
方案4:Lock锁
Runnable r = new Runnable() {
Integer num = 100;
//创建Lock锁对象
Lock lock = new ReentrantLock();
@Override
public void run() {
// TODO Auto-generated method stub
while (num > 0) {
// 当内部有线程执行时 其他线程被阻塞在此处
try {
Thread.sleep(10);// 抱着锁睡觉
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//synchronized (o) {
lock.lock();
// 同步代码块 内部的代码 同时只会有一个线程进入执行
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "买到第" + (101 - num) + "张票");
num--;
}
//}
lock.unlock(); //如果想要提前释放锁 必须执行该代码
// 当内部线程执行完毕 退出同步代码块 会归还锁对象
}
}
};
Thread t0 = new Thread(r, "张三");
Thread t1 = new Thread(r, "李四");
t0.start();
t1.start();
死锁
1.互斥原则:一个资源(对象锁) 同时只能被一个线程使用
2.请求与保持:一个线程请求资源 另一个线程保留资源不释放
3.不剥夺原则:线程已经获取的锁对象 未使用完之前 不可被强制收回
4.循环等待:若干线程之间形成一种首尾相接 互相等待资源的关系
Runnable r = new Runnable() {
Object o1 = new Object();
Object o2 = new Object();
@Override
public void run() {
// TODO Auto-generated method stub
String name = Thread.currentThread().getName();
//乙
synchronized (o1) {
System.out.println(name+"获得了锁1");
//Thread.sleep(10);
synchronized (o2) {
System.out.println(name+"获得了锁2");
//
System.out.println(name+"释放了锁2");
}
System.out.println(name+"释放了锁1");
}
//甲
synchronized (o2) {
System.out.println(name+"获得了锁2");
//Thread.sleep(10);
synchronized (o1) {
System.out.println(name+"获得了锁1");
//
System.out.println(name+"释放了锁1");
}
System.out.println(name+"释放了锁2");
}
}
};
new Thread(r,"甲").start();
new Thread(r,"乙").start();
常用方法
void sleep(long) 当前线程休眠 参数毫秒数 之后自动唤醒 让出CPU 但是不会让出锁 Thread类
void interrupt() 打断某个线程中可以被打断的业务 比如休眠 该线程后续任务正常执行
当前线程处于被打断过的状态 可以使用线程对象.isInterrupted() true/false
void yield() 线程礼让 只在那一刻让出CPU 马上进入就绪状态 争抢CPU 不一定让得出去
void join() 强制执行 并且保证强制执行的新线程必须执行完毕后 原线程才可继续
等待
void wait() 任何对象都有的等待方法 必须通过锁对象去等待 让出CPU 让出锁 Object类
唤醒
void notify() 使用【同一把锁】对象去唤醒 因该锁而等待的线程 而且该方法 不会放出锁
void notifyAll() 使用【同一把锁】去唤醒所有 因该锁而等待的线程
生产者模式
案例
买包子 卖包子
//***等待唤醒 依靠全局同一个锁对象关联
Object o = new Object();
//消费者线程
Runnable r = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
String name = Thread.currentThread().getName();
System.out.println(name.equals("甲")?"老板 来笼包子":"老板来十笼包子");
synchronized (o) {//****获取锁对象
//等待环节
try {
o.wait();//让出CPU 让出锁
//唤醒后 必须先拿到锁 从等待位置向后执行
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(name.equals("甲")?"包子真好吃 最好不要钱":"什么 还要钱");
}
};
//生产者线程
Runnable rr = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("等等 我去买面");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//唤醒小线程 先获得锁对象
synchronized (o) {
//o.notify(); //只唤醒一个因为这把锁等待的线程
o.notifyAll(); //唤醒所有因为这把锁而等待的线程
System.out.println("包子好了 来吃");
}
}
};
//****先启动消费者
Thread t0 = new Thread(r,"甲");
t0.start();
Thread t1 = new Thread(r,"乙");
t1.start();
Thread.sleep(10);//主线程休眠一下 保证生产者后启动
Thread tt = new Thread(rr);
tt.start();
练习
甲到了 吃瓜子 乙到了 喝茶 老板到了 大家开始吃饭吧
其他线程控制方案
1.信号量==》固定线程数量的线程池
信号量:分配了线程 只是在某个环节进行线程数量的约束
固定数量线程池:直接约束提供的线程数量 其他业务只能等待获取线程
案例
只有三个窗口买票 显式一出一进的效果
Runnable r = new Runnable() {
//设置信号量 全局变量
Semaphore s = new Semaphore(3);
//如果控制最大线程数量为1 则等同于同步代码块
@Override
public void run() {
// TODO Auto-generated method stub
//判定信号量 多余线程在此处阻塞
try {
s.acquire();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name+"进入窗口 开始办理业务");
try {
Thread.sleep(new Random().nextInt(3000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(name+"办理完业务 离开窗口");
//更新信号量
s.release();
}
};
for(int i =0;i<10;i++) {
new Thread(r,"第"+(i+1)+"人").start();
}
2.同步屏障 ==》需要足够的线程触发后续任务
案例
甲到了喝杯茶 乙到了吃会儿瓜 老板到了 开饭 甲说 吃点菜 乙说 喝点酒 老板说 吃吃喝喝 集体大合照
//设置线程数量 只有执行的线程达到这个数量 才会执行共同任务和各线程后续任务
CyclicBarrier cb = new CyclicBarrier(3, new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("人员起了 来个大合照");
}
});
Runnable r = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
String n = Thread.currentThread().getName();
if(n.equals("甲")) {
System.out.println("喝口茶");
}else if(n.equals("乙")) {
System.out.println("吃会儿瓜");
}else {
System.out.println("开饭开饭");
}
try {
//线程走到此处 开始判定
cb.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(n.equals("甲")) {
System.out.println("吃点菜");
}else if(n.equals("乙")) {
System.out.println("喝点酒");
}else {
System.out.println("吃吃喝喝");
}
}
};
new Thread(r,"甲").start();
new Thread(r,"乙").start();
Thread.sleep(2000);
new Thread(r,"老板").start();
评论