Java线程深入学习

2020-07-01  本文已影响0人  修符道人

全面深入的讲解线程

线程超级详解:http://blog.csdn.net/cuigx1991/article/details/48219741
需要掌握的知识点:
1)线程的概念
2)线程创建的几种方式
3)线程的状态及转换
4)影响线程调度方式的几个方法
5)线程的同步与锁
6)线程交互协作

知识点一:线程创建的几种方式

1.直接new Thread,实现runnable方法。
2.Thread构造方法传入Runnable接口
3.线程池创建

知识点二:线程安全(同步与锁)

1.线程同步问题的产生

产生原因:多个线程同时访问同一数据/方法
比如:同一线程A里的方法、代码都是一行一行执行的,但是再来个线程B,可能线程A执行到某个方法某一行代码的时候,这时线程B获取了CPU执行权,线程B对线程A下面要执行的一行代码相关的变量作了改动,就会导致安全问题。

2.同步和锁定

1)锁的原理:一个对象(一般是一个实例,也可能是一个字节码)一把锁 ...
一般的文章都认为对象锁就是this或者字节码,这个是不准确的。可以是任意的Object,我认为一种情况是为了避免多创建一个Object,所以就用的this或者字节码;另外一种情况是不同的对象要使用同一把锁,就不能用this了。

2)同步代码块
静态方法的同步代码块,锁可以用当前类的字节码,因为静态方法里无法引用this.

public static intsetName(String name){
      synchronized(Xxx.class){
            Xxx.name = name;
      }
}

--synchronized(xxx):可以理解为给当前代码单元A(方法、代码块)加上锁xxx,只要锁没有释放,任何其它的线程执行到具有相同锁xxx的代码单元B(无论AB是否是同一个对象或者同一个类,锁可以绑定到任何的执行载体上),都会等待代码单元A执行完毕才去执行。曾经老毕的课程里就用去洗手间反锁门作了一个形象的比喻,锁住的卫生间某个时间段只能由一个人使用。

看两个代码单元执行是否互斥,首先看它们的锁是不是同一把锁。

--synchronized释放锁的时机:https://www.jianshu.com/p/888469ebe713

--锁的类型:锁一般分为3大类型,对象(Object)、this、class,this理论上来说也是对象。

非静态synchronized方法的锁是this
静态synchronized方法的锁是class

上面这个结论自己写个小demo很容易证明,我就不作阐述了。

3)线程安全类
当一个类已经很好的同步以保护它的数据时,这个类就称为“线程安全的”。某个类的成员类是安全的,不代表这个类就一定安全。

4)死锁
https://blog.csdn.net/nangeali/article/details/80304456
概念:2个线程在运行时,都互相持有对方的锁,导致互相等待的现象。
解决方案:
https://www.cnblogs.com/xzlf/p/12681525.html
1》不要在同一个代码块中,持有多个对象的锁

https://blog.csdn.net/sinat_41144773/article/details/89476679
2》加锁顺序:当多个线程要相同的一些锁,但是按照不同的顺序加锁,死锁的情况发生率较高,如果,程序运行能确保所有线程都是按照相同的顺序去获得锁,那么死锁就不会发生。

3》加锁时限:加一个超时时间,若一个线程没有在给定的时间内成功获取所需的锁,则进行回退操作,并释放自己本身所持有的锁,一段随机时间之后,重新去获取锁。

4》死锁检测:死锁检测,每当线程去获取锁的时候,会在线程和锁相关的数据结构中将其记下,除此之外,每当线程请求锁,都需要记录在数据结构中。死锁检测是一个死锁避免机制。他主要针对的时那些不可能实现按序加锁并且锁超时也不可行的应用场景。
5)volatile关键字

2.锁对象

相当于对syncronised的封装,使用起来更加的方便。
1)普通锁
2)读写锁
读与读不互斥,读写锁分离,提高读写效率。(就是当前有一个Read线程获取了锁,如果这时候有另外一个Read线程起来了,这个线程仍然可以正常执行。)
https://www.jianshu.com/p/9cd5212c8841
3)信号量
1)Semaphore不一定是锁定某个资源(通常情况下不是线程安全的),而是流程上的概念。
2)信号量的概念不光作用于线程,也可适用于操作系统的进程控制。
https://www.php.cn/java-article-407669.html
https://blog.csdn.net/shenjixiang/article/details/78724705

知识点三:线程间的交互协作

1.wait、notify、notifyAll

https://blog.csdn.net/weixin_42541254/article/details/101291768
void notify()——唤醒在此对象监视器上等待的单个线程。
void notifyAll()——唤醒在此对象监视器上等待的所有线程。
void wait()——导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法。
void wait(longtimeout)——导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法,或者超过指定的时间量。
void wait(longtimeout, int nanos)——导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。

wait、notify对锁的影响

进入 wait()方法后,当前线程释放锁
notify 后,当前线程不会马上释放该对象锁,wait 所在的线程并不能马上获取该对象锁,要等到程序退出 synchronized 代码块后,当前线程才会释放锁,wait所在的线程也才可以获取该对象锁。

调用某个对象的wait、notify等方法,必须

1.在当前线程里获取这个对象的锁
2.且锁一定是同一把锁(同一个对象)

也就是说wait()、notify()等方法调用的格式是固定的。
---wait()方法调用

synchronized (锁对象) {
   锁对象.wait();
}

如果不加synchronized直接调用wait()方法就会报错java.lang.IllegalMonitorStateException: object not locked by thread before wait()
---notify()方法调用

synchronized (锁对象) {
   锁对象.notify();
}

如果不加synchronized直接调用notify()方法就会报错java.lang.IllegalMonitorStateException: object not locked by thread before notify()

1)简单的示例:
第1种情况:协作的线程对象本身作为锁

/**
 * 计算1+2+3+...+100的和
 */
public class ThreadB1 extends Thread {
    int total;
    public ThreadB1() {
    }

    public void run(){
        synchronized (this) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            for (int i=0;i<101;i++){
                total+=i;
            }
            //(完成计算了)唤醒在此对象监视器上等待的单个线程,在本例中线程A被唤醒
            notify();
        }
    }
}
  private void waitTest1() {
        ThreadB1 b=new ThreadB1();
        //启动计算线程
        b.start();
        //线程A拥有b对象上的锁。线程为了调用wait()或notify()方法,该线程必须是那个对象锁的拥有者
        synchronized (b) {
            try {
                System.out.println("等待对象b完成计算......");
                b.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("b对象计算的总和是:" + b.total);
        }
    }

第2种形式:创建新的对象作为锁

/**
 * 计算1+2+3+...+100的和
 */
public class ThreadB2 extends Thread {
    int total;
    private Object lock;


    public ThreadB2(Object lock) {
        this.lock = lock;
    }

    public void run(){
        synchronized (lock) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i=0;i<101;i++){
                total+=i;
            }
            //(完成计算了)唤醒在此对象监视器上等待的单个线程,在本例中线程A被唤醒
            lock.notify();
        }
    }
}
private void waitTest2() {
        Object lock = new Object();
        ThreadB2 b=new ThreadB2(lock);
        //启动计算线程
        b.start();
        //线程A拥有b对象上的锁。线程为了调用wait()或notify()方法,该线程必须是那个对象锁的拥有者
        synchronized (lock) {
            try {
                System.out.println("等待对象b完成计算......");
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("b对象计算的总和是:" + b.total);
        }
    }

》》》将线程稍做改动,在计算和之前就notify。

  public void run(){
        Log.e(TAG, "run: 入口");
        synchronized (this) {
            //(完成计算了)唤醒在此对象监视器上等待的单个线程,在本例中线程A被唤醒
            Log.e(TAG, "run: notify");
            notify();

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.e(TAG, "run: 开始计算");
            for (int i=0;i<101;i++){
                total+=i;
            }
        }
    }

发现程序并不会立马切换到调用wait的线程,而是等notify的线程执行完毕,wait的线程才会继续执行。
如果wait的线程是main线程,notify后面的操作时间很长,main线程会一直等待,会算作是阻塞主线程,提示Skipped xxx frames! The application may be doing too much work on its main thread.,所以这个错误不一定就是主线程做耗时操作导致的。
2)多个线程等待同一个对象锁时使用notifyAll
3)notify比wait先执行,导致wait无法解除。
对上面的简单示例第一种情况,作一下改动。使子线程快速执行完notify方法

public class ThreadB1 extends Thread {
    private static final String TAG = "ThreadB1";
    int total;
    private boolean isFinish = false;

    public ThreadB1() {
    }

    public void run(){
        Log.e(TAG, "run: 入口");
        synchronized (this) {
//            try {
//                Thread.sleep(1000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            Log.e(TAG, "run: 开始计算");
//            for (int i=0;i<101;i++){
//                total+=i;
//            }
            //(完成计算了)唤醒在此对象监视器上等待的单个线程,在本例中线程A被唤醒
            Log.e(TAG, "run: notify");
            notify();
            isFinish = true;
        }
    }

    public boolean isFinish() {
        return isFinish;
    }
}
/**
     * 使notify先于wait执行
     */
    private void waitTest11() {
        final ThreadB1 b=new ThreadB1();
        //启动计算线程
        b.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //线程A拥有b对象上的锁。线程为了调用wait()或notify()方法,该线程必须是那个对象锁的拥有者
        synchronized (b) {
            try {
//                while (!b.isFinish()){
                    System.out.println("等待对象b完成计算......");
                Timer timer = new Timer();
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                      System.out.println("尝试notify一下");
                      b.notify();
                    }
                },5000);
                b.wait();
//                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("b对象计算的总和是:" + b.total);
        }
    }

日志 一直停留在


image.png

可以看到上面,即使我事先使用了timer准备notify解除一下wait,也是无济于事,因为wait造成当前线程等待,而timer也是执行在当前线程,所以也等待了。

官方给出的wait用法

synchronized (obj) {
    while (&lt;condition does not hold&gt;)
        obj.wait();
    ... // Perform action appropriate to condition
}

这里的while判断就是防止notify执行完毕了,wait才执行,导致当前线程意外的一直等待。

2.线程调度

  ThreadJoinTest t1 = new ThreadJoinTest("小明");
        ThreadJoinTest t2 = new ThreadJoinTest("小东");
        t1.start();
        /**join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是:
         程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕
         所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会
         */
        t1.join();
        t2.start();

知识点四:线程池

线程池作用

1)复用线程,避免创建开销。

线程池分类

1)固定大小的线程池
2)单一任务线程池
3)可变尺寸线程池
4)延迟线程池
https://blog.csdn.net/h610968110/article/details/78894513

5)单任务延迟线程池
6)自定义线程池
7)带返回值的线程(依赖于线程池)

线程池的适用场景

https://blog.csdn.net/qq_43692920/article/details/90266704

线程池的关闭

https://www.cnblogs.com/aspirant/p/10265863.html
shutdown与shutDownNow方法

上一篇下一篇

猜你喜欢

热点阅读