线程
进程:正在运行的程序
线程:程序运行过程中的一个小单元
例如:开门开窗户卖票到关窗户关门 结束卖票 进程 其中任意一个窗户可以实现一定任务 线程
关系:进程中 至少包含一个线程 线程一定属于某个进程
多线程配置:当执行多线程任务时 其实是CPU在各个线程中快速切换 时间非常短 感觉是在同时执行
例如:可以通过增加下载任务的线程 更多几率的获取CPU处理权限
CPU时间片段分配原则:
1.分时调度:所有线程轮流使用CPU的使用权 平均分配给每个线程时间片段
2.抢占式调度:优先级高的会大概率优先获取时间片段 如果优先级相同 则CPU随机选择线程执行
多核处理器:相当于在单核CPU切换中 让切换效率翻倍 并没有提高程序的运行速度 只是提升了CPU的利用率
java程序开启后 开始执行的线程有 主线程 同时开启GC线程(垃圾回收线程 守护线程)
线程优先级设定
设置线程优先级
线程对象.setPriority(Thread.MAX_PRIORITY); 10
线程对象.setPriority(Thread.MIN_PRIORITY); 1
线程对象.setPriority(Thread.NORM_PRIORITY); 5
查看线程优先级
SYSO(线程对象.getPriority());
Thread 线程
Thread.currentThread()//获取当前线程对象
System.out.println(Thread.currentThread().getName());//main 主线程
案例:
一个人买100张票
两个人 各自买各自的100张票
两个人共同买100张票–》线程安全问题
方案一 继承自Thread类 重写run()
//案例二:每个用户有自己的100张票 并不会出现线程安全问题
public class Users extends Thread{
int count = 100;
public void run(){
//线程任务
while(count>0){
syso(Thread.currentThread().getName()+"买到第"+(101-count)+"张票");
count--;
}
}
}
//测试类main方法中
Users u1 = new Users();
u1.setName("张三");//给线程对象命名 若不起名 则默认名称效果:Thread-0 Thread-1..
u1.start();//开启线程的必须方法 不能使用run()否则变为单线程普通方法调用效果
Users u2 = new Users();
u2.setName("李四");
u2.start();
//案例三:每个用户共享100张票 会出现线程安全问题---》多个线程共同操作同一个数据 并且进行了数据更新
public class Users extends Thread{
static int count = 100;
public void run(){
//线程任务
while(count>0){
syso(Thread.currentThread().getName()+"买到第"+(101-count)+"张票");
count--;
}
}
}
练习:
商店有10只猫 10只狗
如果是张三来购买 买猫 每次一只 买完为止 如果是李四 则买狗 每次一只 买完为止
方案二 实现Runnable接口
特点:
实现接口 更加有利于代码扩展
实现了任务与线程对象的分离 Runnable接口实现类只负责设计线程任务
可以使用线程池提供线程对象
public class Tickets implements Runnable{
int n = 100;//因为一般只创建一个实现类对象 因此全局变量是所有线程对象共享的
//如果真的给每个线程对象 都单独创建了该实现类对象 那么n为各个对象私有的属性
public void run(){
//每个线程对象都会有自己的run()
int count = 10;//所有线程对象不共享的数据 作为局部变量 放在run()内部
。。。
}
}
//测试类 推荐只创建一个线程任务对象 可以创建多个线程对象 去绑定这个任务
Tickets t = new Tickets();//创建唯一的线程任务对象 不建议创建多个
Thread t1 = new Thread(t,"张三"); //创建线程对象 绑定任务 同时为线程命名
t1.start();
new Thread(t,"李四").start();
练习:
进入窗口一买猫 进入窗口二 买狗 任意一个宠物 买够10只 则关闭所有窗口
杀死线程
t1.stop();
t1.destroy();
t1.resume();
以上方法已被弃用 推荐通过判断为假结束线程任务
public class Buy implements Runnable {
boolean bool = true; //所有线程对象 共享的数据
@Override
public void run() {
// TODO Auto-generated method stub
int count = 0; //每个线程对象私有的属性
String name = Thread.currentThread().getName();
while(bool) {
count++;
if(name.equals("窗口一")) {
System.out.println(name+"买到第"+count+"只猫");
}else {
System.out.println(name+"买到第"+count+"只狗");
}
if(count==10) {
bool = false;
}
}
}
}
//注意 因为未做线程保护 可能会多执行一次
方案三 实现Callable接口
特点:该接口线程任务的方法 带返回值 只能通过线程池提供线程对象
案例:线程一 返回 1累加到10的和 线程二返回 1累乘到10的积
public class YunSuan implements Callable<Integer> {//泛型定义的是方法返回值类型
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
int num = 1;
String name = Thread.currentThread().getName();
System.out.println(name);
if(name.equals("pool-1-thread-1")) {
for(int i =2;i<=10;i++) {
num+=i;
}
}else {
for(int i =2;i<=10;i++) {
num*=i;
}
}
return num;
}
}
//测试类中使用线程池 创建线程对象
线程池
存放线程对象的容器 不用再反复创建线程对象 需要时 直接获取 用完后 归还线程池
线程使用完毕后 会归还到线程池 供后续任务获取使用
线程池一:不固定线程池中线程数量 每次需要就创建 无限创建
默认线程名称 pool-1-thread-1 pllo-1-thread-2… 如果第一个任务已经归还了线程 那么线程池会帮助销毁线程对象 当第二次再申请时 继续创建并获取pool-1-thread-1
YunSuan ys = new YunSuan();//创建唯一任务对象
//线程池
//不固定线程数量
ExecutorService es = Executors.newCachedThreadPool();
Future<Integer> result1 = es.submit(ys);//绑定任务并开始执行 返回带结果的Future对象
//如果绑定执行的是Runnable实现类对象 则不需要返回值
System.out.println("线程一:"+result1.get());//通过get()获取值
线程池二:固定线程池中线程数量 达到最大数量后 其他任务等待 等待有归还线程 再获取该线程对象 绑定任务
//固定线程数量的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
Future<Integer> result1 = es.submit(ys);//绑定任务并开始执行 返回带结果的Future对象
System.out.println("线程一:"+result1.get());//通过get()获取值
Future<Integer> result2 = es.submit(ys); //任务2 还有线程可以获取
System.out.println("线程二:"+result2.get());
Future<Integer> result3 = es.submit(ys); //任务3 只能等待前面的任务归还线程
System.out.println("线程三:"+result3.get());
总结:
1.接口更适合于资源的共享 (一般只创建一个接口实现类对象)
2.接口避免受到单继承约束 有利于项目的扩展 不占用继承位置
3.实现接口 可以实现线程任务与线程对象的分离 可以单独编写任务 可以利用线程池重复使用线程对象
4.线程池只能用于接口(Runnable Callable) 不能用于继承自Thread的线程对象
练习:
60个人通过5个窗口买50张票 显示第几个人从第几个窗口买到第几张票(暂时不用考虑数据安全)
显示第几个人没买到票