java入门(多线程、同步、线程状态)

多线程

并发与并行
  • 并行:指两个或多个事件在同一时刻发生(同时执行)。
  • 并发:指两个或多个事件在同一个时间段内发生(交替执行)。
    在这里插入图片描述
    在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。

而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核越多,并行处理的程序越多,能大大的提高电脑运行的效率。

注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。

线程与进程
  • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
  • 线程:是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

进程
在这里插入图片描述
线程
在这里插入图片描述
进程与线程的区别

  • 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
  • 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。

**注意:**下面内容为了解知识点

1:因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于 CPU 的调度,程序员是干涉不了的。而这也就造成的多线程的随机性。

2:Java 程序的进程里面至少包含两个线程,主进程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个线程,java 本身具备了垃圾的收集机制,所以在 Java 运行时至少会启动两个线程。

3:由于创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是创建多进程。

线程调度:

  • 分时调度

    ​ 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

  • 抢占式调度

    ​ 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
    在这里插入图片描述

创建线程类
自定义线程

多线程的开发步骤:

  1. 自己创建一个类继承Thread,然后重写run方法
  2. 创建线程的对象
  3. 调用start方法开启线程
public class Demo01 {
    public static void main(String[] args) {

        //创建线程对象
        MyThread mt = new MyThread();
        //开启线程
        mt.start();


        for (int i = 0; i < 1000; i++) {
            System.out.println("小强"+i);
        }


    }
}
public class MyThread extends Thread {

    @Override
    public void run() {
        //线程的执行体,用来放线程的逻辑

        for (int i = 0; i < 1000; i++) {
            System.out.println("旺财"+i);
        }

    }
}

在这里插入图片描述

线程的执行流程

主线程执行后期间创建了线程并启动了线程,存在主线程和子线程同时执行。等两个线程都执行完毕,进程执行完毕。
在这里插入图片描述

线程的内存原理分析

进程:执行一个进程后,该进程有自己独立的内存空间【包含了栈,堆,方法区】

线程:线程是进程的一部分,是进程中栈内存的一部分,每个线程运行都会有自己独立的栈空间。堆,方法区两个区域是被所有该进程的线程所共享。
在这里插入图片描述

线程类的使用

线程的对象的两种创建方式:

线程的创建方式1
  • public Thread():分配一个新的线程对象。
  • public Thread(String name):分配一个指定名字的新的线程对象。
    在这里插入图片描述
    以上两种构造方法直接创建线程对象,没有意义。给其子类使用

所以以上两个构造方法要有存在的意义,需要子类去使用。

【代码实践】

public class MyThread  extends Thread{
    //子类种使用Thread的两个构造方法如下:
    public MyThread() {
        super(); //public Thread()
    }

    public MyThread(String name) {
        super(name); //public Thread(String name)
    }

    //自己重写run方法才有存在的意义
    @Override
    public void run() {

    }
}
线程的创建方式2
  • public Thread(Runnable target):分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

Runnable 是一个接口,里面有一个抽象方法

void run()

在这里插入图片描述

直接定义一个类实现Runnable

/*
当前类不是线程类,而是线程将要执行的任务
 */

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        //线程要执行的逻辑
        for (int i = 0; i < 1000; i++) {
            System.out.println("小强"+i);
        }
    }
}

/*

- public Thread(Runnable target):分配一个带有指定目标新的线程对象。
- public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

 */
public class Demo01 {
    public static void main(String[] args) {
        //创建线程执行的任务
        Runnable target = new MyRunnable();

        //创建线程对象
        Thread t1 = new Thread(target);
        t1.start();
        //创建线程对象
        Thread t2 = new Thread(target, "小强");
        t2.start();


    }
}

借助Runnable创建匿名子类

public class Demo02 {

    public static void main(String[] args) {
        //Runnable匿名子类的方式创建

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
            //些线程的逻辑
                System.out.println("匿名子类方式1");
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //些线程的逻辑
                System.out.println("匿名子类方式2");

            }
        },"线程的名字");
        t2.start();

    }

}
Thread中常用的方法
  1. public String getName():获取当前线程名称。
  2. public void start():导致此线程开始执行; Java虚拟机调用此线程的run方法。
  3. public void run():此线程要执行的任务在此处定义代码。
  4. public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
  5. public static Thread currentThread():返回对当前正在执行的线程对象的引用。

【代码实践】

/*
1. public String getName():获取当前线程名称。
2. public void start():导致此线程开始执行; Java虚拟机调用此线程的run方法。
3. public void run():此线程要执行的任务在此处定义代码。
4. public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
5. public static Thread currentThread():返回对当前正在执行的线程对象的引用。

 */
public class Demo01 {
    public static void main(String[] args) {
        //获取主线程的名字:
        //1.先获取主线程的对象
        Thread mt = Thread.currentThread();
        //2.调用getName方法
        String name = mt.getName();
        System.out.println("name = " + name);//main


        //执行10此for循环,每1s执行一次
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

线程安全

线程的安全性问题

在这里插入图片描述
【代码演示】

public class Demo01 {
    public static void main(String[] args) {
        Ticket task = new Ticket();//卖票任务

        //线程执行的任务是同一个
        new Thread(task, "【窗口1】").start();
        new Thread(task, "【窗口2】").start();
        new Thread(task, "【窗口3】").start();


    }
}
/*
模拟卖票任务
 */
public class Ticket implements Runnable {
    private int ticketNum = 100;
    @Override
    public void run() {
        Thread t = Thread.currentThread();
        String name = t.getName();
        //卖票
        while (true) {

            if (ticketNum > 0) {
                System.out.println(name+"::"+ticketNum);
                //票卖一张,减一张
                ticketNum--;
            }


            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }



    }
}

同步

线程的同步

线程安全性问题导致的核心原因: 一个线程在操作一个资源时,被其他线程插足。

要解决线程安全问题,就要保证一个线程在操作一个资源的时候,要独享资源。

线程同步方案:
1)同步代码块
2)同步方法
3)锁机制

同步代码块

【格式】

synchronized(同步锁){
  
     需要同步操作的代码
       
}

同步锁:

  1. 就是一个任意类型的对象
  2. 多个线程需要使用相同的锁

【卖票问题的解决】

将操作资源的代码用同步代码块包裹起来了

/*
模拟卖票任务
 */
public class Ticket implements Runnable {
    private int ticketNum = 100;
    private Object lock = new Object();
    @Override
    public void run() {
        //锁不能在本方法中创建,否则线程会各自有自己的锁
        Thread t = Thread.currentThread();
        String name = t.getName();
        //卖票
        while (true) {

            synchronized (lock) {//上锁
                //被同步的代码,此时只会有一个线程在执行
                if (ticketNum > 0) {
                    System.out.println(name+"::"+ticketNum);
                    //票卖一张,减一张
                    ticketNum--;
                }

            }//释放锁

            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }



    }
}
同步方法

【格式】

修饰符 synchronized 返回值类型 方法名(参数列表){
  同一时刻只能被一个线程使用
}

【锁对象】

  1. 如果是静态方法:锁对象就是类的.Class对象【类型一旦加载到方法区,就会自己创建一个Class对象】,字节码对象

  2. 如果非静态方法:锁对象就是this对象

【代码实践】

/*
模拟卖票任务
 */
public class Ticket implements Runnable {
    private int ticketNum = 100;
    @Override
    public void run() {
        Thread t = Thread.currentThread();
        String name = t.getName();
        //卖票
        while (true) {

            sellTicket(name);


            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

    }

    //被synchronized关键字修饰符的方法,就是同步方法
     private synchronized void sellTicket(String name) {
        if (ticketNum > 0) {
            System.out.println(name+"::"+ticketNum);
            //票卖一张,减一张
            ticketNum--;
        }
    }


}
同步锁 Lock

Lock锁机制:

Lock是一个接口,用来模拟synchronized锁定操作

void lock();//获取锁
void unLock();//释放锁

创建对象时可以借助其子类:ReentrantLock

Lock lock = new ReentrantLock();

【使用格式】

lock.lock(); //获取锁    synchronized(锁对象){
需要同步的代码
lock.unLock(); //释放锁  } 

【代码实践】

/*
模拟卖票任务
 */
public class Ticket implements Runnable {
    private int ticketNum = 100;
    Lock LOCK = new ReentrantLock();
    @Override
    public void run() {
        Thread t = Thread.currentThread();
        String name = t.getName();
        //卖票
        while (true) {
            LOCK.lock();//获取锁
            if (ticketNum > 0) {
                System.out.println(name+"::"+ticketNum);
                //票卖一张,减一张
                ticketNum--;
            }
            LOCK.unlock();//释放锁
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程状态

线程状态分析

线程六种状态
在这里插入图片描述
在这里插入图片描述

等待和唤醒

等待和唤醒指代的是两种方法,一种是让线程进入等待状态的方法,一种是让线程唤醒其他线程的方法

这些方法都来自于同步锁:
1)等待方法:

public void wait() : 让线程进入无限等待状态,会释放当前线程的锁资源,需要其他线程唤醒
public void wait(long time) : 让线程进入计时等待状态,也会释放锁资源,如果计时结束前有其他线程可以唤醒,过了指定时间后会自动唤醒

2)唤醒方法:

public void notify() : 唤醒其他单个正在等待线程
public void notifyAll(): 唤醒所有在等待的线程

注意:

1)等待和唤醒都要使用同一个锁对象执行
2)这些方法都得放到同步代码中

【无限等待】

/*
无限等待:
1.锁对象调用wait方法

唤醒无限等待的线程:
1.锁对象调用notify/notifyAll

 */
public class Demo01 {
    public static void main(String[] args) {
        //锁对象
        Object LOCK = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("进入线程【AAA】");
                synchronized (LOCK) {
                    System.out.println("线程AAA释放锁进入无限等待");

                    try {
                        LOCK.wait();//进入无限等待,会释放锁,阻塞
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程AAA获取了锁继续执行");
                }
                System.out.println("结束线程【AAA】");
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("进入线程【BBB】");
                synchronized (LOCK) {
                    System.out.println("BBB线程唤醒其他线程");
                    LOCK.notify(); // 不会释放锁
                    System.out.println("BBB没有释放锁,继续执行");

                    try {
                        Thread.sleep(2000); //不会释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("结束线程【BBB】");
            }
        }).start();
    }
}

在这里插入图片描述
【计时等待】

public class Demo01 {
    public static void main(String[] args) {
        Object LOCK = new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (LOCK) {
                    System.out.println("进入计时等待2s");
                    try {
                        LOCK.wait(2000);//释放锁,2秒后被唤醒重新获取锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("计时等待结束,重新获取锁执行");
                }
            }
        }).start();
    }
}
等待唤醒案例
吃包子案例

在这里插入图片描述
【代码实践】

public class Demo01 {
    public static void main(String[] args) {
        //定义锁
        Object LOCK = new Object();
        //定义资源
        ArrayList<String> baozi = new ArrayList<>();

        new Thread(new Consumer(LOCK,baozi)).start();//消费者线程
        new Thread(new Producer(LOCK,baozi)).start();//生产者线程


    }
}

生产者

/*
生产者:

定义锁
定义资源
 */
public class Producer implements Runnable{
    private Object LOCK ; //锁
    private ArrayList<String> baozi;//资源

    private int count = 0;
    public Producer(Object LOCK, ArrayList<String> baozi) {
        this.LOCK = LOCK;
        this.baozi = baozi;
    }

    @Override
    public void run() {
        //模拟生产包子
        while (true) {
            //开始同步
            synchronized (LOCK) {
                if (baozi.size() > 0) {
                    try {
                        LOCK.wait(); //包子容器有包子,休息一会儿
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //做包子
                count++;
                System.out.println("【师傅生产包子】::"+count);
                baozi.add(count+"");
                //唤醒吃货去吃包子
                LOCK.notify();
            }

            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }


    }
}

消费者

/*
消费者:

定义锁
定义资源
 */
public class Consumer implements Runnable{
    private Object LOCK ; //锁
    private ArrayList<String> baozi;//资源


    public Consumer(Object LOCK, ArrayList<String> baozi) {
        this.LOCK = LOCK;
        this.baozi = baozi;
    }

    @Override
    public void run() {
        //模拟吃包子
        while (true) {
            //开始同步
            synchronized (LOCK) {
                if (baozi.size() == 0) {
                    try {
                        LOCK.wait(); //包子容器没有包子,休息一会儿
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                String baozi = this.baozi.remove(0);
                System.out.println("【吃货吃包子】::"+baozi);

                //唤醒师傅做包子
                LOCK.notify();
            }

            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值