多线程

3-线程的调度-状态-同步-安全问题

2021-07-07 524 1

简介 介绍线程的调度策略、线程的生命周期、线程同步、线程安全问题

7. 线程中设置CPU调度策略

调度策略

时间片

抢占式高优先级的线程抢占CPU

upfile

Java的调度方法

同优先级线程组成先进先出队列(先到先服务),使用时间片策略

对高优先级,使用优先调度的抢占式策略

 线程的优先级等级

MAX_PRIORITY10

MIN _PRIORITY1

NORM_PRIORITY5

 涉及的方法

getPriority() 返回线程优先值

setPriority(int newPriority) 改变线程的优先级

 说明

线程创建时继承父线程的优先级

低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。



//ThreadPriorityTest.java
package com.ylaihui.thread;

 

class ThreadPriority extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+ ":" + i);
        }
    }
}

 

public class ThreadPriorityTest {
    public static void main(String[] args) {
        ThreadPriority t1 = new ThreadPriority();
        // t1 设置为最高优先级, 主线程设置为最低优先级
        t1.setPriority(Thread.MAX_PRIORITY);
        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);

 

        t1.start();

 

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+ ":" + i);
        }
    }
}



2. 线程的生命周期

 JDK中用Thread.State类定义了线程的几种状态

要想实现多线程, 必须在主线程中创建新的线程对象。 Java语言使用Thread类

及其子类的对象来表示线程, 在它的一个完整的生命周期中通常要经历如下的五

种状态:

新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建

状态 NEW

就绪: 处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已

具备了运行的条件,只是没分配到CPU资源

运行: 当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线

程的操作和功能

阻塞: 在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中

止自己的执行,进入阻塞状态  BLOCKED

死亡: 线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束 TERMINATED

upfile



3. 线程的同步


线程的安全问题

多个线程执行的不确定性引起执行结果的不稳定

多个线程对数据的共享,会造成操作的不完整性,会破坏数据。

多线程处理缓冲区的消息时,存在重复处理错误处理问题,比如重复处理消息100, 错误处理了消息0或-1等。


//ProcessMessageTest.java
package com.ylaihui.thread;

 

class ProcessMessage implements Runnable{
    private int message = 100;
    @Override
    public void run() {
        while(true) {
            if(message > 0) {
                System.out.println("process message: " + message);
                message--;
            }else
                break;
        }
    }
}

 

public class ProcessMessageTest {
    public static void main(String[] args) {
        ProcessMessage p = new ProcessMessage();
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(p);
        Thread t3 = new Thread(p);

 

        t1.start();
        t2.start();
        t3.start();
    }
}



4. 解决线程安全问题(解决线程同步问题)

同步代码块实现线程安全问题

  synchronized(同步监视器){

     //需要被同步的代码

  }  

说明:

      1.操作共享数据的代码,即为需要被同步的代码。  -->不能包含代码多了,也不能包含代码少了。

      2.共享数据:多个线程共同操作的变量。

      3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。

         要求:多个线程必须要共用同一把锁。

      补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。使用继承Thread创建多线程的方式,可以使用继承类的Xxx.class方式充当同步监视器

   同步的方式,解决了线程的安全问题。---好处

   操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 ---局限性


//ThreadSynchronizedBlock.java
package com.ylaihui.thread;

 

class ProcessMessage1 implements Runnable{
    private int message = 100;
    Object obj = new Object();
    @Override
    public void run() {

 

//        synchronized(obj){
        synchronized(this){
            while(true){
                if(message > 0){
                    System.out.println("process message: "+ message);
                    message--;
                }else
                    break;
            }
        }
    }
}

 

public class ThreadSynchronizedBlock {
    public static void main(String[] args) {
        ProcessMessage1 p = new ProcessMessage1();
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(p);
        Thread t3 = new Thread(p);

 

        t1.start();
        t2.start();
        t3.start();
    }
}



5. 解决继承Thread类的线程的安全问题

//ThreadSynchronizedBlock1.java
package com.ylaihui.thread;

 

class ProcessMsg extends Thread{
    private static int message = 100;
    private static Object obj = new Object();
    @Override
    public void run() {
        // 方式一
//        synchronized(obj){
        // ProcessMsg 在运行时只加载一次, 在底层也是一个对象
        synchronized(ProcessMsg.class){
            while(true){
                if(message > 0 ){
                    System.out.println("process message: " + message);
                    message--;
                }else
                    break;
            }
        }
    }
}

 

public class ThreadSynchronizedBlock1 {
    public static void main(String[] args) {
        ProcessMsg t1 = new ProcessMsg();
        ProcessMsg t2 = new ProcessMsg();
        ProcessMsg t3 = new ProcessMsg();

 

        t1.start();
        t2.start();
        t3.start();
    }
}



6. 同步方法实现Runnable创建线程的安全问题

同步代码块的方式:  

synchronized(同步监视器){

     //需要被同步的代码

  }  


同步方法的方式:

需要被同步的代码抽取出来,作为一个单独的方法, 同时将该方法设置为 synchronized

void synchronized fun_name{

//需要被同步的代码

}

举例如下:


//SynchronizedMethodTest.java
package com.ylaihui.thread;

 

import static java.lang.Thread.sleep;

 

class ProcessMessage2 implements Runnable{
    private int message = 100;
    @Override
    public void run() {
        while(true) {
            proc();
        }
    }
    private synchronized void proc(){  // 同步监视器是 this
        try {
            sleep(100);
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(message > 0) {
            System.out.println("process message: " + Thread.currentThread().getName() + "-" +  message);
            message--;
        }
    }
}

 

public class SynchronizedMethodTest {
    public static void main(String[] args) {
        ProcessMessage2 p = new ProcessMessage2();
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(p);
        Thread t3 = new Thread(p);

 

        t1.start();
        t2.start();
        t3.start();
    }
}


7. 同步方法实现继承Thread类创建的线程的安全问题

 1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。

 2. 非静态的同步方法,同步监视器是:this

      静态的同步方法,同步监视器是:当前类本身(Xxx.class,类Xxx加载时产生的对象)


//SynchronizedMethodTest1.java
package com.ylaihui.thread;

 

class ProcMsg extends Thread{
    private static int message = 100;
    @Override
    public void run() {
        while(true){
            proc();
        }
    }
//    private synchronized void proc(){ // 同步监视器是 this, 是 t1 t2 t3
    private static synchronized void proc(){ // 同步监视器是 ProcMsg.class 对象
        if(message > 0){
            System.out.println(Thread.currentThread().getName() + ":" + message);
            message--;
        }
    }
}
public class SynchronizedMethodTest1 {
    public static void main(String[] args) {
        ProcMsg t1 = new ProcMsg();
        ProcMsg t2 = new ProcMsg();
        ProcMsg t3 = new ProcMsg();
        t1.start();
        t2.start();
        t3.start();
    }
}


8. 死锁问题

死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于

阻塞状态,无法继续

解决方法

专门的算法、原则

尽量减少同步资源的定义

尽量避免嵌套同步


1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,

都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

2.说明:

1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

2)我们使用同步时,要避免出现死锁。



9. 死锁的举例


//DeadLock1.java
package com.ylaihui.thread;

 

public class DeadLock1 {

 

    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();

 

        new Thread(){
            @Override
            public void run() {
                synchronized (s1){
                    s1.append("111");
                    s2.append("aaa");
                    try {
                        Thread.sleep(100);
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }

 

                    synchronized (s2){
                        s1.append("bbb");
                        s2.append("222");

 

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();

 

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2){

 

                    s1.append("ccc");
                    s2.append("333");

 

                    try {
                        Thread.sleep(100);
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }

 

                    synchronized (s1){
                        s1.append("ddd");
                        s2.append("444");

 

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}



10. 死锁的举例2


//DeadLock1.java
package com.ylaihui.thread;
 
public class DeadLock1 {
 
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();
 
        new Thread(){
            @Override
            public void run() {
                synchronized (s1){
                    s1.append("111");
                    s2.append("aaa");
                    try {
                        Thread.sleep(100);
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }
 
                    synchronized (s2){
                        s1.append("bbb");
                        s2.append("222");
 
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();
 
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2){
 
                    s1.append("ccc");
                    s2.append("333");
 
                    try {
                        Thread.sleep(100);
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }
 
                    synchronized (s1){
                        s1.append("ddd");
                        s2.append("444");
 
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}



11.  Lock 方式解决线程安全问题

解决线程安全问题的方式三:Lock锁  --- JDK5.0新增

1. 面试题:synchronized Lock的异同?

  相同:二者都可以解决线程安全问题

  不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器

       Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock()

2.优先使用顺序: 优先使用灵活的

Lock  同步代码块(已经进入了方法体,分配了相应资源)  同步方法(在方法体之外)


//SynchronizedLockTest.java
package com.ylaihui.thread;
 
import java.util.concurrent.locks.ReentrantLock;
 
class ProcPicture implements Runnable{
    private static int num = 100;
    private ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while(true) {
            try{
                lock.lock();
                if (num >