线程的基本操作(读书笔记)

Posted Darcy_wang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程的基本操作(读书笔记)相关的知识,希望对你有一定的参考价值。

  • 新建线程
     新建线程很简单,只要使用new关键字创建一个线程对象.并且将它start()起来即可.那么线程start()后,会干什么呢?这才是问题的关键.线程Tread,有一个run()方法.start()方法就会新建一个线程并让这个线程执行run()方法
Thread t1 = new Thread();
t1.start();
 
这里要注意 下面的代码也能通过编译,也能正常运行.但是 却不能新建一个线程,而是在当前线程中调用run方法,只是当做一个普通方法调用
Thread t2 = new Thread();
t1.run();
注意:不要用run()方法来开启新线程.他只会在当前线程中,串行执行run()中的代码
 
创建线程的两种方法:
Thread t1 = new Thread() {
    @Override
    public void run() {
        System.out.println("Hello,I am 他");
    }
};
t1.start();
//Java8 版本
Thread t2 = new Thread(() -> System.out.println("Hello,I am 他"));
t2.start();

public class CreateThread implements Runnable {

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

    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object‘s
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @Override
    public void run() {
        System.out.println("Oh, I am Runnable");
    }
}

 

下面的方法是最常用的!
  • 终止线程
在jdk中 我们发现Thread提供了一个stop()方法,但是stop方法却被废弃了.原因是这个方法太过去暴力了.强行把执行到一半的线程终止.可能会引起一些数据不一致的问题.
因为他释放了这个线程所持有的所有锁,而这些锁恰恰是用来维持一个对象一致性的.例如写数据写到一半,并强行终止.那么对象就会被写坏.同时 由于锁已经被释放了,另一个等待的读线程顺理成章的读到了这个不一致的对象,悲剧也就发生了!
那如果需要停止一个线程时,其实方法很简单,.只需要由我们自行决定线程何时退出就可以了.
public class CreateThread implements Runnable {

    volatile boolean stopme = false;

    public void stopMe() {
        stopme = true;
    }
    @Override
    public void run() {
        while (true) {
            if (stopme) {
                System.out.println("exit by stop me");
                break;
            }
            synchronized (u) {
                int v = (int) (System.currentTimeMillis() / 1000);
                u.setId(v);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                u.setName(String.valueOf(v));
            }
            Thread.yield();
        }
    }
}

 

  • 线程终端 在Java中 线程中断是一种重要的线程协作方式,从表面理解,中断就是让目标线程停止执行的意思,实际上并非完全如此, 
严格意义上说, 线程中断并不是线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出啦.至于目标线程得到通知后如何处理,则完全由目标线程自行决定,这点很重要!
     与线程中断有关的,有三个方法,

Thread t1 = new Thread(new CreateThread());
t1.interrupt();//中断线程 实例方法
t1.isInterrupted();//判断是否中断 实例方法
Thread.interrupted();//判断是否中断,并清除当前中断状态 静态方法

public class CreateThread implements Runnable {


    @Override
    public void run() {
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("Interrupted");
                break;
            }
            try {
                Thread.sleep(2000);//实例线程 休眠2s  当线程休眠中被中断 抛出InterruptedException 异常
            } catch (InterruptedException e) {
                System.out.println("Interrupted When Sleep");
                Thread.currentThread().interrupt();
            }
            Thread.yield();
        }
    }


    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(new CreateThread());
        t1.start();
        Thread.sleep(1000);//main 休眠1s 后执行中断
        t1.interrupt();
    }
}
  • 等待(wait)和通知(notify)
为了支持多线程间的协作,JDK提供了两个非常重要的接口线程等待wait()方法和通知notify()方法,这两个方法并不是在Thread类中的 而是在Object类中,这意味着任何对象都可以调用这两个方法.
 
     当在一个对象实例上调用wait()方法后,当前先吃就会在这个对象上等待,比如线程A中,调用了obj.wait()方法,那么线程A就会停止继续执行,而转为等待状态,等待何时结束呢?线程A会一直等到其他线程调用了obj.notify()方法为止,这时,obj对象就俨然成为了多线程之间通信的有效手段.
     那wait()和notify()究竟是如何工作的呢?如果一个线程调用了object.wwait() 那么他就会进入object对象的等待队列,这个等待队列中,可能会有多个线程,因为系统运行多个线程同时等待一个对象.当object.notify()被调用时,他就会从这个等待队列中,随机选择一个线程,并将其唤醒,这个选择是不公平的,并不是先等待的线程就会优先选择,这个选择是完全随机的.
     除了notify()方法外,Object对象还有一个类似的notifyAll()方法,.他和notify()的功能基本一样,区别在于他会唤醒等待队列中所有等待的线程,而不是随机的一个,
     这里需要注意,Object.wait()方法并不是可以随便调用的.他必须包含在对应的synchronized语句中.无论是wait()和notify()都需要首先获得目标对象的一个监听器,
public class SimpleWN {
    final static Object object = new Object();

    public static class T1 extends Thread {
        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 {
        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) {
        Thread t1 = new T1();
        Thread t2 = new T2();
        t1.start();
        t2.start();
    }
}
 
     上述代码 开启了2个线程T1和T2,T1执行了wait(),注意 T1在执行等待之前,现获取了object对象锁,然后执行等待 T1会停下来等待,并释放对象锁,T2在执行notify()方法之前 获取object对象锁后,执行notify(),为了让效果明显.我们特意T2执行notify()后休眠2秒,,这样做可以明显了看出,T1得到通知后,还会先尝试重新获取对象锁,
技术分享
 
注意 Object.wait()和Thread.sleep()两个方法都可以让线程等待若干时间.除了wait()可以被唤醒外,wait()执行完会释放目标对象的锁,而sleep()不会释放任何资源!
 
  • 等待线程结束(join)和谦让(yield)
很多时候 一个线程的输入可能非常依然于另一个或者多个线程的输出.此时 这个线程就需要等待依赖线程执行完毕,才能继续执行,JDK提供了join()操作来实现这个功能,
public final synchronized void join(long millis)throws InterruptedException
public final void join() throws InterruptedException
第一个方法给出了一个最大等待时间,如果超出给定时间目标线程还在执行,当前线程也会因为等待不及了,而继续执行下去 第二方法表示无线等待.它会阻塞当前线程,知道目标线程执行完毕.
 下面提供一个简单的join实例,
public class JoinMain {
    public volatile static int i = 0;

    public static class AddThread extends Thread {
        public void run() {
            for (i = 0; i < 10000000; i++) ;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        AddThread at = new AddThread();
        at.start();
        at.join();
        System.out.println(i);
    }
}

 

 主函数中,如果不是用join()等待AddThread, 那么得到的i 很可能是0或者一个非常小的数字,但在join()后,表示主线程愿意等待AddThread执行完毕,跟着AddThread一起往前走,故在join()返回时.AddThread已经执行完了.故i 永远是10000000.
注意 不要在应用程序中 在Thread对象实例上使用类似wait()或者notify等方法, 因为折痕有可能会影响系统API的运行,或者被系统API所影响!
 
另外一个比较有趣的方法是Thread.yield();
public static native void yield();
这是一个静态方法,一旦执行,它会使当前线程让出CPU.当时注意,让出CPU并不代表当前线程不执行了.当前线程让出CPU后,还会进行CPU资源的争夺,但是是否能够再次被分配到,就不一定了,因此 对Thread.yield()的调用就好像在说:我已经完成一些最重要的工作 了.我应该休息一下了.可以给其他线程一些工作机会了!
     如果你觉得一个线程不那么重要,或者优先级很低,而且害怕他会占用太多CPU资源,那么可以适当的时候调用这个方法,.给予其他重要的线程更多的工作机会!
  • volatile Java使用了一些特殊的操作或者关键字来申明,告诉虚拟机,在这个地方,要尤其注意,不能随意变动优化目标指令. 易变得 不稳定的.
当你用volatile声明一个变量时,就等于告诉虚拟机,这个变量极有可能会被某些程序或者线程修改.为了确保可见性,虚拟机做了特殊的手段,保证这个变量的可见性!

以上是关于线程的基本操作(读书笔记)的主要内容,如果未能解决你的问题,请参考以下文章

《C++ Concurrency in Action》读书笔记三 同步并发操作

读书笔记

《Linux内核设计与实现》读书笔记- 内核同步方法

C++并发编程----并发代码的设计(《C++ Concurrency in Action》 读书笔记)

C++并发编程----并发代码的设计(《C++ Concurrency in Action》 读书笔记)

Java 线程第三版 第五章 极简同步技巧 读书笔记