java 多线程基础知识

core-java
标签: #<Tag:0x00007f1d27c23e90>

#1

当我们用 java 命令启动一个 java 程序时, 就会启动一个 JVM 进程, 在一个 JVM 进程中会有多个线程. 所有 java 代码都是执行在某个线程里.

多线程种类:

首先, 很多文章中提到了主线程, 子线程的概念, 但我并没有在 Java 官方文档中找到明确的定义. 我个人认为, java 中的线程不存在主和子的关系. 很多人提到的所谓的主线程, 其实就是 java 程序的入口方法 main 方法启动时所在的线程. 但是这个线程和其他线程并没有直接关系. 即使是在 main 方法中启动的线程也不能说是 main 的子线程, 因为他们之间也没有依赖, 或其他关系(在不调用某些建立关系的方法, 比如 join() 的前提下.).

所以各个线程之间除非人为设置关系, 或使用共享资源, 否则所有线程都是活在自己的世界里, 不会对其他线程有任何影响, 也就不会存在比如主线程等待子线程这样的情况.

User Thread

  • 就是常见的线程, 用户自定义的线程线程
  • main线程是 user thread, 并且不能被设置为 daemon thread

Daemon Thread 守护线程

  • 顾名思义就是守护 user thread 的线程. 也就是说为其他线程提供服务的线程 比如 GC.
  • 用户可以自定义一个线程为守护线程
daemonThread.setDaemon(true);  
daemonThread.isDaemon(); 
  • setDaemon 方法必须在线程启动前调用, 也就是说, 不能将正在运行的线程修改为守护线程
  • 守护线程的优先级比较低
  • 如果 JVM 中只剩下守护线程, JVM 则会自动终止所有守护线程然后退出.
  • 在守护线程中产生的线程也是守护线程

Java 线程的状态:

New

  • 当我们新建一个线程对象, 但还没有调用 start() 方法时. 该线程的状态为 New.

Runnable

  • 调用 start 方法后, 线程状态转变为 Runnable.
  • Runnable 状态的线程会在线程池中等待线程调度器调用.

Running

  • ​当线程调度器选中一个线程, 开始执行 run 方法, 该线程为 Running 状态

Blocked

  • ​由于某些原因, 线程会进入等待状态. 比如调用 sleep, wait, join 等方法, 或需要调用对象锁被其他线程占用. 当等待结束后, 线程会变为 Runnable状态, 并再次进入线程池中等待调用.

Dead

  • 线程执行完成后, 或调用 stop 方法, 线程变为 Dead 状态,
  • Dead 状态的线程不能通过调用 start 方法再次回到 Runnable状态

线程的优先级

java 中的线程优先级的范围是1~10, 默认的优先级是5. 最高优先级为10, 最低为1
相关方法

getPriority()
setPriority(int newPriority)

设置优先级的作用比较有限, 也就是说可能在某些系统中, 情况下低优先级会先于高优先级执行.
想深入研究的同学可以参考这篇文章

What is Java 'thread priority'?

多线程常用方法:

start()

  • 用于启动一个线程. 它内部会通过调用 run()方法来启动线程,
  • 但并不是一旦调用 start, run 方法就会立即被调用, 只有获得CPU 资源后, 才会执行 run 方法.
  • 调用 start() 后, 线程会从 New 状态变为 Runnable
  • start 方法只能启动一个状态为新建(new)的线程, 对于已经死亡(Dead)的线程,
  • start 是无效的. 对于一个线程, start 只能调用一次.

getId()

  • 线程 ID, 一般是一个数字
System.out.println(t.getId());

getName和setName

  • 得到和设置线程的名字
t1.setName("thread 1");
System.out.println("name: " + t1.getName());

isAlive()

  • 判断当前线程是否已经是 dead 状态,
  • dead 的线程返回 false. 否则返回 true
System.out.println("run="+this.isAlive());

Thread.sleep()

  • sleep() 用于暂停一个正在执行中的线程, 并且可以指定暂停时间.
  • 在指定的时间结束后, 进程会进入Runnable 状态, 而不是直接执行. 所以线程的实际暂停时间是大于等于sleep 方法的指定时间的.
  • sleep 方法不释放锁.
  • 应避免在synchronized的代码中使用 sleep. 应为synchronized会获得对象锁, 而 sleep方法又不释放锁.
  • TimeUnit.SECONDS.sleep(y) 方法可以实现同样的功能. 这个方法会调用 sleep 方法, 所以本质上是一样的.

yield()

  • 调用yield()方法, 线程会变为 Runnable 状态 而不是 blocked 状态. 也就是说有可能一个线程调用 yield 方法进入 Runnable 后立刻又被 CPU 选装再次进入 Running 状态. 感觉就想 yield 没有任何效果.
  • 它跟sleep方法类似, 同样不会释放锁. 但 sleep 会将线程置为 blocked状态

join()

  • 把指定的线程加入到当前线程, 可以将两个并行执行的线程合并为顺序执行的线程. 比如在线程T2中调用了线程T1的Join()方法, 直到线程T1执行完毕后, 才会继续执行线程T2
  • 当线程T对象在另外一个线程, 比如 main 线程中调用 join 方法时, main 线程会得到T 线程的对象锁, 并调用 wait() 方法

wait(), notify(), notifyAll()

  • 这三个方法建议在 synchronized 中使用. 也就是说 在获得对象锁后再调用对象的 wait() notify(), notifyAll()方法
  • 在一个线程中调用object 的 wait() 会释放对象锁, 当前线程进入 blocked 状态, 等待notify(), notifyAll()方法
  • 在另外一个线程中调用 notify() 后, 对象锁不会被立即释放, 而是执行完 synchronized 代码后, 锁才会被释放. 然后 JVM 随机选择一个 wait() 的线程, 将它变为 runnable 状态, 等待 CPU 分配资源.
  • notify(), notifyAll()的区别是 notify() 会随机唤醒一个线程, 而 notifyAll()会通知所有等待的线程, 然后所有线程去争夺对象锁, 但最终, 和 notify 一样, 只有一个线程会得到锁.
  • 例子
public static void main(String[] args) throws InterruptedException {

    class Count {
        private int count;
        public int getCount() {
            return count;
        }
        public void addCount() {
            count++;
        }
    }

    Count count = new Count();

    Thread t1 = new Thread() {
        public void run() {
            synchronized (count) {
                System.out.println("T1 start");
                while (count.getCount() <= 3) {
                    try {
                        count.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("Tread 1 count is 1.");
            }
        }
    };

    Thread t2 = new Thread() {
        public void run() {
            synchronized (count) {
                System.out.println("T2 start count=" + count.getCount());
                while (count.getCount() <= 3) {
                    count.addCount();
                }

                count.notify();
                System.out.println("T2 end count=" + count.getCount());
            }
        }
    };

    t1.start();
    Thread.sleep(100);
    t2.start();
}

//打印
T1 start
T2 start count=0
T2 end count=4
Tread 1 count is 1.

最后从这里盗用一张图

参考

Multithreading in Java - JournalDev

Java Thread Primitive Deprecation

Java中守护线程的总结 - CSDN博客

What is Java 'thread priority'?