Java Day19

Java

day19_2023.9.20

多线程

程序、进程、线程

程序:程序其实是指令和数据的有序集合,本身没有特殊含义,是一个静态概念
进程:进程是执行程序的一次执行过程,它是一个动态的概念,本质上其实是系统的资源分配单位,当我们运行一个程序后,这个程序的进程就会启动。
线程:线程其实是进程中的CPU调度和执行的单位,进程中可以包含若干个线程,一个进程最少有一个线程

在同一个进程中,可以执行多个任务,而每个任务就可以看成是一个线程
比如: 放音乐的时候,可以一边放音乐,一边点击菜单,或者一边下载其他音乐
总结 :
进程其实就是指运行中的程序,特点 :动态、独立、并发
线程是进程内部的一个执行单元,是程序中某一个功能的单一顺序控制流程

面试题:进程和线程的区别

1,根本区别 : 进程是资源的分配单位 ,线程是资源的执行单位
2,开销:
进程具有独立的代码和数据空间,进程间的切换开销较大
线程可以看做是轻量的进程,每个线程有独立的运行区域,线程之间开销小
3,所处环境:
进程是运行在操作系统中的多个任务
线程是在同一个程序中,有多个顺序流同时执行
4,内存分配 :
进程在运行的时候,系统会为每个进程分配不同的内存区域
线程的内存是由CPU分配的,

5,包含关系:
进程包含线程
没有线程的进程,可以看做是单线程的,如果一个进程有多个线程,执行过程就是多条线的

Java中的线程

主线程

Java中main()方法就是主线程的入口

其他的子线程运行,必须在main()线程中,所以,main()方法必须是最后完成执行的,因为它要执行各种关闭动作。

即使在程序运行的时候,没有自定线程,程序中也包含多个线程,比如 gc程序

将来,在一个程序中,如果开辟了多个线程,那么线程的运行由CPU(调度器)来完成调度,先后顺序不能人为干预

将来有多个线程的时候,如果对同一份资源操作,会存在资源抢夺的问题(线程不安全问题),需要加入并发控制。

Java中线程的创建和启动

Java中创建线程的方式 一共有四种:

1,继承Thread类

2,实现Runnable接口

3,实现Callable接口

4,使用线程池Executors工具类创建线程池

继承Thread类实现多线程

构造方法
Thread()
分配一个新的 Thread对象。
Thread(Runnable target)
分配一个新的 Thread对象。
Thread(Runnable target, String name)
分配一个新的 Thread对象。
Thread(String name)
分配一个新的 Thread对象。

继承Thread类创建多线程步骤

1,自定义一个类,继承Thread,并且在类中,重写run方法,run方法中,存放的是你需要实现的具体的逻辑代码。
2,创建自定义的线程子类对象
3,通过线程子类对象,调用start()方法,来启动子线程
为什么是调用start()方法 ?
线程调用start()方法之后,表示该线程已经准备好被执行了,然后再有JVM调用run()方法,
如果直接在main()方法中,调用run()方法,那么run()方法就被当做普通方法执行了

public class MyThread extends Thread{
    @Override
    public void run() {
        //System.out.println("子线程--" + Thread.currentThread().getName() + "--在运行");
        for (int i = 0; i < 5; i++) {
            System.out.println("线程--" + Thread.currentThread().getName() + "--在运行");
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
      /*  //获取当前执行的线程对象
        Thread thread = Thread.currentThread();
        System.out.println(thread.isAlive()); //true
        System.out.println("当前运行的线程是:" + thread.getName());

        //给当前线程重新设置个名字
        thread.setName("主线程");
        System.out.println("当前运行的线程是:" + thread.getName());

        //创建子线程对象,通过start()方法启动线程
        //MyThread myThread = new MyThread();
        //myThread.start();
        new Thread(new MyThread(),"创建的子线程1").start();
        new Thread(new MyThread()).start();*/

        for (int i = 0; i < 5; i++) {
            System.out.println("线程--" + Thread.currentThread().getName() + "--在运行");
        }

        MyThread myThread = new MyThread();
        //如果直接调用run()方法,其实这个run()方法被当做普通方法调用
        //这个时候,也是main线程在执行
        //myThread.run();
        myThread.start();
    }
}

继承Thread创建多线程,并创建2个子线程,一个线程叫 :小明,一个线程叫:小红,
分别让这两个线程循环5次,输出以下内容: 当前线程名 : xxx ,正在被调用

public class ThreadDemo1 extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("当前的线程:" + Thread.currentThread().getName()
            +"正在被调用");
            try {
                Thread.sleep(5000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class TestDemo1 {
    public static void main(String[] args) {
        new Thread(new ThreadDemo1(),"小明").start();
        new Thread(new ThreadDemo1(),"小红").start();
    }
}

实现Runnable接口创建线程

实现步骤:

1,自定义一个类,实现Runnable接口,重写run()方法

2,创建实现类的对象,并以这个对象,作为new Thread()的参数,构造出Thread类的对象

3,通过Thread类的对象调用start()方法

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "--" + i);
        }

    }
}
public class RunnableTest {
    public static void main(String[] args) {
        //直接创建出来的Runnable接口对象,不能直接调用start()方法
        MyRunnable myRunnable = new MyRunnable();
        //myRunnable.
        //先创建Thread对象,将线程对象传进去
        new Thread(myRunnable).start();
    //创建Thread对象的时候,直接存入Runnable对象,并取名
        new Thread(new MyRunnable(),"线程2").start();
    }
}
使用匿名内部类实现
public class RunnableDemo01 {
    public static void main(String[] args) {
        //通过匿名内部类的方式实现多线程
       new Thread(new Runnable() {
           @Override
           public void run() {
               for (int i = 0; i < 5; i++) {
                   System.out.println(Thread.currentThread().getName() + "--" + i);
               }
           }
       }).start();
    }
}

继承Thread和实现Runnable接口的区别

1,继承Thread类
将来就不能继承其他类了
编写简单,可以直接操作线程对象,无须再通过Thread类去调用其他方法
2,实现Runnable接口
将来还可以继承其他类
可以实现对象的数据共享(会存在并发问题)
将来使用Runnable接口的方式实现多线程比较常用

初识线程安全问题

public class TicketRunnable implements Runnable {
    private int ticketNum = 10;
    @Override
    public void run() {
        while (true){
            if (ticketNum <=0 ){
                break;
            }
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()
            +"---->抢到了第"+ticketNum-- +"张票");
        }
    }

    public static void main(String[] args) {
        //这里操作的不是相同的对象,所以他们输出的是30条内容
        //new Thread(new TicketRunnable(),"小明").start();
        //new Thread(new TicketRunnable(),"小红").start();
        //new Thread(new TicketRunnable(),"黄牛").start();

        //在外面创建对象,将对象传入new Thread中,创建多个子线程
        //这时候,多个子线程就会共享这个对象中的数据
        TicketRunnable ticketRunnable = new TicketRunnable();

        new Thread(ticketRunnable,"小明").start();
        new Thread(ticketRunnable,"小红").start();
        new Thread(ticketRunnable,"黄牛").start();
    }
}

线程的生命周期

在这里插入图片描述
**新生状态 :**使用new关键字建立一个线程对象,该线程对象就处于新生状态
处于新生状态的线程,拥有自己的内存空间,通过调用start方法进入就绪状态
就绪状态 : 新建的线程,通过调用start方法,进入就绪状态
就绪状态表示,线程可运行了,但是还没被分配到cpu,处于就绪队列,等待系统分配CPU
当系统选定一个等待的线程,就会从就绪进入执行状态,这个动作也称为cpu调度
**运行状态:**运行状态的线程,获得了cpu时间片,执行自己的run()方法中的代码,
直到完成任务死亡或者等资源而阻塞
阻塞状态:运行状态的线程,在某些情况下,会进入阻塞状态
阻塞分为三种 :
1,等待阻塞 : 调用wait方法会出现
2,同步阻塞: 调用方法的时候,方法上加了synchronized关键字
3,其他阻塞 : 调用sleep()方法、join()方法、或者io阻塞等,会发生
死亡状态: 线程的生命周期最后一个阶段,死亡的原因 :
1,所有内容正常执行完毕了,线程会死亡
2,线程执行被强制终止
3,线程抛出异常未被捕获

public class RunnableDemo implements Runnable {

    @Override
    public void run() {
        System.out.println("线程执行了");
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程在经过短暂的休眠后,重新执行了");
    }
}

public class RunnableTest {
    public static void main(String[] args) {
        RunnableDemo runnableDemo = new RunnableDemo();
        Thread thread = new Thread(runnableDemo);
        System.out.println("线程被创建");
        thread.start();
        System.out.println("线程准备就绪");
    }
}

线程调度相关方法

void setPriority(int newPriority) 更改此线程的优先级。

public class RunnableDemo implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "--" + i);
        }
    }
}

public class RunnableTest {
    public static void main(String[] args) {

        RunnableDemo runnableDemo = new RunnableDemo();

        //Thread thread1 = new Thread(new RunnableDemo(), "线程1");
        //Thread thread2 = new Thread(new RunnableDemo(), "线程2");
        Thread thread1 = new Thread(runnableDemo, "线程1");
        Thread thread2 = new Thread(runnableDemo, "线程2");

        thread1.setPriority(Thread.MAX_PRIORITY); //设置线程1 的优先级
        thread2.setPriority(Thread.MIN_PRIORITY);
        //设置优先级并不代表,优先级高的会先执行完
        thread1.start();
        thread2.start();
    }
}

void join() 等待这个线程死亡。

public class JoinTest {
    public static void main(String[] args) throws InterruptedException {
        //创建子线程并执行
        Thread t = new Thread(new RunnableDemo(),"子线程");
        t.start();
        //主线程main执行的内容
        for (int i = 0; i < 10; i++) {
            if (i == 5){
                t.join();  //使用join方法,完成插队的操作
            }
            System.out.println(Thread.currentThread().getName() + "--" + i);
        }
    }
}

static void yield() 对调度程序的一个暗示,即当前线程愿意出让当前使用的处理器。

public class JoinTest {
    public static void main(String[] args) throws InterruptedException {
        //创建子线程并执行
        Thread t = new Thread(new RunnableDemo(),"子线程");
        t.start();
        //主线程main执行的内容
        for (int i = 0; i < 10; i++) {
            if (i == 5){
                t.yield(); //线程礼让
                //执行yield()方法,不代表后面的执行全部让给其他线程
                //只是会出让下一次执行机会,执行完后,cpu继续执行调度
            }
            System.out.println(Thread.currentThread().getName() + "--" + i);
        }
    }
}

void setDaemon(boolean on) 将此线程标记为 daemon线程或用户线程。 当程序中正在运行的线程都是守护线程,那么JVM将退出(程序结束)
这个方法,必须要在线程启动前调用
常用的JVM垃圾回收线程、内存管理线程,都是守护线程

public class DaemonDemo implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("守护线程在运行");
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


public class DaemonTest {
    public static void main(String[] args) {
        System.out.println("程序开始");
        DaemonDemo daemonDemo = new DaemonDemo();
        Thread t = new Thread(daemonDemo);
        t.setDaemon(true);  //设定守护线程

        t.start();  //启动线程

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程运行结束");
    }
}

线程的安全性问题

在多线程运行的环境下,程序运行的结果,和我们预期的结果不相符(排除错误问题),就可以看作是线程安全问题。
线程安全问题出现后,不太好解决,因为每次运行的结果可能都会不一样,问题不容易复现,解决比较困难,所以,我们应该在编写代码阶段,就将问题解决。

出现线程安全问题的根本原因,就是因为它们执行的步骤不是原子性的操作,所有会有其他线程读取到中间的执行步骤。
将来可以通过加上synchronized关键字解决线程安全问题

public class ThreadA {
    //临界资源,多个线程共享访问的资源
    int num;
    public int getNum(){
        return num;
    }
    public synchronized int updateNum(){
        //临界资源发生写的操作
        //先计算num+1
        //把结果赋值给num
        return num++;
    }
}

public class ThreadB extends Thread {
    ThreadA ta = new ThreadA();
    @Override
    public void run() {
        while (true){
            System.out.println("现在运行的是:"
            +Thread.currentThread().getName() + ",num的值=====" +
                    ta.updateNum());
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ThreadB tb = new ThreadB();
        new Thread(tb).start();
        new Thread(tb).start();
        new Thread(tb).start();
        new Thread(tb).start();
    }
}

线程安全问题发生的条件 ?

1,多线程的环境下

2,存在临界资源,并且多个线程会去共享访问这个资源

3,存在并发写临界资源的情况

线程同步

为什么要并发编程

充分利用多核CPU的计算能力,通过并发可以将多核CPU计算的性能发挥到极致

方便业务的拆分,提升系统的性能

并发编程可能会遇到各种问题,比如 :线程安全、死锁、内存泄露…

并发编程的三要素 :

**原子性:**一个或者多个操作,要么全部执行成功,要么全部执行失败

**可见性:**一个线程对共享变量的修改,另一个线程能够立刻看到

**有序性:**程序执行的顺序,按照代码先后顺序执行

并发编程的问题怎么解决?

使用线程同步,给线程执行的代码加锁

线程同步 :就是线程的一种等待机制,如果将来有多个线程需要访问同一个对象,就会进入这个对象的等待池,形成队列,前面的线程使用完了,下个线程再使用

Java中实现线程同步:

Java中,为了保证数据在方法中访问的正确性,加入了锁机制(synchronized、Lock),将来当一个线程获取到对象的锁之后,就把资源独占,其他线程必须等这个线程将锁释放,才能使用

线程同步的问题:

1,一个线程获得锁,其他的线程只能等待,挂起,相对来说,运行速度会慢一些

2,在多线程竞争下,加锁、释放锁、都会导致比较多的上下文切换、调度的延迟,引起性能问题

3,优先级高的线程,可能会等待一个优先级低的线程,导致优先级倒置,引起性能问题

Synchronized的用法

synchronized关键字,是Java中用来控制线程同步的

将来可以用来修饰方法、变量

synchronized在jdk1.6之后,被引入了大量的优化,用来减少锁操作的开销,所以现在synchronized比较常用

1,直接将synchronized用在方法上,称为同步方法

写法: public synchronized void method(){ }

synchronized修饰的方法,锁的是这个类的对象,每个对象对应了一把锁,每个synchronized方法,都需要获取到调用该方法的对象的锁,才能执行,否则线程就会阻塞

2,synchronized修饰静态方法 : 是给 Class类对象加锁,也就是给当前类加锁,也会作用于类的每个实例对象

写法: public synchronized static void method(){ }

3,synchronized用来修饰代码块,称为同步代码块

写法: synchronized(对象){}

修饰的对象可以称为同步监视器,一般使用都是将共享资源对象,放到这里,作为同步监视器

public class TicketRunnable implements Runnable {
    private int ticketNum = 10;

    boolean flag = true;
    @Override
    public void run() {
        while (flag) {
            buyTicket();
        }
    }
    //写一个买票方法
    public synchronized void buyTicket(){
            //买票结束的判断
            if (ticketNum <=0 ){
                flag = false;
                return;
            }
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()
                    +"---->抢到了第"+ticketNum-- +"张票");

    }

    public static void main(String[] args) {

        //在外面创建对象,将对象传入new Thread中,创建多个子线程
        //这时候,多个子线程就会共享这个对象中的数据
        TicketRunnable ticketRunnable = new TicketRunnable();

        new Thread(ticketRunnable,"小明").start();
        new Thread(ticketRunnable,"小红").start();
        new Thread(ticketRunnable,"黄牛").start();
    }
}
public class TicketRunnable implements Runnable {
    private int ticketNum = 10;

    boolean flag = true;
    Object obj =  new Object();
    @Override
    public void run() {
        while (flag) {
            buyTicket();
        }
    }
    //写一个买票方法
    public void buyTicket(){
        synchronized (obj){
            //买票结束的判断
            if (ticketNum <=0 ){
                flag = false;
                return;
            }
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()
                    +"---->抢到了第"+ticketNum-- +"张票");

        }

    }

    public static void main(String[] args) {

        //在外面创建对象,将对象传入new Thread中,创建多个子线程
        //这时候,多个子线程就会共享这个对象中的数据
        TicketRunnable ticketRunnable = new TicketRunnable();

        new Thread(ticketRunnable,"小明").start();
        new Thread(ticketRunnable,"小红").start();
        new Thread(ticketRunnable,"黄牛").start();
    }
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值