Maxon's Blog

Java线程基础回顾

6

Java线程基础回顾

概念

  • 程序(Program)
    • 完成特定任务,用某种语言编写的一组指令的集合.简单来说:就是我们的代码.
  • 进程
    • 进程指运行中的程序,比如我们使用的QQ,就启动了一个进程,操作系统就会为该进程分配内存空间.
    • 进程是程序的一次执行过程,或是正在运行的一个程序.是动态过程:有它滋生的生产,存在和消亡的过程.
  • 线程
    • 线程是由进程创建,是进程的一个实体
    • 一个进程可以拥有多个线程
    • **单线程:**同一时刻,只允许执行一个线程
    • **多线程:**同一时刻,可以执行多个线程
    • 并发:同一时刻,多个任务交替执行,造成一种"貌似同时"的错觉,简单来说,单核CPU实现的多任务就是并发
    • **并行:**同一时刻,多个任务同时执行,多核CPU可以实现并行
    • 宏观并行,微观并发.

线程常用方法

方法描述
setName设置线程名称,使之与参数name相同
getName返回该线程名称
start使该线程开始执行;Java虚拟机底层调用start0()方法
run调用线程对象run方法
setPriority设置线程优先级 ( MIN 1 NORM 5 MAX 10)
getPriority返回线程优先级
sleep休眠
interrupt中断线程 (不是终止)
Java
package tech.aowu.ThreadMethod;

public class ThreadMethod01 {
    public static void main(String[] args) throws InterruptedException {

        T t = new T();
        t.setName("Aowu");
        t.setPriority(Thread.MIN_PRIORITY);
        t.start();

        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println(i);
        }
        t.interrupt();//当执行到这里 就会中断t线程的休眠
    }
}
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) {
                //当该线程执行到一个interrupt方法时, 就会catch一个异常,可以加入自己的业务代码
                //捕获到InterruptedException 执行以下
                System.out.println(Thread.currentThread().getName()+"被中止...");
            }
        }
    }
}

方法描述
yield线程的礼让.让出cpu让其他线程执行,但礼让的时间不确定,也不一定成功 具体情况看CPU
join线程的插队.插队的线程一旦插入成功.则一定先执行完插入的线程所有的任务
Java
package tech.aowu.ThreadMethod;
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("主线程让子线程先吃");
                t2.join();
                System.out.println("子线程吃完");
            }
        }
    }
}
class T2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Join Thread ...... 子线程吃了: "+i);
        }
    }
}

用户线程和守护线程

  • 用户线程:也叫工作线程
  • 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
  • 常见的守护线程:垃圾回收机制
Java
package tech.aowu.ThreadMethod;

public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
        T3 t3 = new T3();
        //希望当main线程结束后,子线程也可以结束
        //只需将子线程设置为守护线程
        t3.setDaemon(true);
        t3.start();
        for (int i = 0; i < 5; i++) {
            System.out.println("main......");
            Thread.sleep(1000);
        }
    }
}
class T3 extends Thread{

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程..........");
        }
    }
}

创建线程

image-20230101133843234

继承Thread类,重写run方法

  • start()方法启动线程
  • run()方法则只是调用了run方法
  • start()方法调用start0()方法后,该线程并不一定会立马执行,只是将线程改编成可运行状态.具体什么时候执行取决于CPU,由CPU统一调度
Java
public class Thread01 {
    public static void main(String[] args) {
        Cat cat1 = new Cat();
        /*
         * public synchronized void start() {
         *      start0();
         * }
         *
         * private native void start0();
         * //start0()是本地方法,由JVM调用 底层是C/C++实现
         * //真正实现start0()方法,而不是run
         * */
        cat1.start();//启动线程
//        cat1.run();//单纯调用run方法
        //当main线程启动一个子线程Thread-1 主线程不会阻塞,回继续执行

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"主线程 i="+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//当一个类继承了Thread类,该类就可以当作一个线程
//重写run方法
//run Thread类实现了Runnable接口的run方法
class Cat extends Thread{
    int times=0;
    @Override
    public void run() {

        while (true){
            System.out.println(Thread.currentThread().getName()+" 回顾Thread"+times++);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times==8) break;
        }

    }

image-20230101135912333

实现Runnable接口,重写run方法

  • Java是单继承,在某些情况下一个类可能已经继承了父类,这时在用继承Thread类来创建线程显然是不可能了,由此,我们还可以通过实现Runnable接口来创建进程

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

        Dog dog = new Dog();
        Thread dogThread = new Thread(dog);//由于Runnable接口没有start方法所以不能直接调用  这里使用了设计模式(代理模式)
        dogThread.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("100");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


    }
}

class ThreadProxy implements Runnable{ //模拟实现代理模式   可以把ThreadProxy当作Thread

    private Runnable target=null;//属性,类型Runnable

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

    public void start(){
        start0();//实现多线程的方法
    }

    private void start0() {//只是模拟了Thread类里的start0方法
        run();
    }
    @Override
    public void run() {
        if (target!=null){
            target.run();
        }
    }
}

class Dog implements Runnable{//实现Runnable接口
    int times=0;
    @Override
    public void run() {
        while (true){
            System.out.println(Thread.currentThread().getName()+" Wang"+times++);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

实现Callable接口,重写call方法(没用过)

线程终止

  • 线程完成任务,自动退出
  • 通过使用变量来控制run方法退出,即 通知
Java
package tech.aowu.ThreadExit;

public class ThreadExit {
    public static void main(String[] args) throws InterruptedException {
        T t1 = new T();
        t1.start();
        //希望main线程控制t1线程终止,必须可以修改loop变量
        //让t1 退出run方法 从而终止 t1线程 -> 通知方式
        System.out.println("主线程休眠3s");
        Thread.sleep(3000);
        t1.setLoop(false);
    }
}
class T extends Thread{
    private int count=0;
    //设置一个控制变量
    private boolean loop=true;

    public void setLoop(boolean loop) {this.loop = loop;}
    @Override
    public void run() {
        while (loop){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" is still running... nums: "+count++);
        }
    }
}

线程生命周期

image-20230101165108118

状态描述
NEW尚未启动的线程
RUNNABLE在JVM中执行的线程,Runnable可细分Ready和Running状态
BLOCKED被阻塞等待监视器锁定的线程
WAITING正在等待另一个线程执行特定动作的线程
TIMED_WAITING正在等待另一个线程执行动作打到指定等待时间的线程
TERMINATED已退出的线程
Java
package tech.aowu.ThreadState;

public class ThreadState {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        System.out.println(t.getName()+" State: "+t.getState());
        t.start();

        while (Thread.State.TERMINATED!=t.getState()){
            System.out.println(t.getName()+" State: "+t.getState());
            Thread.sleep(500);
        }
        System.out.println(t.getName()+" State: "+t.getState());
    }
}
class T extends Thread{
    @Override
    public void run() {
        while (true){
            for (int i = 0; i < 3; i++) {
                System.out.println("i="+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            break;
        }
    }
}

image-20230101170647321

线程同步 Synchronized

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

实现同步:

  • 同步代码块

    Java
    synchronized(对象){//得到对象的锁,才能够同步代码
    	//需要被同步的代码
    }
    
  • Synchronized还可放在方法声明中,表示整个方法为同步方法

    Java
    public synchronized void method(String param){
        //需要被同步的代码
    }
    

互斥锁

  • Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
  • 每个对象都对应于一个可成为"互斥锁"的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
  • 关键字synchronized来与对象的互斥锁联系.当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
  • 同步的局限性:导致程序的执行效率降低
  • 同步方法(非静态)的锁可以是this,也可以是其他对象(要求是同一对象)
  • 同步方法(静态的)的锁为当前类本身

注意

  • 同步方法如果没有使用static修饰:默认锁对象为this
  • 如果方法使用static修饰,默认锁对象:当前类.class
  • 实现的步骤
    • 需要先分析上锁的代码
    • 选择同步代码块或同步方法
    • 要求多个线程的锁对象为同一个即可

卖票问题

  • 同步方法 public synchronized void func(){} 这时锁加在this对象 与 synchronized (this){}类似
  • 同步代码块 synchronized (object){} 锁加在object对象
  • 静态同步方法的锁为当前类本身 public synchronized static void func(){} 锁加在 currclass.class
  • 静态方法中的同步代码块锁加载当前类本身 public static void func(){synchronized (currclass.class){}}
    • 原因分析:静态方法是类方法,没有实例对象就已经加载,所以无法使用this
Java
package tech.aowu.ticket;
//使用多线程模拟10个窗口同时售票100张
public class SellTicket {
    public static void main(String[] args) {
        SellTicket03 sellTicket03 = new SellTicket03();
        for (int i = 0; i < 10; i++) {new Thread(sellTicket03).start();}
    }
}
class SellTicket04 implements Runnable{
    private  int ticketNum=30;//让多个线程共享ticketNum
    private boolean loop=true;

    Object object= new Object();

    public void sell(){

        synchronized (object){ //同步代码块 加锁在this对象
            if (ticketNum <= 0) {
                System.out.println("售罄...");
                loop = false;
                return;
            }
            System.out.println("窗口:" + Thread.currentThread().getName() + "售出一张票" + "剩余票数:" + (--ticketNum));
            //休眠50ms
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public  void run() {
        while (loop){
            sell();
        }
    }
}

//实现接口,使用同步方法synchronized实现线程同步
class SellTicket03 implements Runnable{
    private  int ticketNum=100;//让多个线程共享ticketNum
    private boolean loop=true;

    public synchronized void sell(){//方法加锁 同步方法  这时锁加在this对象  也可在代码块写synchronized,同步代码块
        if (ticketNum<=0){
            System.out.println("售罄...");
            loop=false;
            return;
        }
        System.out.println("窗口:"+Thread.currentThread().getName()+"售出一张票"+"剩余票数:"+(--ticketNum));
        //休眠50ms
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public  void run() {
        while (loop){
            sell();
        }
    }
}

//Thread方式
class SellTicket01 extends Thread{
    private static int ticketNum=100;//让多个线程共享ticketNum

    @Override
    public void run() {
        while (true){
            if (ticketNum<=0){
                System.out.println("售罄...");
                break;
            }

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

            System.out.println("窗口:"+Thread.currentThread().getName()+"售出一张票"+"剩余票数:"+(--ticketNum));

        }
    }
}
}

线程死锁

多个线程都占有了对方的锁资源,都不愿意释放,导致死锁.

Java
package tech.aowu.ticket;

public class DeadLock {
    public static void main(String[] args) {
        DeadLockDemo deadLockDemo = new DeadLockDemo(true);
        deadLockDemo.start();
        DeadLockDemo deadLockDemo2 = new DeadLockDemo(false);
        deadLockDemo2.start();
    }
}
class DeadLockDemo extends Thread{
    static Object object1=new Object();
    static Object object2=new Object();

    boolean flag;

    public DeadLockDemo(boolean flag){
        this.flag=flag;
    }
    @Override
    public void run() {
        //如果flag为true 线程A就会先得到object1对象锁 然后尝试获取object2对象锁
        //如果线程A得不到object2锁 就会进入blocked状态
        //如果flag为false 线程B就会现持有object2对象锁 然后尝试获取object1对象锁
        //如果线程B得不到object1对象锁 就会Blocked
        if (flag){
            synchronized (object1){
                System.out.println(Thread.currentThread().getName()+" 进入1");
                synchronized (object2){
                    System.out.println(Thread.currentThread().getName()+" 进入2");
                }
            }
        }else {
            synchronized (object2){
                System.out.println(Thread.currentThread().getName()+" 进入3");
                synchronized (object1){
                    System.out.println(Thread.currentThread().getName()+" 进入4");
                }
            }
        }
    }
}

释放锁

什么时候会释放锁?

  • 当前线程的同步方法,同步代码块执行结束.
  • 当前线程在同步方法,同步代码块中遇到break,return
  • 当前线程在同步方法,同步代码块中出现了未处理的Error或Exception,导致异常结束.
  • 当前线程在同步代码块,同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁.

以下操作不会释放锁!!

  • 线程执行同步代码块或同步方法时,程序调用了Thread.sleep(),Thread.yield()方法暂停当前线程的执行,不会释放锁.
  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将线程挂起,该线程不会释放锁,
    • 注意!! 尽量避免使用suspend()和resume()方法来控制线程