《Java 高并发》04 线程的基本操作

新建线程

新建线程很简单。只要使用new 关键字创建一个线程对象,并且调用 start 方法启动线程。

Thread t = new Thread();
t.start();

注意:run 方法不是用来启动线程。如果调用 run 方法它只会作为普通方法来执行,而不会开启线程执行。

终止线程

一般来说,线程在执行完毕后就会结束,无须手工关闭。但凡是都有例外。Thread 类提供了一个 stop 方法来终止线程。如果调用 stop 方法,就可以立即将一个线程终止。

目前 stop 方法已经过期。因为 stop 方法太过于暴力,它会把执行到一半的线程终止,此时可能会引起数据不一致问题。

举例:对象 User 有 id、name 两个属性。写线程总是把 id、name 写成相同的值。当写线程在写对象时,读线程由于无法获得锁,因此必须等待,所以读线程是看不见一个写了一半的对象。此时,写线程写完id后,很不辛被 stop,此时对象 u 的 id 为1,而 name 任然为0,出于不一致状态。而被终止的写线程简单地讲锁释放,度线程获取到锁后,读取数据,于是读到了 id=1 而 name=0 。

public class StopThreadTest {

    public static User u = new User();

    public static class User {
        private int id;
        private String name;

        public User() {
            this.id = 0;
            this.name = "0";
        }

        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }

        public void setId(int id) {
            this.id = id;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getId() {
            return id;
        }

        public String getName() {
            return name;
        }


        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }

    public static class ChangeObjectThread extends Thread {

        @Override
        public void run() {
            while (true) {
                synchronized (u) {
                    int i = (int) (System.currentTimeMillis() / 1000);
                    u.setId(i);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    u.setName(String.valueOf(i));

                }
            }
        }
    }

    public static class ReadObjectThread extends Thread {

        @Override
        public void run() {
            while (true) {
                synchronized (u) {
                    if (u.getId() != Integer.valueOf(u.getName())) {
                        System.out.println(u.toString());
                    }

                }
            }
        }
    }

    public static void main(String[] args) {
        try {
            new ReadObjectThread().start();
            while (true) {
                ChangeObjectThread thread = new ChangeObjectThread();
                thread.start();

                Thread.sleep(150);

                thread.stop();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

打印结果:

User{id=1619771639, name='1619771638'}
User{id=1619771640, name='1619771639'}

那么如果优雅的停止一个线程,又不会产生数据不一致问题?可以考虑定义一个开关,通过开关去控制。

public class StopThreadTest {

    public static User u = new User();
    public static boolean stopme = true;

    public static class User {
        private int id;
        private String name;

        public User() {
            this.id = 0;
            this.name = "0";
        }

        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }

        public void setId(int id) {
            this.id = id;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getId() {
            return id;
        }

        public String getName() {
            return name;
        }


        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }

    public static void stopMe(){
        stopme = false;
    }

    public static class ChangeObjectThread extends Thread {

        @Override
        public void run() {
            while (stopme) {
                synchronized (u) {
                    int i = (int) (System.currentTimeMillis() / 1000);
                    u.setId(i);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    u.setName(String.valueOf(i));

                }
            }
        }
    }

    public static class ReadObjectThread extends Thread {

        @Override
        public void run() {
            while (true) {
                synchronized (u) {
                    if (u.getId() == Integer.valueOf(u.getName())) {
                        System.out.println(u.toString());
                    }
                    System.out.println(u.toString());

                }
            }
        }
    }

    public static void main(String[] args) {
        try {
            new ReadObjectThread().start();
            while (true) {
                ChangeObjectThread thread = new ChangeObjectThread();
                thread.start();

                Thread.sleep(150);
              
                stopMe();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

日志打印:

User{id=1619774686, name='1619774686'}
User{id=1619774686, name='1619774686'}
User{id=1619774686, name='1619774686'}

线程中断

从表面上理解,中断就是让目标线程停止执行的意思,实际上并非如此。

严格来讲,线程中断并不会是线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出。至于目标线程是否退出,由目标线程自己决定。

线程中断三个方法:

// 中断线程
public void interrupt();
// 判断线程是否中断
public boolean isInterrupted();
// 判断线程是否中断,并清楚当前中断状态
public static boolean interrupted();

interrupt() 方法通知目标方法中断,也就是设置中断标志位,中断标志位表示当前线程已经被中断了;isInterrupted() 判断当前线程是否有被中断;interrupted() 也是用来判断当前线程是否被中断,但同时会清除当前线程的中断标志位状态。

    public void interruptTest1(){
        try {
            Thread t = new Thread() {
                @Override
                public void run() {
                    while (true) {
                        Thread.yield();
                    }
                }
            };
            t.start();
            Thread.sleep(2000);
            t.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

线程t 虽然进行了中断,但是并没有线程中断后处理的逻辑,因此线程t 即使被中断,但是这个中断不会发生任何左右。

优化:线程中断就退出while

    public void interruptTest2() {
        try {
            Thread t = new Thread() {
                @Override
                public void run() {
                    while (true) {
                        // 判断当前线程是否被中断 
                        if (Thread.currentThread().isInterrupted()){
                            System.out.println("Interrupted");
                            break;
                        }
                        Thread.yield();
                    }
                }
            };
            t.start();
            Thread.sleep(2000);
            t.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

等待和通知

为了支持多线程之间的协作,JDK 提供了两个非常重要的接口线程等待 wait() 和通知 notify()。注意,这两个方法不是在 Thread 类中,而是在 Object 类。这也意味着任何对象都能调用。

public final void wait() throws InterruptedException;
public final native void notify();

当一个对象实例调用wait 方法后,当前线程就会在这个对象上等待。比如,线程A 中,调用了obj.wait() 方法,那么线程A 就会停止继续执行,转为等待状态。当其他线程调用obj.notify() 方法为止结束等待状态。此时obj 对象就俨然成为多个线程之间的有效通讯手段。

扩展

面试题:多线程之间的通讯方式?

  1. wait()、notify()
  2. 同步 synchronized
  3. while 轮训
  4. 管道通信(PipedInputStream、PipedOutPutStream)

PS:清楚有这么一个东西即可,如何实现水平有限,可自行查阅。有错请指教

wait()、notify() 工作过程:如果一个线程调用了 object.wait() 方法,那么它就会进入object 对象的等待队列。在这个队列中,可能会有多个线程。当调用 object.notify() 被调用时,它会从这个等待队列中,随机选择一个线程,并将它唤醒。同时 Object 对象还提供了另一个方法 notifyAll() 方法,它和notify() 功能基本一致,不同的是notifyAll 会唤醒这个队列中的所有等待的线程,而不是随机选择一个。

强调,调用wait() 方法必须在 snchronzied 语句中,无论是wait()、notify() 都需要先获得锁,当执行wait() 方法后,会释放这个锁。这样做的目的是使得其他等待该锁的线程不至于无法正常执行。

public class WaitNotifyTest {

    final static Object object = new Object();

    public static class T1 extends Thread {
        @Override
        public void run() {
            synchronized (object) {
                System.out.println(System.currentTimeMillis() + ": T1 start");
                try {
                    System.out.println(System.currentTimeMillis() + ": T1 wait for object");
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + ": T1 end");
            }
        }
    }

    public static class T2 extends Thread {
        @Override
        public void run() {
            synchronized (object) {
                System.out.println(System.currentTimeMillis() + ": T2 start ! notify one thread");
                object.notify();
                System.out.println(System.currentTimeMillis() + ": T2 end");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();
        t1.start();
        t2.start();
    }

}

如上,两个线程 t1、t2。t1 执行 object.wait() 方法前,获取object 对象锁。因此,在执行 object.wait() 是,它是持有 object 锁,wait() 执行后,t1 会进入等待,并释放 object 的锁。t2 在执行 notify() 之前也会先获取 object 的对象锁。t1 在得到 notify() 通知后,还是会先尝试重新获取 object 锁。上述运行日志打印:

1620273470618: T1 start
1620273470618: T1 wait for object
1620273470618: T2 start ! notify one thread
1620273470618: T2 end
1620273472620: T1 end

挂起和继续执行

挂起suspend 和继续执行resume 是一对相反的操作,被挂起suspend 的线程,必须要等到继续执行resume 操作后,才能继续执行。目前 suspend()、resume() 已经过时,不推荐使用。

使用 suspend() 挂起线程会导致线程被暂停,同时并不会释放任何锁资源。此时,其他线程想要访问被它暂用的锁时,都会导致无法正常继续执行。直到对应的线程进行了resume() 操作,被挂起的线程才能继续,从而其他阻塞的线程才可以继续执行。严重的情况是:它暂用的锁不会被释放,因此可能会导致整个系统工作不正常。而且,对于被挂起的线程,从它的线程状态上看,居然还是Runnable ,严重影响对系统当前状态的判断。

public class SuspengResumeTest {

    public static Object object = new Object();
    static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    static ChangeObjectThread t2 = new ChangeObjectThread("t2");

    public static class ChangeObjectThread extends Thread{

        public ChangeObjectThread(String name) {
            super.setName(name);
        }

        @Override
        public void run() {
            synchronized (object) {
                System.out.println(System.currentTimeMillis() + " in "+ getName());
                Thread.currentThread().suspend();
                System.out.println(System.currentTimeMillis() + " in "+ getName());

            }
        }
    }

    public static void main(String[] args) {
        try {
            t1.start();
            Thread.sleep(1000);
            t2.start();
            t1.resume();
            t2.resume();
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果打印:

1620285481858 in t1
1620285482859 in t1
1620285482859 in t2

通过日志发现,他们都获取到了锁。但是线程不会退出,而是是会挂起。虽然主函数已经调用了 resume() ,但是由于事件先后顺序的缘故,导致 t2 线程被永远挂起,并且占用了对象锁。

优化 suspend()、resume():

public class SuspengResumeTest2 {

    public static Object object = new Object();

    public static class ChangeObjectThread extends Thread {

        volatile boolean suspendme = false;

        public void suspendsMe() {
            suspendme = true;
        }

        public void resumeMe() {
            suspendme = false;
            synchronized (this) {
                notify();
            }
        }

        @Override
        public void run() {
            while (true) {
                synchronized (this) {
                    while (suspendme) {
                        try {
                            wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }

                synchronized (object) {
                    System.out.println("in ChangeObjectThread");
                }
                Thread.yield();
            }
        }
    }

    public static class ReadObjectThread extends Thread{
        @Override
        public void run() {
            while (true) {
                synchronized (object) {
                    System.out.println("in ReadObjectThread");
                }
                Thread.yield();
            }
        }
    }

    public static void main(String[] args) {
        try {
            ChangeObjectThread t1 = new ChangeObjectThread();
            ReadObjectThread t2 = new ReadObjectThread();
            t1.start();
            t2.start();
            Thread.sleep(1000);
            t1.suspendsMe();
            System.out.println("suspend t1 2 sec");
            Thread.sleep(2000);
            System.out.println("resume t1");
            t1.resumeMe();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

等待线程结束join 和谦让yield

很多时候,一个线程的执行很可能需要依赖于另外一个或者多个线程执行完毕之后才能继续执行。比如,日常工作需要产品先出需求文档,然后召开需求评审,紧接着进行软件开发。JDK 提供了 join() 来实现这个功能。

public final void join() throws InterruptedException;
public final synchronized void join(long millis) throws InterruptedException;

第一个 join() 表示无限等待,他会一致阻塞当前线程,直到目标线程执行完毕。

第二个 join(long) 表示最大等待时间,如果超过给定时间目标线程还在执行,当前线程也会因为“等不及了”,而继续往下执行。

public class JoinTest {
    public volatile static int num = 1;

    public static class JoinThread extends Thread {
        @Override
        public void run() {
            for (; num < 100000000; num++) ;
        }
    }

    public static void main(String[] args) {
        try {
            JoinThread joinThread = new JoinThread();
            joinThread.start();
            joinThread.join();
            System.out.println("num :" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

结果打印:

num :100000000

如果把 joinThread.join(); 注释掉,查看日志 num :1

主函数在等待 joinThread 线程执行完毕再继续执行,此时 num 为 100000000。

扩展

join() 的本质是让调用线程 wait() 在当前线程对象实例上。源码:

public final void join() throws InterruptedException {
    join(0);
}
    public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

可以看到,它让调用线程在当前线程对象上进行等待。当线程执行完成后,被等待的线程会在退出前调用 notifyAll() 通知所有的等待线程继续执行。因此,不建议直接在 Thread 对象实例上使用类似于 wait()和notify() 等方法,因为这有可能影响系统API的工作。

Thread 类中的另一个方法 yield(),定义:

public static native void yield();

静态方法,一大执行,它会使得当前线程让出CPU。但是要注意,让出CPU 并不表示当前线程不执行。当前线程在让出CPU 后,还会进行CPU 资源的争夺,能够再次被分配就不一定了。因此,Thread.yield() 的调用就好像再说,我已经完成了一些最重要的工作了,可以休息一下了,可以给其他线程一些工作机会!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值