Java并发编程之线程属性和方法

1. 线程方法

1.1 方法概览

类.方法 简介
Thread.sleep() 线程休眠,不释放锁
Thread.join() 等待其它线程执行完毕
Thread.yield() 放弃已经获取到的CPU资源
Thread.currentThread() 获取当前线程的引用
Thread.start()/Thread.run() 启动线程和线程执行内容
Thread.interrupt() 中断线程
Thread.stop()/Thread.suspend()/Thread.resume() 已废弃
Object.wait()/Object.notify()/Object.notifyAll() 线程等待和唤醒

1.2 wait()/notify()/notifyAll()

  • 阻塞阶段
    执行了wait方法意味着当前线程进入阻塞阶段,直到发生以下四种情况之一,该线程才会被唤醒:
    1. 另外一个线程调用这个对象的notify方法且刚好被唤醒的是本线程;
    2. 另外一个线程调用这个对象的notifyAll方法;
    3. 过了wait规定的超时时间,如果传入0,就是永久等待;
    4. 线程自身调用了interrupt方法,wait方法因为响应中断被唤醒,进而抛出异常;
  • 唤醒阶段
    1. notify方法会唤醒单个等待线程,如果有多个等待线程,则随机选取一个;
    2. notifyAll方法会唤醒所有等待线程;

1.3 wait()/notify()/notifyAll()方法详解

下面展示wait和notify的基本用法:

public class WaitNotify {

    private static Object object = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    System.out.println(Thread.currentThread().getName() + "开始执行");
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "重新获取到锁");
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    object.notify();
                    System.out.println(Thread.currentThread().getName() + "调用了notify()");
                }
            }
        });
        t1.start();
        Thread.sleep(200);
        t2.start();
    }
}
Thread-0开始执行
Thread-1调用了notify()
Thread-0重新获取到锁

可以看出,t1线程先启动并执行了wait方法,释放了锁挂起,然后t2线程启动,获取到了锁并执行了notify重新唤醒了t1线程,t1重新获取到锁,在挂起的位置继续执行。
下面是wait和notifyAll的代码演示:

public class WaitNotifyAll implements Runnable {

    private static Object object = new Object();

    @Override
    public void run() {
        synchronized (object) {
            System.out.println(Thread.currentThread().getName() + "获取到锁");
            try {
                object.wait();
                System.out.println(Thread.currentThread().getName() + "重新获取到锁");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        WaitNotifyAll waitNotifyAll = new WaitNotifyAll();
        Thread t1 = new Thread(waitNotifyAll);
        Thread t2 = new Thread(waitNotifyAll);
        t1.start();
        t2.start();
        Thread.sleep(200);
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    object.notifyAll();
//                    object.notify();
                    System.out.println(Thread.currentThread().getName() + "拿到了锁并执行了notifyAll");
                }
            }
        });
        t3.start();
    }
}
Thread-0获取到锁
Thread-1获取到锁
Thread-2拿到了锁并执行了notifyAll
Thread-1重新获取到锁
Thread-0重新获取到锁

首先t1拿到了锁并执行了wait方法挂起t1线程,然后t2拿到了锁并执行了wait方法挂起t2线程,然后t3拿到了锁并执行了notifyAll唤醒t1和t2线程,t2线程先重新获取到了锁,待执行完毕之后释放了锁,t2重新获取到了锁。
如果改用notify方法则随机唤醒t1和t2线程其中的一个,如果t1被唤醒,则t2陷入了无尽的等待,整个进程永远不会结束。
下面展示wait方法只能释放调用wait方法的对象的锁,不能释放其他对象的锁:

public class ReleaseOwnMonitor {

    private static final Object lockA = new Object();
    private static final Object lockB = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lockA) {
                    System.out.println(Thread.currentThread().getName() + "获取到lockA");
                    synchronized (lockB) {
                        System.out.println(Thread.currentThread().getName() + "获取到lockB");
                        try {
                            System.out.println(Thread.currentThread().getName() + "释放了lockA");
                            lockA.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lockA) {
                    System.out.println(Thread.currentThread().getName() + "获取到lockA");
                    System.out.println(Thread.currentThread().getName() + "正在尝试获取lockB...");
                    synchronized (lockB) {
                        //看看t2能不能获取到lockB
                        System.out.println(Thread.currentThread().getName() + "获取到lockB");
                        try {
                            lockA.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        t1.start();
        Thread.sleep(500);
        t2.start();
    }
}
Thread-0获取到lockA
Thread-0获取到lockB
Thread-0释放了lockA
Thread-1获取到lockA
Thread-1正在尝试获取lockB...

结果表明,t2一直在尝试获取lockB,但是由于在t1中只有lockA执行了wait方法,释放的也只是lockA的锁,t1仍然持有lockB的锁,所以t2获取不到lockB的锁。

1.4 wait()/notify()/notifyAll()特点和性质

  1. 用这些方法之前,当前线程必须首先获取到monitor锁
  2. 如果使用notify方法只会唤醒一个等待线程
  3. 都属于Object类,所有的类继承与Object,所有的对象都可以调用这些方法
  4. 类似功能的Condition
  5. 线程同时持有多个锁,使用wait方法只释放调用wait方法的对象的锁
  6. 线程执行object.wait后,进入到WAITING状态,而线程被唤醒后,因为是另外一个线程持有相同的锁才能执行object.notify方法进行唤醒,另外一个线程可能还没有执行完,所以当前线程通常没有立即获取到monitor锁,那么当前线程会从WAIT状态进入到BLOCKED状态,抢到锁后会进入到RUNNABLE状态
  7. 发生异常,可以直接到TERMINATED状态

1.5 sleep()方法详解

sleep可以让线程进入Waiting状态,并且不占用CPU资源,但是不释放锁,直到规定时间后再执行,休眠期间如果被中断,会抛异常并清除中断状态。

public class SleepDontReleaseMonitor implements Runnable {

    @Override
    public void run() {
        syn();
    }

    private synchronized void syn() {
        System.out.println(Thread.currentThread().getName() + "获取锁");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "退出同步代码块");
    }

    public static void main(String[] args) {
        SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
        new Thread(sleepDontReleaseMonitor).start();
        new Thread(sleepDontReleaseMonitor).start();
    }

}
public class SleepDontReleaseLock implements Runnable {

    private static final Lock lock = new ReentrantLock();
    @Override
    public void run() {
        lock.lock();
        System.out.println(Thread.currentThread().getName() + "获取到了锁");
        try {
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + "苏醒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
        new Thread(sleepDontReleaseLock).start();
        new Thread(sleepDontReleaseLock).start();
    }
}

可以看出,无论是synchronized和lock,执行sleep方法都不释放锁。

1.6 join()方法详解

  • 作用:因为新的线程加入了我们,我们要等他执行完再出发
  • 用法:main等待t1执行完毕,如下所示:
public class JoinBasic implements Runnable {

    @Override
    public void run() {
        System.out.println("子线程开始执行");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("子线程执行结束");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new JoinBasic());
        thread.start();
        thread.join();
        System.out.println("主线程执行结束");
    }
}

子线程开始执行
子线程执行结束
主线程执行结束
  • join执行期间,是主线程等待子线程,所以我们看看join期间主线程是什么状态:
public class JoinMainState implements Runnable {
    Thread mainThread = Thread.currentThread();
    @Override
    public void run() {
        System.out.println("子线程开始执行");
        try {
            Thread.sleep(1000);
            System.out.println(mainThread.getState());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("子线程执行结束");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new JoinMainState());
        t1.start();
        t1.join();
    }
}
子线程开始执行
WAITING
子线程执行结束

结果表明,主线程等待子线程join期间,主线程是WAITING状态

  • 因为线程执行完毕之后会自动调用notifyAll方法,所以我们可以让主线程等待,然后等子线程执行完后自动唤醒主线程,所以替代方法如下:
public class JoinPrinciple {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "子线程执行完毕");
            }
        });
        thread.start();
        System.out.println("等待所有子线程运行完毕");
//        thread.join();
        //给thread对象上锁,thread对象执行完run方法后会自动调用thread.notifyAll()方法唤醒其他线程,即唤醒主线程
        synchronized (thread) {
            thread.wait();
        }
        System.out.println("所有子线程运行完毕");
    }
}
等待所有子线程运行完毕
Thread-0子线程执行完毕
所有子线程运行完毕
        synchronized (thread) {
            thread.wait();
        }

上面这段代码也是join方法中的核心方法。

1.7 yield方法详解

  • 作用:释放我的CPU时间片,线程状态仍然是Runnable状态,不会释放锁,不会阻塞,即便释放了CPU时间片,下一个可能还是我
  • yield和sleep区别:sleep期间,线程调度器不会把这个线程调度起来,而yield期间,立刻又可以被调度起来。

2. 线程属性

2.1 属性概览

属性名称 用途
ID 标识不同的线程
Name 自定义线程名称
isDaemon 是否是守护线程
Priority 告诉线程调度器,用户希望哪些线程多运行、哪些少运行,共10个等级,默认值是5

2.2 ID

从1开始,我们自己创建线程的ID早已不是2,因为JVM在后开启了很多其它子线程。

public class threadId {
    public static void main(String[] args) {
        Thread thread = new Thread();
        System.out.println("子线程:" + thread.getId());
        System.out.println("主线程:" + Thread.currentThread().getId());
    }
}
子线程:11
主线程:1

2.3 Name

对于没有指定名字的线程,会有默认名称

public Thread() {
  init(null, null, "Thread-" + nextThreadNum(), 0);
}
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
  return threadInitNumber++;
}

2.2 isDaemon

给用户线程提供服务,没有用户线程就没有守护线程

  1. 线程类型默认继承自父线程,守护线程创建的线程自动是守护线程,用户线程同理

  2. 被谁启动,通常,所有守护线程由JVM启动,JVM启动时,会有一个用户线程,main函数

  3. 不影响JVM退出,所有用户线程都结束,即便有守护线程,JVM也会退出

整体无区别,唯一的区别在于是否影响JVM的退出,守护线程不会影响。

2.2 Priority

10个级别,默认5

    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

程序设计不应依赖于优先级,跟操作系统息息相关,不可靠

  • 因为不同操作系统不一样
  • 优先级会被操作系统改变
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章