Java - JavaSE - 多线程

多线程

多线程创建的三种方式:

  1. 继承 Thread 类,重写 run 方法。
  2. 实现 Runnable 接口。
  3. 实现 Callable 接口。这个方法比较编写代码比较复杂,是 JDK 1.5 出来的。

方式 1 和 方式 2 的区别:

  1. extends 是继承,一般要增强类的功能使用,重写或添加方法。
  2. implements 是实现接口。
  3. 单继承和多实现。

Thread 类源码分析

从官方文档得到:

  • JVM 是支持多线程的。
  • 线程是有优先级的。当在线程中创建一个线程对象时,新对象的优先级和当前线程相同,当且仅当线程是守护线程,新的线程对象才能是守护线程。
  • 创建线程的方式:继承 Thread 类;实现 Runnable 接口。
//继承 Thread 类
class PrimeThread extends Thread {
    long minPrime;
    PrimeThread(long minPrime) {
        this.minPrime = minPrime;
    }

    public void run() {
        // compute primes larger than minPrime
        . . .
    }
}

//给出调用方法
PrimeThread p = new PrimeThread(143);
p.start();
//实现 Runnable 接口
class PrimeRun implements Runnable {
    long minPrime;
    PrimeRun(long minPrime) {
        this.minPrime = minPrime;
    }

    public void run() {
        // compute primes larger than minPrime
        . . .
    }
}

//给出调用方法
PrimeRun p = new PrimeRun(143);
new Thread(p).start();

从源码级别可以看到这两种创建线程方法的区别,一个是直接创建继承 Thread 类的子类对象,子类对象可以直接调用 start 方法;另一个是使用了 Thread 类不同的构造器,传入的对象是一个实现了 Runnable 接口的类对象。

Thread.State - Thread 的内部枚举类

Thread 类从源码可以看到,线程一共有六种状态,分别是:

public enum State {
    //A thread that has not yet started is in this state. 
    //新建,刚刚 new 出来,还没有调用 start 方法
    NEW,
    
    //A thread executing in the Java virtual machine is in this state. 
    //线程在虚拟机中正在运行
    RUNNABLE,
    
    //A thread that is blocked waiting for a monitor lock is in this state.
    //被锁住了
    BLOCKED,
    
    //A thread that is waiting indefinitely for another thread to perform a particular action is in this state. 
    //处于等待状态
    WAITING,
    
    //A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
    //确定时间的等待状态
    TIMED_WAITING,
    
    //A thread that has exited is in this state. 
	//线程已经退出,即终结了
    TERMINATED;
}

感兴趣的同学可以再去看看源码。

synchronized

synchronized 简单解读

原理可以看这篇博客 https://www.cnblogs.com/paddix/p/5367116.html ,Object 类有一个隐式的 monitor,在调用 synchronized 修饰的方法时,会调用 monitorenter 指令,此时犹如房门上锁,房内存放的都是 synchronized 方法,这种加锁的方式很粗暴,业内称重量级锁。这种说法很容易理解,只需要选择一个方法使用却将很多同步方法一起锁住,妨碍了其他方法的调用,在并发编程中很少使用这种加锁方式。

Object 类的 notify notifyAll 和 wait 方法也需要 monitor 的作用,因此在使用这三个方法需要在 synchronized 修饰的方法或代码块下,由于三个方法都是 Object 类的 final 方法,意味着 Java 世界所有的对象都有这三个方法。

测试:不同线程调用同一对象的同步方法和非同步方法

public class SynchronizedTest01 {

    public static void main(String[] args) {
        Print print = new Print();
        new Thread(() -> {
            print.printA();
        }, "A").start();

        new Thread(() -> {
            print.printB();
        }, "B").start();


        try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            print.printA();
        }, "A").start();

        new Thread(() -> {
            print.printX();
        }, "X").start();
    }
}

class Print {
    synchronized void printA() {
        System.out.println("我是 " + Thread.currentThread().getName() + " 线程,我已经调用了 printA() ,但是我要休息 3s");
        try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("A");
    }

    synchronized void printB() {
        System.out.println("我是 " + Thread.currentThread().getName() + " 线程,我等了 A 线程 3s ,这货终于好了,我赶紧打印");
        System.out.println("B");
    }

    void printX() {
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("我是 " + Thread.currentThread().getName() + " 线程,我看 A 线程要休息 3s ,我休息 1s 后赶紧打印");
        System.out.println("X");
    }
}

输出结果:对照 code 和输出的内容可以很好的理解。

我是 A 线程,我已经调用了 printA() ,但是我要休息 3s
A
我是 B 线程,我等了 A 线程 3s ,这货终于好了,我赶紧打印
B
我是 A 线程,我已经调用了 printA() ,但是我要休息 3s
我是 X 线程,我看 A 线程要休息 3s ,我休息 1s 后赶紧打印
X
A

结论:同一个对象,其同步方法不能同时调用,非同步方法不受此限制。

posted @ 2020-01-02 11:23  学习趁早  阅读(151)  评论(0编辑  收藏  举报