Fork me on GitHub

Java线程基础学习笔记

Java线程

进程和线程

  • 进程是指运行中的程序

    进程是程序的一次执行过程,或是正在运行的一个程序,是动态过程:有它自身的产生、存在和消亡的过程

  • 线程是由进程创建的,是进程的一个实体。一个进程可以拥有多个线程

并发与并行

单线程与多线程

  • 单线程:同一时刻,只允许执行一个线程
  • 多线程:同一个时刻,可以执行多个线程

并发

  • 同一时刻,多个任务交替执行。比如单核CPU实现的多任务就是并发

并行

  • 同一时刻,多个任务同时执行。比如多核CPU可以实现并行

线程基本使用

创建线程有两种方式:

继承Thread类,重写run方法

package thread_.createThread;

/**
 * 通过继承Thread类创建一个线程
 *
 * 1.开启一个线程,该线程每隔1秒输出一句话
 * 2.当输出10次之后结束线程
 */
public class Thread01_ extends Thread{
    /**
     * 执行程序就启动了一个进程 同时开启一个main线程
     * 在main线程中 调用start方法的时候 又开启了一个子线程(Thread-0)
     */
    public static void main(String[] args) {
        //创建一个Cat对象 可以当做线程使用
        Cat cat = new Cat();
        cat.start();//启动线程 启动过程中会调用run方法
        //当main线程启动一个子线程Thread-0 主线程不会阻塞 会继续执行
        //此时主线程与子线程交替执行
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程线程:"+Thread.currentThread().getName()+i);
            //休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//1.当一个类继承了Thread 该类就可以当做线程使用
//2.会重写run方法 实现自己的业务代码 而Thread中的run方法是来自Runnable接口
class Cat extends Thread{
    @Override
    public void run() {
        int times = 0;
        while (true){
            //业务代码
            System.out.println("子线程:"+Thread.currentThread().getName()+(++times));
            //让线程休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (times == 8){
                break;//退出循环之后 线程也退出
            }
        }

    }
}

  • 为什么要调用start方法

实现Runnable接口,重写run方法

package thread_.createThread;

/**
 * 通过实现Runnable接口来创建线程
 */
public class Thread02_ {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.run();    //不能直接调用run方法 因为没有开启一个线程
        //dog.start();    //也不能调用start
        Thread thread = new Thread(dog);	//底层使用了设计模式——静态代理
        thread.start(); //在这里调用run方法
    }
}

class Dog implements Runnable{
    @Override
    public void run() {
        int count = 0;
        while (true){
            System.out.println("hello!:"+Thread.currentThread().getName()+(++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

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

代理过程

//模拟一个最简单的Thread
class ThreadProxy implements Runnable{
    private Runnable target = null;//初始化一个Runnable对象

    @Override
    public void run() {
        if (target != null){
            target.run();   //如果target不为空,就去调用run方法
        }
    }

    public ThreadProxy(Runnable target){
        this.target = target;   //将传进来的target进行赋值
    }

    public void start(){    //最终应该去引用start方法
        start0();
    }

    private void start0() {
        run();  //最终实现了一个多线程
    }
}

多个子线程

package thread_.createThread;

/**
 * 在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{

    @Override
    public void run() {
        int count = 0;
        while (true){
            System.out.println("Hello!:"+Thread.currentThread().getName()+(++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

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

class T2 implements Runnable{

    @Override
    public void run() {
        int count = 0;
        while (true){
            System.out.println("World!:"+Thread.currentThread().getName()+(++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 5){
                break;
            }
        }
    }
}

从本质上来看,继承Thread和实现Runnable来创建线程并没有什么区别,但是通过实现Runnable接口的方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制

多线程售票系统

继承Thread方式

package thread_.ticket;

/**
 * 使用多线程,模拟三个窗口同时售票
 * 三个窗口售票100张
 */
public class SellTicket {
    public static void main(String[] args) {
        SellTicket01 sellTicket01 = new SellTicket01();
        SellTicket02 sellTicket02 = new SellTicket02();
        SellTicket03 sellTicket03 = new SellTicket03();
        sellTicket01.start();
        sellTicket02.start();
        sellTicket03.start();
        //会出现线程超卖问题
    }
}


//继承Thread方式
class SellTicket01 extends Thread{
    private static Integer ticketNum = 100;//让多个线程共享num
    @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));

        }
    }
}

class SellTicket02 extends Thread{
    private static Integer ticketNum = 100;//让多个线程共享num
    @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));

        }
    }
}

class SellTicket03 extends Thread{
    private static Integer ticketNum = 100;//让多个线程共享num
    @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));

        }
    }
}

以上代码出现了超卖问题

实现Runnable接口

package thread_.ticket;

public class SellTicket02_ {
    public static void main(String[] args) {
        SellTicket2_ ticket2 = new SellTicket2_();
        new Thread(ticket2).start();
        new Thread(ticket2).start();
        new Thread(ticket2).start();


    }
}

class SellTicket2_ implements Runnable{

    private Integer ticketNum = 100;//让多个线程共享num
    @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));

        }
    }
}

以上代码也出现了超卖问题

线程终止

通知线程退出

  • 当线程完成任务之后,会自动退出
  • 可以使用变量控制run方法退出的方式来停止线程,即通知方式
package thread_.exit_;

public class ThreadExit_ {
    public static void main(String[] args) throws InterruptedException {
        AThread aThread = new AThread();
        aThread.start();
        for (int i = 0; i < 60; i++) {
            Thread.sleep(50);//main线程休眠
            System.out.println("线程"+Thread.currentThread().getName()+i);
            if (i == 20){
                Thread.sleep(10000);
                System.out.println("=========================休眠中===========================");
                aThread.setLoop(false);
            }
        }
    }
}

class AThread extends Thread {
    private Boolean loop = true;
    @Override
    public void run() {
        while (loop){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("线程"+Thread.currentThread().getName()+"运行中...");
        }
    }

    public void setLoop(Boolean loop) {
        this.loop = loop;
    }
}

线程常用方法

  • setName 设置线程名称
  • getName 返回线程名称
  • start 使线程开始执行
  • run 调用线程对象run的方法
  • setPriority 更改线程优先级
  • getPriority 获取线程优先级
  • sleep 在指定时间内让线程休眠
  • interrupt 终端线程

线程中断

中断线程一般用于正在休眠的线程

线程插队

  • yield:线程礼让。让出CPU,给其他线程执行
  • join:线程插队。插队成功之后,插入的线程先执行完所有的任务
package thread_.exit_;

public class ThreadJoin_ {

    public static void main(String[] args) throws InterruptedException {
        JoinThread thread = new JoinThread();
        thread.start();
        for (int i = 0; i < 20; i++) {
            Thread.sleep(1000);
            System.out.println("主线程执行:"+i);

            if (i == 5){
                thread.join();
            }
        }
    }
}

class JoinThread 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("子线程执行"+i);
        }
    }
}

守护线程

用户线程

守护线程

一般为工作线程服务,当所有的用户线程结束,守护线程自动结束

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

package thread_.exit_;

/**
 * 守护线程
 * 当主线程结束 子线程还在执行
 * 实现:只要主线程结束 即使子线程无限执行 也要结束
 */
public class ThreadGuard {
    public static void main(String[] args) throws InterruptedException {
        SonThread sonThread = new SonThread();
        //如果希望 当主线程结束 子线程也自动结束
        //只需要将子线程设置成守护线程
        sonThread.setDaemon(true);
        sonThread.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("主线程在执行:"+i);
            Thread.sleep(1000);
        }
    }
}

class SonThread extends Thread{
    int num = 0;
    @Override
    public void run() {
        for (; ; ) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("子线程执行:"+(++num));
        }
    }
}

线程七大状态

线程生命周期

  • NEW

    尚未启动的线程

  • RUNNABLE

    可运行状态

  • BLOCKED

    被阻塞等待监视器锁定的线程

  • WAITING

    正在等待另一个线程执行特定动作的线程

  • TIME_WAITING

    正在等待另一个线程执行动作达到指定等待时间的线程

  • TERMINATED

    已退出的线程

线程同步机制

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

public class SellTicket02_ {
    public static void main(String[] args) {
        SellTicket2_ ticket2 = new SellTicket2_();
        new Thread(ticket2).start();
        new Thread(ticket2).start();
        new Thread(ticket2).start();

    }
}

//使用同步方法synchronized
class SellTicket2_ implements Runnable{

    private Integer ticketNum = 10000000;    //让多个线程共享num

    public synchronized void  sell (){  //在同一个时刻 只能有一个线程来执行run方法
        if (ticketNum <= 0){
            System.out.println("售票结束...");
            return;
        }

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

        System.out.println(Thread.currentThread().getName()+" 售出一张票" +
                ",剩余票数"+(--ticketNum));
    }
    @Override
    public synchronized void run() {
        while (true){
            sell();
        }
    }
}

互斥锁

  • 在Java中引入互斥锁,用来保证数据操作的完整性
  • 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任意时刻,只能有一个线程访问该对象
  • 关键字synchonrized来与对象的互斥锁联系。当某个对象用synchonrized来修饰的时候,表明该对象上锁了
  • 局限性: 程序执行效率降低了
  • 同步方法的锁(非静态)可以使this,也可以是其他对象
  • 同步方法的锁(静态)为当前类对象

线程死锁

多个线程都占用了对方的锁资源,但不肯相让,导致了死锁

释放锁

以下操作会释放锁

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

以下操作不会释放锁

  • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。尽量避免使用suspent()和resume()方法来控制线程
posted @ 2021-09-13 19:44  Blueshadow^_^  阅读(68)  评论(0编辑  收藏  举报