Java线程基础

【韩顺平讲Java】一天学会线程 Thread Synchronized 互斥锁 进程 并行 并发 死锁等笔记

线程

线程介绍

进程
  1. 进程是指运行中的程序,操作系统为进程分配内存空间
  2. 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有他自身产生、存在和消亡的过程。
线程
  1. 线程由进程创建的,是进程的一个实体

  2. 一个进程可以拥有多个线程

  3. 单线程:同一时刻,只允许执行一个线程

  4. 多线程哼:同一时刻,可以执行多个线程

  5. 并发**:同一时刻,多个任务交替执行**,造成一种“貌似同时”的错觉,简单来说,单核CPU实现的多任务就是并发。(eg:人只有一个大脑,一心多用)

  6. 并行:**多个任务同时执行。**多核CPU可以实现并行。

  7. 并发和并行可以在同一台电脑中存在

    java获取电脑进程信息

    public class RuntimeTest {
        public static void main(String[] args) {
            //获取java程序关联的运行时对象
            var rt=Runtime.getRuntime();
            //获取当前电脑的cpu数-->核心数:即可同时执行的进程数availableProcessors
            System.out.println("处理器数量:"+rt.availableProcessors());
            System.out.println("空闲内存数:"+rt.freeMemory());
            System.out.println("总内存数:"+rt.totalMemory());
            System.out.println("可用最大内存数:"+rt.maxMemory());
        }
    }
    

线程基本使用

在这里插入图片描述

  1. 继承Thead 类,重写run方法
  2. 实现Runnable接口,重写run方法
线程使用案例-继承Thead 类,重写run方法
/*
 * 演示通过继承Thead类 创建线程
 * */

import jdk.swing.interop.SwingInterOpUtils;

public class Thead01 {
    public static void main(String[] args) {
        //创建一个cat对象,可以当做线程使用
        //crtl+j 显示所有快捷键的快捷键

        Cat cat = new Cat();
        //start() 源码
        /*
        (1)
        public synchronized void start() {
              start0();
        }
       (2)
        //start0 是本地方法,是JVM调用,底层是c/c++实现
        //真正实现多线程的作用,是start0(),而不是run方法。实际上是start0()中以多线程的机制调用run方法
         private native void start0();
        */

        cat.start(); //启动线程,会自动调用run方法
        //cat.run(); //run方法就是一个普通的方法,没有真正启动一个线程,就会把run方法执行完毕,才向下执行。相当于串行化,而不是多线程
        //说明:当main线程启动一个子线程 Thread-0, 主线程不会阻塞,会继续执行

        System.out.println("主线程" + Thread.currentThread().getName() + "继续执行");
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程 i=" + i);
            //让主线程休眠
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//1. 当一个类继承了Thead类,该类就可以当成线程使用
class Cat extends Thread {
    @Override
    public void run() { //重写run方法,实现自己的业务逻辑
        int time = 0;
        while (true) {
            //该线程每隔1秒,在控制台输出“喵喵,我是小猫咪”
            System.out.println("喵喵,我是小猫咪" + (++time) + " 线程名称:" + Thread.currentThread().getName());
            //让线程休眠1秒 crtl+alt+t

            try {
                Thread.sleep(1000); //单位是毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (time == 8) {
                break;
            }
        }
    }
}
线程使用案例-实现Runnable接口,重写run方法
//实现Runnable接口,重写run方法
public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        // dog.start(); 这里不能调用start
        //创建一个Thread对象,把 dog对象(实现Runnable),放入Thread
        Thread thread = new Thread(dog);
        thread.start();

        Tiger tiger = new Tiger();
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
    }
}

class Animal{}
class Tiger extends Animal implements Runnable{
    @Override
    public void run() {
        System.out.println("老虎嗷嗷叫.....");
    }
}

//线程代理类,模拟了一个最简单的Thread类
class ThreadProxy implements Runnable{ //可以把ThreadProxy类 当成Thread
    private Runnable target=null;

    @Override
    public void run() {
          if(target != null){ //动态绑定(运行时类型 tiger)
              target.run();
          }
    }

    public ThreadProxy(Runnable target) {
        this.target = target;
    }

    public void start(){
        start0(); //这个方法是真正实现多线程的方法
    }

    private void start0(){
        run();
    }

}

class Dog implements Runnable{ //通过实现Runnable接口,开发线程
    int count=0;
    @Override
    public void run(){ //普通方法
        while (true){
            System.out.println("小狗旺旺叫...Hi"+(++count)+" "+Thread.currentThread().getName());

            //休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (count==10){
                break;
            }
        }
    }
}
线程使用案例-多线程执行
/*
 * main 线程启动两个子线程
 * */

public class Thread03 {
    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);
        thread1.start();
        thread2.start();
    }
}

class T1 implements Runnable {
    int count = 0;

    @Override
    public void run() {
        while (true) {
            //每隔1秒输出 “hello world”,输出10次
            System.out.println("hello world" + (++count) + "~~~");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (count == 10) {
                break;
            }
        }
    }
}

class T2 implements Runnable {
    int count = 0;

    @Override
    public void run() {
        while (true) {
            //每隔1秒输出 “hi”,输出5次
            System.out.println("hi" + (++count) + "~~~");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (count == 5) {
                break;
            }
        }
    }
}
继承Thread vs 实现Runnable 的区别

实现Runnable接口方式更适合多个线程共享一个资源的情况,并且避免了单继承的限制。建议使用Runnable接口。

T3 t3=new T3("hello");
Thread thread1 = new Thread(t3);
Thread thread2 = new Thread(t3);
thread1.start();
thread2.start();

System.out.println("主线程完毕");
售票问题

售票系统-模拟编程三个售票窗口售票100,分别使用 继承Thread和Runnable方式实现,并分析问题?

问题–>会出现超卖

package ticket;

public class SellTicket {
    public static void main(String[] args) {
        /*
        //测试
        SellTicket01 sellTicket01 = new SellTicket01();
        SellTicket01 sellTicket02 = new SellTicket01();
        SellTicket01 sellTicket03 = new SellTicket01();

        //这里会出现超卖....
        sellTicket01.start();
        sellTicket02.start();
        sellTicket03.start();
        */

        SellTicket02 sellTicket02 = new SellTicket02();
        Thread thread1 = new Thread(sellTicket02);
        Thread thread2 = new Thread(sellTicket02);
        Thread thread3 = new Thread(sellTicket02);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

//使用Thread
class SellTicket01 extends Thread {
    public static int ticketNum = 100; //让多个线程共享 ticketNum

    @Override
    public void run() {
        while (true) {

            if (ticketNum < 0) {
                System.out.println("售票结束.....");
                break;
            }

            //休眠50秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口" + Thread.currentThread().getName() + "卖出一张票" + ",剩余票数=" + (--ticketNum));
        }
    }
}

//使用Runnable
class SellTicket02 implements Runnable {
    public static int ticketNum = 100; //让多个线程共享 ticketNum

    @Override
    public void run() {
        while (true) {

            if (ticketNum < 0) {
                System.out.println("售票结束.....");
                break;
            }

            //休眠50秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口" + Thread.currentThread().getName() + "卖出一张票" + ",剩余票数=" + (--ticketNum));
        }
    }
}

线程常用方法

常用基本方法

  1. setName //设置线程名称,使之与参数name相同
  2. getName //返回该线程的名称
  3. start //使该线程开始执行;java 虚拟机底层调用该线程的 start0 方法
  4. run //调用线程对象run方法
  5. setPriority //更改线程优先级
  6. getPriority //设置线程优先级
  7. sleep //在指定的毫秒数内让当前正在执行的线程休眠 (暂停执行)
  8. interrupt //终端线程
注意事项和细节
  1. start 底层会创建新的线程,调用run ,run就是一个简单的方法调用,不会启动新线程
  2. 线程优先级的范围 MIN_PRIORITY=1、 NORM_PRIORITY=5 、 MAX_PRIORITY=10;
  3. interrupt ,中断线程 , 但没有真正的结束线程。所以一般用于 中断正在休眠的线程
  4. sleep :线程的静态方法,使当前线程休眠
package method;

public class ThreadMethod01 {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.setName("laohan");
        t.setPriority(Thread.MIN_PRIORITY);
        t.start(); //启动子线程

        // 主线程打印5个hi,然后就中断 子线程的休眠
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " hi" + i);
        }
        System.out.println(t.getName() + " 线程的优先级=" + t.getPriority());
        t.interrupt(); //当执行到这里,就会中断子线程的休眠

    }
}

class T extends Thread {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + " 吃包子~~~" + i);
            }
            try {
                System.out.println(Thread.currentThread().getName() + " 休眠中~~~");
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + " 被 interrupt了~~~");
            }
        }
    }
}

yield和join

  1. yield: 线程的礼让。让出cpu, 让其他线程执行,但礼让时间不确定,所以也不一定礼让成功【礼让是自己让 Thread.yield() 】
  2. join: 线程的插队。一旦插队成功,则肯定执行完插入线程的所有任务【插队是别人插 t2.join();】
package method;

public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
        T2 t2 = new T2();
        t2.start();

        for (int i = 1; i <= 20; i++) {
            Thread.sleep(1000);
            System.out.println("主线程(小弟)" + " 吃了" + i + "包子");
            if (i == 5) {
                System.out.println("主线程(小弟)让 子线程(老大)先吃");
                //join() 插队,一定成功
                //t2.join(); //让子线程插队,执行完毕

                Thread.yield(); //礼让,不一定成功

                System.out.println("子线程(老大)吃完了 主线程(小弟)再吃");
            }
        }
    }
}

class T2 extends Thread {
    @Override
    public void run() {

        for (int i = 1; i <= 20; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程(老大)吃了" + i + "包子");
        }
    }
}

用户线程和守护线程

  1. 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束

  2. 守护线程: 一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束 【Thread.setDaemon(true): 设置为守护线程】

  3. 常见的守护线程:垃圾回收机制

    package method;
    
    public class ThreadMethod03 {
        public static void main(String[] args) throws InterruptedException {
            MyDaemonThread myDaemonThread = new MyDaemonThread();
            //如果希望当main线程结束后,子线程自动结束
            //只需将子线程设为守护线程即可
            myDaemonThread.setDaemon(true);
            myDaemonThread.start();
            for (int i = 0; i < 10; i++) {
                System.out.println("辛苦工作....");
                Thread.sleep(1000);
            }
    
        }
    
    }
    class MyDaemonThread extends Thread {
        public void run() {
            for (; ; ) {//无限循环
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("快乐聊天,哈哈哈哈~~~");
            }
        }
    }
    
    

线程生命周期

JDK 中用Thread.State 枚举表示了线程的几种状态

在这里插入图片描述

首先线程的状态可以分为6态或7态,具体状态如下

6态

  • New:新建状态
  • Runnable:可运行状态
  • Terminated:终止状态
  • Waiting:等待状态
  • TimedWaiting:超时等待状态
  • Blocked:阻塞状态

7态

  • New:新建状态
  • Ready:就绪状态
  • Running:运行状态
  • Terminated:终止状态
  • Waiting:等待状态
  • TimedWaiting:超时等待状态
  • Blocked:阻塞状态

其实6态与7态差别不大,只不过7态把Runnable可运行状态,拆解成了Ready就绪状态与Running运行状态。

线程状态转换图

在这里插入图片描述

Sychronized

线程同步机制

  1. 在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
  2. 也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成

同步具体方法-Sychronized

1. 同步代码块
    
    Sychronized (对象){ //得到对象的锁,才能操作同步代码
          //需要被同步代码;
}

2. Sychronized 还可以放在方法声明中,表示整个方法-为同步方法
    
    public synchronized void m(String name){
      //需要被同步代码;
}
【售票问题】-同步机制:不会出现超卖
package ticket;

public class SellTicket {
    public static void main(String[] args) {
   
        //测试
        SellTicket03 sellTicket03 = new SellTicket03();
        Thread thread1 = new Thread(sellTicket03);
        Thread thread2 = new Thread(sellTicket03);
        Thread thread3 = new Thread(sellTicket03);
        thread1.start();
        thread2.start();
        thread3.start();

    }
}

//使用Runnable,使用synchronized 实现线程同步
class SellTicket03 implements Runnable {
    public static int ticketNum = 100; //让多个线程共享 ticketNum
    private boolean loop=true;
    
    public synchronized void sell(){//同步方法,在同一时刻,只能有一个线程执行sell方法
        if (ticketNum <= 0) {
            System.out.println("售票结束.....");
            loop=false;
            return;
        }
        //休眠50秒
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("窗口" + Thread.currentThread().getName() + "卖出一张票" + ",剩余票数=" + (--ticketNum));
    }

    @Override
    public void run() {
        while (loop) {
              sell();  //sell 是一个同步方法
        }
    }
}

互斥锁

  1. java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
  2. 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
  3. 关键字synchronized 来与对象的互斥锁联系。当某个对象用 synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问
  4. 同步的局限性: 导致程序的执行效率降低
  5. 同步方法(非静态的)的锁可以是this, 也可以是其他对象(要求是同一对象)
  6. 同步方法(静态的)的锁为当前类本身
public class SellTicket {
    public static void main(String[] args) {

        //测试
        SellTicket03 sellTicket03 = new SellTicket03();
        Thread thread1 = new Thread(sellTicket03);
        Thread thread2 = new Thread(sellTicket03);
        Thread thread3 = new Thread(sellTicket03);
        thread1.start();
        thread2.start();
        thread3.start();

    }
}

//使用Runnable,使用synchronized 实现线程同步
class SellTicket03 implements Runnable {
    public static int ticketNum = 100; //让多个线程共享 ticketNum
    private boolean loop=true;
    Object object =new Object();

    //同步方法(静态的)的锁为当前类本身
    //1.  public synchronized static void m1(){} 锁加在 SellTicket03.class
    //2. 如果在静态方法中,要实现一个同步代码块,互斥锁还是 SellTicket03.class
    public synchronized static void m1(){

    }
    public static void m2(){
        synchronized (SellTicket03.class){
            System.out.println("m2");
        }
    }


    //1. public synchronized void sell(){}  就是一个同步方法
    //2. 这时锁在 this对象
    //3. 也可以在代码块上写 synchronized , 同步代码块, 互斥锁在this 对象
    public /*synchronized*/ void sell(){//同步方法,在同一时刻,只能有一个线程执行sell方法

        synchronized(/*this*/ object){      //改为  nchronized(new Object()) 又出现超卖现象,锁失效,因为不是同一个锁对象
        if (ticketNum <= 0) {
            System.out.println("售票结束.....");
            loop=false;
            return;
        }
        //休眠50秒
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("窗口" + Thread.currentThread().getName() + "卖出一张票" + ",剩余票数=" + (--ticketNum));
        }
    }

    @Override
    public void run() {
        while (loop) {
              sell();  //sell 是一个同步方法
        }
    }
}

注意事项和细节

  1. 同步方法如果没有使用static修饰: 默认对象为this

  2. 如果同步方法使用static修饰,默认锁对象: 当前类.class

  3. 实现的步骤:

    • 需要先分析上锁的代码
    • 选择同步代码块或同步方法
    • 要求多个线程的锁对象为同一个即可! 【多个线程争夺同一个锁对象】

死锁

/*
 * 模拟线程死锁
 * */
public class DeadLock {
    public static void main(String[] args) {
        //模拟死锁下现象
        DeadLockDemo A = new DeadLockDemo(true);
        A.setName("A");
        DeadLockDemo B = new DeadLockDemo(false);
        B.setName("B");
        A.start();
        B.start();

    }
}

//线程
class DeadLockDemo extends Thread {
    static Object o1 = new Object();  //保证多线程,共享一个对象,这里使用static
    static Object o2 = new Object();
    boolean flag;

    public DeadLockDemo(boolean flag) {
        this.flag = flag;
    }

    public void run() {
        //下面这个业务逻辑的分析
        //1. 如果flag 为 T, 线程A 就会先得到o1 对象锁, 然后尝试去获取 o2 对象锁
        //2. 如果线程A 得不到 o2 对象锁,就会Blocked
        //3. 如果flag 为 F, 线程B 就会先得到o2 对象锁, 然后尝试去获取 o1 对象锁
        //4. 如果线程B 得不到 o1 对象锁,就会Blocked
        if (flag) {
            synchronized (o1) { //对象互斥锁,下面是同步代码
                System.out.println(Thread.currentThread().getName() + "进入1");
                synchronized (o2) { //这里获得li对象的监事全
                    System.out.println(Thread.currentThread().getName() + "进入2");
                }
            }
        } else {
            synchronized (o2) {
                System.out.println(Thread.currentThread().getName() + "进入3");
                synchronized (o1) { //这里获得li对象的监事全
                    System.out.println(Thread.currentThread().getName() + "进入4");
                }
            }
        }
    }
}

释放锁

下面操作会释放锁
  1. 当前线程的同步方法、同步代码块执行结束
  2. 当前线程在同步方法、同步代码块遇到break、return
  3. 当前线程在同步方法、同步代码块中出现了未处理的Error或Exception,导致异常结束
  4. 当前线程在同步方法、同步代码块中执行了线程对象的wait() 方法,当前线程暂停,并释放锁
下面操作不会释放锁
  1. 线程执行同步方法或同步代码块时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁

  2. 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起

    • 提示:应尽量避免使用suspend()和resumen()来控制线程,方法不推荐使用
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值