程序、进程、线程
程序是一个静态代码块,它是应用程序执行的蓝本。
进程:是指一种正在 运行的程序,有自己的地址空间
进程的特点:动态性,并发性 ,独立性
线程:进程内部的一个执行单元,它是程序中一个单一的顺序控制流程
线程特点:轻量级进程、独立调度的基本单位(时间片)、可并发执行、共享进程资源
并发与并行的区别
并发:多个cpu同时执行多个任务
并行:一个CPU(采用时间片)同时执行多个任务
创建线程方式
方式一 :继承Thread,重写run()
创建线程:class TortoiseThread extends Thread(){}
启动线程:TortoiseThread tt=new TortoiseThread();
tt.start()
线程中常用到的方法:
1.获取线程的名字:this.getName()//当前线程对象是直接继承Thread
Thread.currentThread().getName()//currentThread()表示当前线程对象
2.线程休眠:Thread.sleep();
3.线程命名:方式一:在Thread中有带有(String name )构造器
TortoiseThread tt=new TortoiseThread(“zhangjie”);
方式二:set方法
tt.setName(“sdjsd”)
4.线程优先级设置:getpriority()
5.线程id信息获取:getid()
方式二:实现Runnable
编写线程对象实现Runnable 实现run()方法
1.创建线程对象:Runnable runnable=new TRunnable();
创建thread:Thread thread=new Thread(runnable,“zhangjie”);
thread.start()
2.也可以使用匿名内部类:来创建线程
方式三:实现Callable(有返回值,能异常处理)
与实现Runnable 相比Callable更强大:
应用场景:
创建一个线程,该线程会产生一个随机数,获取产生的随机数
//call方法和run方法的比较:
1.call 方法有返回值
2.call方法默认声明异常,run 没有声明异常只能try--catch
public class TestCallable implements Callable<v>{
//重写指定返回类型的call方法
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
return null;
}
public static void main(String[] args) {
//创建callable对象
Callable<Integer> testAOP = new TestAOP();
//创建futureTask,将Callable---->Runnable
FutureTask<Integer> ft = new FutureTask<>(testAOP);
//创建线程
Thread t = new Thread(ft);
//启动线程
t.start();
//futureTask可以操作线程中数据,以及对线程的控制
//ft.get();取出线程返回的数据
//ft.isCancelled()线程是否被取消
//ft.isdone()线程是否结束
}
}
继承和实现两种方式创建线程的比较:
继承:
优点:代码简单run 方法可以通过this调用线程中的方法
缺点:java是单继承的继承Thread后,无法再去继承其他类成员变量不能被共享
实现:
优点:可以去继承其他类,还可以实现别的接口,可以通过匿名内部类来创建,成员变量可以方便共享
缺点:代码较为复杂,在run方法中无法使用this调用线程中的方法;需要使用Thread.currentThread.getName()来调用
线程的生命周期
运行状态的三个去处?
a)死亡状态:线程运行完毕进入死亡状态
b)阻塞状态,遇到阻塞事件时,进入到阻塞状态
c)就绪状态:yield()
就绪状态的三个来处?
新生状态:调用start方法
阻塞状态:结束阻塞状态就到就绪状态
运行状态:调用yield方法
导致阻塞的情况有哪些?
等待用户输入;
执行了sleep();
join()方法
线程的控制
线程的优先级
getPriority()
setPriority()
MIN_PRIORITY=1
MAX_PRIORITY =10
优先级越高,该线程被优先调用概率越大
线程控制方法
sleep():
让当前线程睡觉,睡指定的时长,单位是毫秒,线程会进入到阻塞状态
join();
在一个线程中插入另一个线程,主线程被终止,子线程被执行完成后主线程在继续执行
yield();
将线程直接从运行状态回到就绪状态;如果调用了yield方法后没有其他等待执行的线程这个时候当前线程就会恢复执行
setDaemon();
可以将指定的线程设置成后台线程
创建后台线程的线程结束时,后台也会随之消亡
只能在线程启动结束之前把他设置为后台线程
interrupt(); isInterrupted();
发出了一个中断信号,需要手动干预中断。
stop();
强制性让线程死亡(已过时)
线程同步
应用场景
多个用户同时操作一个银行账户。每次取款100元,取款前先检查余额是否足够,如果不够,放弃去款。
分析
使用多个线程解决
开发一个取款线程类 ,每个用户对应一个线程对象
因为多个线程共享同一个银行账户使用Runnable方式解决
思路
- 创建银行账户类Account
- 创建取款线程AccountRunnable
- 创建测试类TestAccount,让两个线程同时取款
线程安全问题()
利用同步的方式来解决这个问题
锁:同步监视器,通常可以使用共享对象作为锁,this,还可以准备一个任意不变的对象(没有业务逻辑,只作为锁)
方式一:使用同步代码块(synchronized)锁住帐户对象
synchronized(product){
//代码块
}
- 第一线程来到,发现有锁,但是open的状态,进去执行,同时状态修改为close
- 第一个线程可能在执行过程中释放cpu资源,阻塞状态,但是不会开锁
- 第二个线程获取到cpu资源,发现有锁状态为close,第二个线程也阻塞;
- 第一个线程重新获取cpu资源,继续执行后续的代码,执行结束出来,修改状态为open
- 第二个线程获取到cpu资源,重复第一个线程的操作;
方式二:使用同步方法(锁住存取业务方法)只要有一个方法被锁住,所有同步方法都被锁住====stringBuffer
方式三:使用lock锁(1.5之后)显示锁所有操作都要自己来制定(准备锁,自己上锁,自己开锁)
1.准备一把锁利用
//准备一把锁
Lock lock=new ReentrantLock();
2.手动上锁
//在lock中有一个方法
//此处省略500行代码
lock.lock();
//此处省略需要处理的上锁业务
try{
(....................)
}finally{
//手动开锁
lock.unlock();
}
lock锁和synchronized比较:
使用lock 锁,jvm将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供了更多的子类)
优先使用顺序:Lock -----同步代码块(已经进入方法体,分配了相应的资源)-----同步方法(在方法之外)。
线程通信
应用场景:生产者和消费者问题
假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费
如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
//解决线程通信问题
wait()
notify()
notifyAll()
生产者、消费者、传递者
生产者生产—>传递者传递---->消费者消费
修改:flag 不应该使用boolean 类型了,可以通过使用 int值
1:表示没有产品,需要生产者生产
2:表示有产品,可以传递了,需要传递着传递
3:表示已经传递,可以消费了,需要消费者消费
生产者应该在什么时候等待?不等于1的时候就应该等待
传递着应该在什么时候等待?不等于2的时候就应该等待
消费者应该在什么时候等待?不等于3的时候就应该等待
//wait()与sleep()区别??
wait()方法可以阻塞线程,同时可以释放锁
sleep() 方法可以阻塞线程但是无法释放锁
线程组
线程组表示一个线程集合。
线程组也可以包含其他线程组。线程组构成 一棵树,在树中,除了初始线程组外,每个线程组都有一个父线程组。
顶级线程组名system ,线程默认线程组名称main
在创建之初,线程被限制到一个组里,而且不能改变到一个的组
线程组的作用
统一管理:便于对一组线程进行批量管理线程或线程组对象。
安全隔离:允许线程访问有关自己的线程组的信息,但是不允许它访问有关其线程组的父线程组或者其他任何线程组的信息。
getThreadGroup()获得线程所在的线程的名字
getThreadGroup().getParent()获得顶级线程组
自己定义线程组
//创建线程组
ThreadGroup tg=new ThreadGroup("乌龟");
//给线程组添加 子线程
TortoiseThread t1=new TortoiseThread(tg,"乌龟1");
TortoiseThread t2=new TortoiseThread(tg,"乌龟2");
//批量管理线程
//设置优先级setPriority();
//设置守护线程setDaemon(true)
//查看当前线程有哪些线程是活跃的activeCount();
线程池
- 创建和销毁对象是非常耗费时间的
- 创建对象,需要分配内存资源
- 线程池参数:
- corePoolSize()核心池的大小
- maxmumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
- TimeUnit:keepAliveTime的时间单位
- 阻塞队列
runnable()
//创建线程池
//Executors.newCacheThreadPool();//创建一个可变长度的线程池
ExecutorService pool=Executors.newSingleThreadPool();//创建一个只有一个的线程池
//Executors.newFixedThreadPool(n);//指定固定数量的线程池
//Executors.newScheduledThreadPool(n)//创建一个线程,它可安排在给定延迟后运行命令或者定期地执行。
//使用线程池
for (int i=0;i<20;i++){
//创建Runnable命令
Runnable command=new Runnable(){
@Override
public void run(){
system.out.println("start...");
Thread.sleep(1000);
system.out.println("start...");
}
};
//将命令交给线程池去执行
pool.execute(command);
}
//关闭线程池
pool.shutdown();
callable()