进程
计算机同一时刻只能有一个进程 CPU分配时间片 CPU通过一个指针,在打开的进程里,非常迅速的轮转,所以可以边听音乐,边敲代码,边看ppt 进程是由开发人员设置的 进程就是正在进行的程序 独立性:系统中独立存在的实体,进程之间相互独立 动态性:程序是一个静态的指令集和,进程是一个正在系统中活动的指令集和,进程中加入时间概念,进程具有自己的生命周期和各种不同的状态,是程序中不具备的 并发性:多个进程可以在单个处理器并发执行,多个进程之间不会互相影响 并行:指在同一时刻,有多条指令在处理器上同时执行,宏观和微观都是同时的 并发:指在同一时刻只能有一条指令执行,因为指针的迅速轮转,宏观上认为是同时的
线程
线程是进程中的顺序执行控制流,是一条执行路径,是操作系统能够进行运算调度的最小单位 简而言之:一个程序运行至少一个进程,一个进程包含一个线程(单线程)或多个线程 线程依赖进程存在 Thread类,JAVASE中规定,一个类只要继承Thread类,此类就是一个线程类 子类中必须重写父类Thread类的run()方法,此方法为线程的主体,也就是当线程被CPU调度时要执行的方法 如果无法使用this获取线程名称,可以使用currentThread获取 可设置CPU的优先级 线程的优先级并不能保证线程的执行顺序,只是CPU给优先级高的线程的资源概率大 优先级低的也不是没有机会执行
生命周期
新建状态(New): 当一个线程对象被创建后,线程就处于新建状态。 在新建状态中的线程对象从严格意义上看还只是一个普通的对象,还不是一个独立的线程,不会被线程调度程序调度。 新建状态是线程生命周期的第一个状态。 就绪状态 (Runnable): 处于新建状态中的线程被调用start()方法就会进入就绪状态。 处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,但并不是说执行了start()方法线程就会立即执行。 在等待/阻塞状态中的线程,被解除等待和阻塞后将不直接进入运行状态,而是先进入就绪状态 运行状态(Running): 处于就绪状态中的线程一旦被系统选中,使线程获取了CPU时间,就会进入运行状态。 线程在运行状态下随时都可能被调度程序调度回就绪状态。在运行状态下还可以让线程进入到等待/阻塞状态。 在通常的单核CPU中,在同一时刻只有一个线程处于运行状态。在多核的CPU中,就可能两个线程或更多的线程同时处于运行状态,这也是多核CPU运行速度快的原因。 注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,必须先处于就绪状态中。 阻塞状态(Blocked): 根据阻塞产生的原因不同,阻塞状态又可以分为三种 等待阻塞:运行状态中的线程执行wait()方法,使当前线程进入到等待阻塞状态; 锁阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),线程会进入同步阻塞状态。 其他阻塞:通过调用线程的sleep()或发出了I/O请求时等,线程会进入到阻塞状态。当sleep()睡眠结束或I/O处理完毕时,线程重新转入就绪状态。
继承Thread类
public class XianCheng {
public static void main(String[] args) {//单线程
//4创建两个线程对象,
Thread01 t1=new Thread01();
Thread01 t2=new Thread01();
//5启动线程
//t1.run();//可执行,还是单线程,
//t2.run();//同上,和调用普通方法没区别,不会启动线程
//必须调用start()方法才能启动线程,然后由JVM调用这个线程来执行线程的run()方法
t1.start();
t2.start();
Thread02 t3=new Thread02();
Thread02 t4=new Thread02();
Thread02 t5=new Thread02();
t3.setName("t3");
t4.setName("t4");
t5.setName("t5");
//获取优先级
System.out.println(t3.getPriority());
System.out.println(t4.getPriority());
System.out.println(t5.getPriority());
t3.setPriority(1);
t4.setPriority(5);
t5.setPriority(10);
//t5优先级最高,t4次高,t3最低
t3.start();
t4.start();
t5.start();
}
}
//1定义一个类继承Thread类,该类就是一个线程类
class Thread01 extends Thread{
//2重写Thread类中的run()方法
@Override
public void run() {
//3编写业务代码
for (int i = 0; i < 100; i++) {
//this就是指当前调用的对象,获取线程名称
System.out.println(this.getName()+"run方法执行" +i);
}
}
}
class Thread02 extends Thread{
@Override
public void run() {
for (int i = 0; i < 101; i++) {
System.out.println(this.getName()+"线程执行run()方法"+i);
}
}
}
实现Runnable接口
Thread类的局限性,猫类继承了动物类,就不能继承Thread类 为此,Thread提供了两个构造方法 →使用Runnable接口实现多线程 →Runnable接口中没有start方法,依赖于Thread启动线程 好处:继承其他类没有限制
public class XianCheng02 {
public static void main(String[] args) {
//4.创建线程类Runnable01对象(他不是线程对象,而是线程对象的执行目标对象)
Runnable01 target=new Runnable01();
//5.创建Thread类的对象,将目标对象作为参数传入,作为执行目标,
// 目的为了启用多个线程
//真正意义上的线程对象,target是线程对象的执行目标对象
Thread t1=new Thread(target,"t1");//第二个参数为线程名称
Thread t2=new Thread(target,"t2");
//6.启动线程
t1.start();
t2.start();
}
}
//1.创建一个类,实现Runnable接口
class Runnable01 implements Runnable{
//2.重写Runnable接口中的run方法
@Override
public void run() {
//3.业务代码
for (int i = 0; i < 101; i++) {
System.out.println(Thread.currentThread().getName()+"线程执行了run()"+i);
}
}
}
线程案例
public class XCAnLi {
public static void main(String[] args) {
Ticket t1=new Ticket();
t1.setName("窗口1");
Ticket t2=new Ticket();
t2.setName("窗口2");
Ticket t3=new Ticket();
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
//通过继承Thread类
class Ticket extends Thread {
//不加static就不是随着类而加载,会出现重复卖票的情况,各自持有100张
static int tickets=100;
@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(this.getName() + "卖出了第" + (tickets--) + "张票");
} else {
break;
}
}
}
}
public class XCAnLi {
public static void main(String[] args) {
Tickets t=new Tickets();
Thread t1=new Thread(t,"窗口1");
Thread t2=new Thread(t,"窗口2");
Thread t3=new Thread(t,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
//通过实现Runnable类
class Tickets implements Runnable{
//不用static修饰,因为目标对象只有一个,所以不会影响
int tickets=100;
@Override
public void run() {
while (true){
if(tickets>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(tickets--)+"张票");
}else {
break;
}
}
}
}
线程安全
线程安全问题: 例如上边的案例可能出现一票多卖或者负数情况。多次运行有可能出现问题 判断一个程序是否有线程安全问题依据: 是否存在多线程环境 是否存在共享数据 是否存在多条语句访问,修改共享数据 为了解决这样的问题就使用到了:同步代码块 同步代码块是指一个时间段内只能有一个线程执行,提供synchronized关键字 将有可能发生线程安全问题的代码包含在同步代码块中,同一时间只允许一个线程进入同步代码块 必须指定一个需要同步的对象,也称为锁对象,这里的锁对象,可以是任意对象,但必须只能是一个 若使用this作为锁对象,需保证多个线程执行时,this指向的是同一个对象 也可以使用关键字将一个方法修饰成同步方法,它能实现和同步代码块同样的功能 访问该方法的其他线程都会发生阻塞,直到当前线程访问完毕,其他线程才有机会访问
同步代码块
synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问 格式:synchronized (锁对象) {需要同步的代码} 锁对象可以是任意类型。 多个线程对象要使用同一把锁。
//还是使用售票案例
public class XCAQ {
public static void main(String[] args) {
Tickets2 t=new Tickets2();
Thread t1=new Thread(t,"窗口1");
Thread t2=new Thread(t,"窗口2");
Thread t3=new Thread(t,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Tickets2 implements Runnable {
//不用static修饰,因为目标对象只有一个,所以不会影响
int tickets = 100;
/*
当不可以使用this时,可先创建object对象
※static int tickets=100;
※static Object obj=new Object();
synchronized (obj){}
※两行都必须是类的,使用static修饰
*/
@Override
public void run() {
while (true) {
synchronized (this) {//this指目标对象
/*
当前类使用this没问题,因为只有一个目标对象,
当直接使用Thread类,有多个对象时,则不要使用this,因为this表示的锁对象有多个
*/
if (tickets > 0) {
try {
Thread.sleep(200);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + (tickets--) + "张票");
} else {
break;
}
}
}
}
}
同步方法
用synchronized修饰的方法叫做同步方法,保证一个线程执行该方法的时候,其他线程只能在方法外等着。 格式:public synchronized void method(){可能会产生线程安全问题的代码}
public class XCAQ2 {
public static void main(String[] args) {
Tickets4 t1=new Tickets4();
t1.setName("窗口1");
Tickets4 t2=new Tickets4();
t2.setName("窗口2");
Ticket t3=new Ticket();
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Tickets4 extends Thread {
static int tickets = 100;
@Override
public void run() {
while (true) {
setSyncObj();
if(tickets<=0){
break;
}
}
}
public static synchronized void setSyncObj(){//随着类创建而创建,锁对象只有一个,this
if (tickets > 0) {
try {
Thread.sleep(200);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + (tickets--) + "张票");
}
}
}
Lock锁
为了更清晰的表达如何加锁和释放锁,jdk5以后提供了一个新的锁对象Lock 比使用synchronized方法和语句获得更广泛的锁定操作 提供了获得锁和释放锁的方法 void lock():获得锁 void unlock():释放锁 Lock是不能直接实例化,采用实现ReentrantLock来实例化 睡眠sleep()方法: 让当前线程进入到睡眠状态,到指定毫秒后自动醒来继续执行
public class LockDemo {
public static void main(String[] args) {
Tickets5 t=new Tickets5();
Thread t1=new Thread(t,"窗口1");
Thread t2=new Thread(t,"窗口2");
Thread t3=new Thread(t,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Tickets5 implements Runnable {
static int tickets = 100;
private ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
if (tickets > 0) {
//此时如果出现异常,也未释放锁
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + "卖出了第" + (tickets--) + "张票");
} else {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//将释放锁放在finally代码块中,就出现异常还是其他情况,释放锁都会被执行
lock.unlock();
}
}
}
}
死锁
死锁:线程死锁是指由于两个或多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行 线程嵌套会出现死锁情况 下面案例就是死锁现象
public class LockDemo {
public static void main(String[] args) {
Object obj1=new Object();
Object obj2=new Object();
new Thread(()->{
while(true){
synchronized (obj1){
//线程1
synchronized (obj2) {
System.out.println("tom run");
}
}
}
}).start();
new Thread(()->{
while(true){
synchronized (obj2){
//线程2
synchronized (obj1) {
System.out.println("jack run");
}
}
}
}).start();
}
}
/*执行结果,没有结束,但处于死锁中
tom run
tom run
tom run
tom run
tom run
tom run
tom run
tom run
tom run
tom run
tom run
jack run
*/
/*
//MyThread类操作等同于第一个new Thread(()->{})
class MyThread extends Thread{
Object obj1=new Object();
Object obj2=new Object();
@Override
public void run() {
while(true){
synchronized (obj1){
synchronized (obj2) {
System.out.println("tom run");
}
}
}
}
}*/
线程状态
锁中有提到睡眠方法
等待和唤醒
void wait():导致当前线程等待,直到另一个线程调用该对象的唤醒方法 void notify():唤醒正在等待对象监视器的单个线程 void notifyAll():唤醒正在等待对象监视器的所有线程
//小案例来实现
/* 消费者,桌子,生产者
消费者步骤: 生产者步骤:
1.判断桌子上是否有汉堡包 1.判断桌子上是否有汉堡包
2.如集没有就等待 如果有就等待。如果没有才生产。
3.如果有就开吃 2.把汉堡包放在桌子上。
4.吃完之后,桌子上的汉堡包就没有了, 3.叫醒等待的消费者开吃
叫醒等待的生产者继续生产
议堡包的总数量减—
*/
public class WaitNotify {
public static void main(String[] args) {
customer c=new customer();
Producer p=new Producer();
c.start();
p.start();
}
}
class customer extends Thread{
@Override
public void run() {
while (true){
synchronized (Desk.lockobj){
if(Desk.count==0){
break;
}else {
if (Desk.flag){
//桌子上有面包
System.out.println("消费者在消费面包");
Desk.flag=false;
Desk.lockobj.notifyAll();
System.out.println("消费者吃了第"+Desk.count+"个面包");
Desk.count--;
}else {
//桌子上没有面包
//使用哪个对象当作锁,就必须用这个对象调用唤醒和等待
try {
Desk.lockobj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
while (true){
synchronized (Desk.lockobj){
if(Desk.count==0){
break;
}else {
if (!Desk.flag){
//桌子上没有面包就生产
System.out.println("生产者开始生产面包");
Desk.flag=true;
Desk.lockobj.notifyAll();
}else {
//桌子上有面包就等待
try {
Desk.lockobj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
class Desk{
//声明一个标记
//true表示桌子上有面包,允许消费者执行
//false表示桌子上没有面包,此时允许厨师执行
public static boolean flag=false;
//最大生产数量
public static int count=15;
//因为都是围绕桌子进行,所以将锁设在桌子比较合适
//锁对象
public static final Object lockobj=new Object();
}
线程池
线程池:用来装线程 代码实现: 1.创建一个空池子,创建Executors中的静态方法 2.有任务需要执行时,创建线程对象 ---------->submit方法 线程池会自动帮创建对象 任务执行完毕,线程对象归还线程池 ------>^ 执行完毕也会自动归还 创建线程池方式一 static ExcutorService newCachedThreadPool():创建一个根据需要创建新线程的线程池,但在他们可用时将重用以前构造的线程 创建线程池方式二 static ExcutorService newFixedThreadPool(int nThreads):创建一个指定最多线程数量的线程池,nThreads表示最多线程数量
//方式一
public class XCpool {
public static void main(String[] args) throws InterruptedException {
//创建一个默认的线程池对象,默认为空,默认数量是int类型最大值
//Executors这个类可以帮助我们创建线程池对象
//ExecutorService这个类可以控制线程池
ExecutorService pool = Executors.newCachedThreadPool();
pool.submit(()->{
System.out.println(Thread.currentThread().getName()+"1在执行");
});
Thread.sleep(3000);
pool.submit(()->{
System.out.println(Thread.currentThread().getName()+"2在执行");
});
pool.submit(()->{
System.out.println(Thread.currentThread().getName()+"3在执行");
});
pool.shutdown();
}
}
//方式二
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);//是最大值,而不是初始值
for (int i = 0; i < 20; i++) {
es.submit(()->{
System.out.println(Thread.currentThread().getName()+"1在执行");
});
es.submit(()->{
System.out.println(Thread.currentThread().getName()+"2在执行");
});
es.submit(()->{
System.out.println(Thread.currentThread().getName()+"3在执行");
});
}
ThreadPoolExecutor pe=(ThreadPoolExecutor)es;
System.out.println(pe.getPoolSize());//10,提交10个任务,创建10个线程
es.shutdown();
}
}
/*
1核心线程数量,2最大线程数量,3空闲线程存活时间,4时间单位,
5阻塞队列: 让任务在队列中等待,有线程空闲了就会从阻塞队列中获取任务并执行
6创建线程方式:创建线程对象(Executors.defaultThreadFactory()默认方式)
7超出任务拒绝策略:
1)什么时候拒绝:当提交的任务 > 池子中最大线程数量+队列容量
2)怎么拒绝:ThreadPoolExecutor.AbortPolicy()是默认的策略,抛出(内部类)表示丢弃任务并抛出
ThreadPoolExcutor.DiscardPolicy()丢弃任务,不抛出异常,不推荐的做法(只执行 最大线程数量+阻塞队列)
ThreadPoolExcutor.DiscardOldestPolicy:抛弃队列中等待时间最久的任务,然后把当前任务加入队列中
ThreadPoolExcutor.CallerRunsPolicy:调用任务的run()方法绕过线程池直接执行
核心线程数量: 不能小于0
最大线程数量: 不能小于等于0,数量>=核心线程数量
空闲线程最大存活时间: 不能小于0
时间单位: 时间单位
任务队列: 不能为null
创建线程工厂: 不能为null
任务的拒绝策略: 不能为null
*/
public static void main(String[] args) {
//第5项为阻塞队列 //第7项用的是内部类,拒绝处理程序
ThreadPoolExecutor pe=new ThreadPoolExecutor(1,3,2, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
//pe.submit(()->{}) 用lambda表达式也可以
for (int i = 0; i < 5; i++) {//正好
pe.submit(new RunnableTest());
}
pe.shutdown();
//测试7.2)的第四项
ThreadPoolExecutor tpe=new ThreadPoolExecutor(1,3,2, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());
//pe.submit(()->{}) 用lambda表达式也可以
for (int i = 0; i < 5; i++) {//正好
int y=i;
tpe.submit(()->{
System.out.println(Thread.currentThread().getName()+" :"+y);
});
}
tpe.shutdown();
}
}
class RunnableTest implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程执行了");
}
}
本文含有隐藏内容,请 开通VIP 后查看