Java 多线程(基础)

线程相关概念

 线程基本使用

第一种方法:继承 Thread 类 

public class Test {
    public static void main(String[] args) throws Exception {
        /*
         * 当运行 Java 程序时,相当于启动了一个进程
         * 而这个进程首先会创建一个主线程(即进入 main 方法,也叫 main 进程)
         * Cat 对象继承了 Thread ,可以当做线程使用
         * cat.start(); 又启动了一个新的线程,即主线程中又开启了一个子线程
         * 此时主线程和子线程交替进行,这就是并发(单核 CPU)
         * 注意点:
         * 当主线程执行完毕后,如果子线程没有执行完,则子线程会继续执行
         * 而进程会等所有的线程执行完后才会销毁,即主线程销毁时子线程有可能还在运行
         * 子线程也可以再启动线程
         * start() 方法的源码:
         * public synchronized void start(){
            start0();
         * }
         * // start0 是一个本地方法,由 JVM 调用,底层是 c/c++ 实现
         * // 所以真正实现多线程的是 start0() ,而不是 run()
         * private native void start0();
         */
        // 创建 Cat 对象,当做线程使用
        Cat cat = new Cat();

        /*
         * 如果直接调用 cat.run(); 实际上执行的还是 main 线程,不会开启一个新的线程
         * 此时程序将会阻塞在 run 方法处,不会往下执行代码
         * 即将会把 cat.run(); 方法执行完毕后才会继续往下执行
         */
        cat.start(); // 启动线程 -> 最终会执行 cat.run()

        // 当 main 线程启动一个子线程 Thread-0 ,主线程不会阻塞,会继续执行
        // 这时,主线程和子线程是交替执行的
        // Thread.currentThread().getName() 返回线程的名字
        System.out.println("主线程继续执行" + Thread.currentThread().getName()); // 主线程继续执行main
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程 i=" + i);
            // 让主线程休眠 1s
            Thread.sleep(1000);
        }
    }
}

/*
 * 当一个类直接继承了 Thread 类,该类就可以当做线程使用
 * 此时重写 run 方法,写上自己的业务逻辑代码
 * run 方法是 Thread 类实现了 Runnable 接口里的 run 方法
 * run 方法源码如下:
 * 
 * @Override
 * public void run() {
     if(target != null){
       target.run();
     }
 * }
 */
class Cat extends Thread {
    int times = 0;

    @Override
    public void run() { // 重写 run 方法,写自己的业务逻辑
        while (true) {
            // 该线程每隔一秒在控制台输出“喵喵,我是小猫咪”
            System.out.println(Thread.currentThread().getName() + "喵喵,我是小猫咪" + (++times));
            // 让该线程休眠一秒
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }

            if (times == 80) {
                break; // 当 times 到 80,退出 while 循环,退出线程
            }
        }
    }
}

 第二种方法:实现 Runnable 接口

public class Test {
    public static void main(String[] args) throws Exception {
        Dog dog = new Dog();
        // dog.start(); // Runnable 接口只有一个 run 方法,不能调用 start 方法
        // dog.run(); // 也不能直接调用 run 方法,会造成阻塞,并不会真正开启线程,原因同上个案例
        // 应该通过创建 Thread 对象进而调用 start 方法
        // 此处是底层使用了一种设计模式(代理模式)实现的
        Thread thread = new Thread(dog);
        thread.start();

        // 简单模拟代理模式
        Tiger tiger = new Tiger(); // Tiger 实现了 Runnable 接口,同上面的 Dog 类
        // 因为 Tiger 实现了 Runnable 接口,所以可以作为参数传给 ThreadProxy 类
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        // 方法的调用顺序: start() -> start0() -> run()
        // 所以 thread.start(); 同理
        threadProxy.start();
    }
}

class Dog implements Runnable { // 通过实现 Runnable 接口,开发线程
    int count = 0;

    @Override
    public void run() { // 重写 run 方法,写自己的业务逻辑
        while (true) {
            // 该线程每隔一秒在控制台输出“喵喵,我是小猫咪”
            System.out.println(Thread.currentThread().getName() + " hi~ 小狗汪汪叫" + (++count));
            // 让该线程休眠一秒
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }

            if (count == 10) {
                break; // 当 count 到 10,退出 while 循环,退出线程
            }
        }
    }
}

class Animal {
}

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

// 模拟实现 Runnable 接口开发线程的机制
// 线程代理类,模拟了一个极简的 Thread 类
class ThreadProxy implements Runnable {
    private Runnable target = null; // 设置一个 Runnable 类型的属性

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

    public void start() {
        // start0 方法才是真正实现多线程的方法
        // 它其实是由 JVM 调用的,在这里只是简单的模拟
        start0();
    }

    public void start0() {
        // 在 start0 方法中调用 run 方法
        run();
    }

    @Override
    public void run() {
        if (target != null) {
            target.run(); // 动态绑定
        }
    }
}

public class Test {
    public static void main(String[] args) {
        // 创建两个线程
        T1 t1 = new T1();
        T2 t2 = new T2();

        // 启动线程 t1
        Thread thread = new Thread(t1);
        thread.start();

        // 启动线程 t2
        t2.start();
    }
}

class T1 implements Runnable {
    int num = 0;

    @Override
    public void run() {
        while (true) {
            System.out.println("hello world!" + (++num));
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }

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

class T2 extends Thread {
    int count = 0;

    @Override
    public void run() {
        while (true) {
            System.out.println("hi~" + (++count));

            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }

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

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

 对第二点的说明:

如以上 T1 类为例:

// 首先要先创建 T1 类对象

T1 t1 = new T1();

// 启动线程 t1

Thread thread1 = new Thread(t1);

thread1.start();

// 再次启动线程

Thread thread2 = new Thread(t1);

thread2.start();

说明:此时 thread1 和 thread2 都是利用 t1 创建的线程,所以共享 t1 里的 run 方法,达到了共享资源的目的,而且是采用实现接口的形式,打破了单继承的限制

线程终止

public class Test {
    public static void main(String[] args) throws Exception {
        // 创建线程
        T t = new T();

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

        // 让主线程休眠 5s
        Thread.sleep(5000);
        /*
         * 如果希望 main 线程去控制 t 线程的终止,必须修改 loop 变量
         * 让 t 退出 run 方法,从而终止 t 线程
         * 这种方式称为通知方式
         */
        t.setLoop(false);
    }
}

class T extends Thread {
    private int count = 0;
    private boolean loop = true; // 控制变量

    @Override
    public void run() {
        while (loop) {
            System.out.println("hi~" + (++count));

            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

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

 线程常用的方法

public class Test {
    public static void main(String[] args) throws Exception {
        // 创建线程
        T t = new T();

        t.setName("zs"); // 设置线程名称
        t.setPriority(Thread.MIN_PRIORITY); // 设置线程优先级,此处设置为最低的优先级
        System.out.println(t.getName() + "的优先级是" + t.getPriority()); // 获取线程名称
        t.start(); // 启动线程 t

        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("hi~ " + i);
        }

        t.interrupt(); // 中断 t 线程的休眠
    }
}

class T extends Thread { // 自定义线程类
    int num = 0;

    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 10; i++) {
                // Thread.currentThread().getName() 获取当前线程的名字
                System.out.println(Thread.currentThread().getName() + " 吃包子~~~" + i);
            }

            try {
                System.out.println(Thread.currentThread().getName() + " 休眠中~~~");
                Thread.sleep(10000); // 休眠20秒
                num++;
            } catch (InterruptedException e) {
                // 当线程执行到一个 interrupt 方法时,就会 catch 一个异常
                // InterruptedException 捕获到了一个中断异常
                // 如果希望让某个线程提前终止休眠,可以调用 interrupt 方法
                // 可以在此处编写业务逻辑
                System.out.println(Thread.currentThread().getName() + "被 interrupt 了");
            }

            if (num == 3) {
                break;
            }
        }
    }
}

 对于 yield 方法,在资源很丰富的情况下,不需要礼让,比如有多个 CPU ,能够同时运行多个线程,不需要让某一个线程礼让另一个线程,因为完全可以同时执行多个线程

public class Test {
    public static void main(String[] args) throws Exception {
        // 创建线程
        T t = new T();
        t.start(); // 启动线程 t

        for (int i = 1; i <= 20; i++) {
            Thread.sleep(1000);
            System.out.println("主线程吃了 " + i + " 个包子");

            // 如果不加以控制,主线程和子线程将会交替进行
            if (i == 5) {
                System.out.println("主线程吃了5个包子,然后先让子线程先吃");
                // Thread.yield(); // 礼让,不一定成功
                t.join(); // 让 t 线程插队,即先让 t 线程先执行, t 线程执行完毕后主线程才继续执行
                System.out.println("子线程已经吃完包子了,主线程可以继续吃了");
            }
        }
    }
}

class T extends Thread { // 自定义线程类
    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            try {
                Thread.sleep(1000); // 休眠2秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程吃了 " + i + " 个包子");
        }
    }
}

用户线程和守护线程

public class Test {
    public static void main(String[] args) throws Exception {
        // 创建线程
        DaemonThread daemonThread = new DaemonThread();
        // 正常情况下,主线程结束时,子线程还在运行(因为子线程目前是一个无限循环的)
        // 如果希望当主线程结束后,子线程可以自动结束,只需将子线程设置成守护线程即可
        // 需要先设置,再启动
        // daemonThread.setDaemon(true);
        daemonThread.start();

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

class DaemonThread extends Thread { // 把一个线程设置为守护线程
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }

            System.out.println("子线程无限循环输出中....");
        }
    }
}

线程 7 大状态

线程状态转换图

public class Test {
    public static void main(String[] args) throws Exception {
        // 验证线程的状态
        // 创建线程
        T t = new T();
        System.out.println(t.getName() + "的状态:" + t.getState());
        t.start();

        while (Thread.State.TERMINATED != t.getState()) { // 只要 t 线程不处于终止装填
            System.out.println(t.getName() + "的状态:" + t.getState());
            Thread.sleep(1000);
        }

        System.out.println(t.getName() + "的状态:" + t.getState());
    }
}

class T extends Thread { // 把一个线程设置为守护线程
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 10; i++) {
                System.out.println("hi " + i);
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            break;
        }
    }
}

 线程同步机制

public class Test {
    public static void main(String[] args) throws Exception {
        SellTicket sellTicket = new SellTicket();
        new Thread(sellTicket).start(); // 线程1 —— 窗口1
        new Thread(sellTicket).start(); // 线程2 —— 窗口2
        new Thread(sellTicket).start(); // 线程3 —— 窗口3
    }
}

// 售票问题:当有多个线程同时购买车票时,可能会出现车票为负数的情况
// 实现接口方式,使用 synchronized 实现线程同步,解决售票问题
class SellTicket implements Runnable {
    private int ticketNum = 30; // 假设有 100 张票
    private boolean loop = true;

    /*
     * 添加了 synchronized 关键字后,将 sell 方法变成了一个同步方法
     * 此时,在同一时刻,只能有一个线程来执行 sell 方法
     */
    public synchronized void sell() {
        if (ticketNum <= 0) {
            System.out.println("车票已经售罄...");
            loop = false;
            return;
        }

        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("窗口:" + Thread.currentThread().getName() +
                "售出 1 张票,还剩 " + (--ticketNum) + " 票");
    }

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

互斥锁

 说明:在选择上锁方式时,选择同步代码块要比选择同步方法效率要高,因为同步代码块的范围相对同步方法来说要小,效率也就高一些

public class Test {
    public static void main(String[] args) throws Exception {
        SellTicket sellTicket = new SellTicket();
        new Thread(sellTicket).start(); // 线程1 —— 窗口1
        new Thread(sellTicket).start(); // 线程2 —— 窗口2
        new Thread(sellTicket).start(); // 线程3 —— 窗口3
    }
}

// 售票问题:当有多个线程同时购买车票时,可能会出现车票为负数的情况
// 实现接口方式,使用 synchronized 实现线程同步,解决售票问题
class SellTicket implements Runnable {
    private int ticketNum = 30; // 假设有 100 张票
    private boolean loop = true;
    /*
     * SellTicket sellTicket = new SellTicket(); 创建了一个对象
     * 再把该对象传给三个线程:
     * new Thread(sellTicket).start();
     * new Thread(sellTicket).start();
     * new Thread(sellTicket).start();
     * 所以三个线程操作的是同一个对象
     */
    Object obj = new Object();

    /*
     * 同步方法为静态时,互斥锁为当前类本身
     * 如以下的 func 方法的互斥锁是加在 SellTicket.class 上的
     */
    public synchronized static void func() {

    }

    /*
     * 在静态方法中实现一个同步代码块,不能用 this
     * 而是需要用 SellTicket.class
     */
    public static void func2() {
        synchronized (SellTicket.class) {
            System.out.println("func2...");
        }
    }

    /*
     * 添加了 synchronized 关键字后,将 sell 方法变成了一个同步方法
     * 此时,在同一时刻,只能有一个线程来执行 sell 方法
     * 此时锁在 this 对象身上
     */
    public /* synchronized */ void sell() {
        // 也可以在代码块上写 synchronized ,这种方式称为同步代码块,以下这种写法互斥锁还是在 this 身上
        // 也可以把互斥锁放在其他对象身上(前提要求要是同一个对象)
        synchronized (/* this */ obj) {
            if (ticketNum <= 0) {
                System.out.println("车票已经售罄...");
                loop = false;
                return;
            }

            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }

            System.out.println("窗口:" + Thread.currentThread().getName() +
                    "售出 1 张票,还剩 " + (--ticketNum) + " 票");
        }
    }

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

线程的死锁

public class Test {
    public static void main(String[] args) throws Exception {
        // 模拟死锁现象
        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
    static Object o1 = new Object();
    static Object o2 = new Object();
    boolean flag;

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

    @Override
    public void run() {
        /*
         * 假设 flag 为 true ,此时线程A 就会先得到/持有 o1 对象锁,然后尝试获得 o2 对象锁
         * 如果线程A 得不到 o2 对象锁,就会 Blocked (阻塞)
         * 假如此时 flag 为 false ,线程B 会得到 o2 对象锁,然后尝试获得 o1 对象锁
         * 如果线程B 得不到 o1 对象锁,就会 Blocked (阻塞)
         * 那么此时线程A 和线程B 互相等待对方所持有的锁,就处于死锁状态
         */
        if (flag) {
            synchronized (o1) { // 对象互斥锁,下面就是同步代码
                System.out.println(Thread.currentThread().getName() + "进入1");
                synchronized (o2) {
                    System.out.println(Thread.currentThread().getName() + "进入2");
                }
            }
        } else {
            synchronized (o2) {
                System.out.println(Thread.currentThread().getName() + "进入3");
                synchronized (o1) {
                    System.out.println(Thread.currentThread().getName() + "进入4");
                }
            }
        }
    }
}

 释放锁

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值