zoukankan      html  css  js  c++  java
  • Java:多线程


    本文内容:

    • 什么是线程
    • 线程的生命周期
    • Thread实现的多线程
    • Runable实现的多线程
    • 线程常用函数
    • 线程的控制
    • 线程同步
    • 线程通信

    首发日期:2018-05-13


    Thread实现的多线程:

    实现方法:

    1. 定义一个类继承Thread。
    2. 覆盖run方法,将自定义代码写到run方法中
    3. 创建子类对象就是创建线程对象
    4. 子类调用Thread类中的start方法就可以执行线程,并会调用run方法。
    class MyThread extends Thread{
        public void run() {
            for (int i=0;i<10;i++) {
                System.out.println("子线程拿到执行权");
            }
        }
    }
    
    public class Demo {
    
        public static void main(String[] args) {
            MyThread t=new MyThread();
            t.start();
            for (int i=0;i<10;i++)
                System.out.println("主线程运行");
        }
    
    }

    上述代码结果【该结果有随机性,如果想要有明显的抢夺运行权,可以增大i】:

    主线程运行
    子线程拿到执行权
    子线程拿到执行权
    子线程拿到执行权
    子线程拿到执行权
    子线程拿到执行权
    主线程运行
    主线程运行
    主线程运行
    主线程运行

    补充:

    • 执行run与start的区别:执行run仅仅相当于调用函数,并没有创建线程。而start是开启线程,并让开启的线程去执行run方法中的线程任务

    Runable实现的多线程:

    虽然已经有了继承Thread实现的多线程,但是由于在java中只支持单继承,一个类一旦继承了某个父类就无法再继承Thread类 ,因为这样,所以才有了Runable实现的多线程,这样的多线程是将实现接口Runable的类的对象传入Thread()中来创建线程对象。

    • Runable是一个接口,Thread类实现了这个接口

    实现方法:

    1. 定义一个类实现Runnale接口,重写run方法。 【run的权限是public的】
    2. 将这个类的一个对象传入Thread()中,使用Thread类直接创建线程对象。
    3. 线程对象调用start()。
    class Car implements Runnable{
        public void run() {
            for (int i=0;i<5;i++) {
                System.out.println("子线程拿到执行权");
            }
        }
    }
    
    public class Demo {
    
        public static void main(String[] args) {
            Car c=new Car();
            Thread t=new Thread(c);
            t.start();
            for (int i=0;i<5;i++)
                System.out.println("主线程运行");
        }
    
    }

    上述代码结果【该结果有随机性,如果想要有明显的抢夺运行权,可以增大i】:

    主线程运行
    子线程拿到执行权
    子线程拿到执行权
    子线程拿到执行权
    子线程拿到执行权
    子线程拿到执行权
    主线程运行
    主线程运行
    主线程运行
    主线程运行

    两种方式的区别:

    • Runnable的多个线程实例可以使用同一个实例变量,而继承Thread实现的线程无法共享同一个实例变量
    • Runnable是实现,java允许多实现,不允许多继承,所以使用实现Runnable的同时可以继承其他类。

    补充:

    • 还有实现callable接口的方式可以创建新线程。
    • 上面的两种实现方式都不可以获取返回值,获取返回值应该使用callable的方式来创建新线程

    线程常用函数:

    1. getId():返回该线程的标识符
    2. getName():返回线程的名字 【在实现Runnable的类中,没有线程对象,所以需要变成Thread.currentThread().getName();】
    3. setName():给线程设置名字【在new Thread()时可以传入一个参数作为线程的名字】
    4. currentThread():返回当前执行的线程对象
    5. getState():返回线程的状态
    6. isAlive():判断线程是否处于活动状态,返回布尔值
    7. join(int seconds):等待线程结束,有seconds代表最多等待秒数 【比如某个线程调用join(),主线程会等待这个线程执行完毕才会返回主线程执行】
    8. sleep(int seconds):让线程暂停指定秒数
    9. wait():让线程暂停
    10. interrupt():中断线程。【对于非堵塞线程,那么就会将线程中断;对于可取消的阻塞状态中的线程(Thread.sleep(), Object.wait(), Thread.join(), ),那么可以“吵醒”线程】
    11. isDaemon():判断线程是否是守护线程
    12. setDaemon():设置成守护线程
    13. setPriority():更改线程的优先级
    14. yield():暂停该线程,让出CPU执行权,重新到队列中竞争CPU权限

    线程的控制:

    线程等待:

    • join():join函数实现的效果是,如果某个线程调用join函数,那么会等到这个线程完全执行完毕才会轮到其他线程继续执行。
      • 也可以提供参数,join(n)代表等待线程执行n毫秒

    image

    守护线程

    默认情况下,主线程会等待其他线程结束才会结束程序运行,设置守护线程的效果是:如果一个线程设置成了守护线程,那么主线程不会等待该线程结束就结束程序运行(如果非主线程就只有这一个的话)

    • 线程.setDaemon(true);
    • setDaemon必须要在线程start之前调用。

    image

    线程睡眠:

    • sleep(毫秒数):线程调用sleep函数可以使线程暂停一段时间,让线程进入堵塞状态。

    image

    改变线程优先级:

    • setPriority(int newPriority);优先级高的会更容易获得CPU执行权限
    • 右边是一些常量,默认优先级是5image

    线程让步:

    yield():让出自己的CPU权限,重新进入队列中竞争CPU权限【这时候优先级高的占便宜】


    线程同步:

    为什么需要线程同步:

    • 当有多个线程操作同一变量时,如果不能统一的执行完整操作,那么可能会发生线程A未执行完成,线程B过来操作变量,导致后面线程A操作这个变量时已经不是它之前取到的变量值。
    • 线程同步的实质:到了需要线程安全的代码,由并行改成串行,只允许一个进程执行。

    同步的方式:

    • 同步代码块:synchronized(同步锁对象){同步代码}  【对于通过实现Runnable得出的多线程一般同步锁对象是this或者类名.class【或者是一些共享的对象】,理论上也同步锁对象可以任意的对象,但应该避免使用不必要的资源来作为同步锁】 【也因为同步代码块的同步锁对象可以比较随便,所以开放性比较强,使得一个类中不同的同步代码块可以使用不同的锁】
      • RTVD`JKAFP9MYDB}Y`L}ZEQ
    • 同步函数:synchronized 返回值类型 方法名(参数列表){同步代码}
      • 当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
      • 非静态函数的同步锁默认是this,静态同步函数的同步锁所任是字节码文件对象"类名.class"

        8Y%B2~{D1QZS[29)TE$P@LI[1]

    同步锁:

    • jdk5提供了新的同步锁对象来显示使用同步锁,对于同步代码块来说,在多个使用时需要使用多个锁,锁的意义并不是很明显,而同步锁类创建的对象就带有同步锁的意义。
    • 常见的同步锁类(这几个锁类都实现Lock接口)有:ReentrantLock(可重入锁,递归锁),ReentrantReadWriteLock.ReadLock(读取锁),ReentrantReadWriteLock.WriteLock(写锁) 【一般都使用ReentrantLock】
    • 它的操作是在需要锁住的代码之前lock对象.lock(),在操作结束后 lock对象.unlock(),

    image

    死锁:

    当使用上锁后,可能会发生死锁。A拿了锁1,A想要拿锁2;B拿了锁2,B想要拿锁1;于是就发生了死锁。

    解决方法:只能避免。避免相互调用彼此的锁(或者说某种独占资源)


    线程通信:

    线程通信最经典的例子是生产者-消费者例子:生产者生产完后提醒一下消费者来消费,消费者消费完后提醒生产者生产。【一个盘子时,生产者生产完就得叫消费者;多个盘子时,生产者判断是否没有空盘子再自己进行等待,消费者判断没有东西消费就等待】

    传统方式:

    • 对于还没轮到的,对象调用wait方法,等待别人唤醒自己 【wait方法调用者不是线程对象,是这个监视器(或者说是锁),如果你的锁是this时不需要前缀,不然需要调用锁对象来调用。】
    • 当想唤醒另外一方时,调用notify方法来唤醒。【notify是唤醒任意一个在等待锁的进程,notifyAll是唤醒所有在等待锁的进程】
    class RestRoom{
        int count=0;
        boolean panzi=true;
        
        public synchronized void  produce() {
            try {
                if(!panzi) {
                    this.wait();
                }
                count++;
                System.out.println("我生产了一个面包"+count);
                panzi=false;
                this.notify();
                
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        public synchronized void consume() {
            try {
                if(panzi) {//有空盘子
                    this.wait();
                }
                System.out.println("我消费了一个面包"+count);
                panzi=true;
                this.notify();
                
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
        
    }
    
    class Producer  implements Runnable{
        RestRoom r;
        Producer(RestRoom r){
            this.r=r;
        }
        public void run() {
            for(int i=0;i<100;i++) {
                r.produce();//生产100次
            }
        }
    }
    
    class Consumer  implements Runnable{
        RestRoom r;
        Consumer(RestRoom r){
            this.r=r;
        }
        public void run() {
            for(int i=0;i<100;i++) {
                r.consume();//生产100次
            }
        }
    }
    
    
    public class TongbuDemo {
    
        public static void main(String[] args) {
            RestRoom r=new RestRoom();
            Producer pro=new Producer(r);
            Consumer con=new Consumer(r);
            Thread t1=new Thread(pro);
            Thread t2=new Thread(con);
            t1.start();
            t2.start();
        }
    }

    使用注意:

    • 如果只有一个生产者,一个消费者,在等待唤醒的只有可能是“对方”;但如果存在多个生产者或多个消费者时,那么可能会唤醒“本方”,这时可以使用notifyAll(),唤醒所有的本方,然后本方再通过判断条件来wait【一种常用的先同再异的思想】【同时因为有可能已经有线程之前就进入了判断环节,所以需要使用while才能把它留在判断环节中】。
    package runable_线程;
    
    
    class RestRoom{
        int count=0;
        boolean panzi=true;
        
        public synchronized void  produce() {
            try {
                while(!panzi) {
                    this.wait();
                }
                count++;
                System.out.println("我生产了一个面包"+count);
                panzi=false;
                this.notifyAll();
                
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        public synchronized void consume() {
            try {
                while(panzi) {//有空盘子
                    this.wait();
                }
                System.out.println("我消费了一个面包"+count);
                panzi=true;
                this.notifyAll();
                
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
        
    }
    
    class Producer  implements Runnable{
        RestRoom r;
        Producer(RestRoom r){
            this.r=r;
        }
        public void run() {
            for(int i=0;i<100;i++) {
                r.produce();//生产100次
            }
        }
    }
    
    class Consumer  implements Runnable{
        RestRoom r;
        Consumer(RestRoom r){
            this.r=r;
        }
        public void run() {
            for(int i=0;i<100;i++) {
                r.consume();//生产100次
            }
        }
    }
    
    
    public class TongbuDemo {
    
        public static void main(String[] args) {
            RestRoom r=new RestRoom();
            Producer pro=new Producer(r);
            Producer pro2=new Producer(r);
            Consumer con=new Consumer(r);
            Consumer con2=new Consumer(r);
            Thread t1=new Thread(pro);
            Thread t2=new Thread(pro2);
            Thread t3=new Thread(con);
            Thread t4=new Thread(con2);
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

    新增了lock接口的一系列实现类后,可以使用Condition来进行线程通信:

    • 可以使用 同步锁对象.newCondition() 来获取Condition对象
    • Condition中等待的方式变成了condition.await();提醒的方式变成了condition.sign()和condition.signAll();
    • 新的Condition对象允许一个锁能有多个Condition对象,所以我们可以使用不同的Condition对象来代表不同的身份。

    在单一的生产者和消费者时,代码与上面相同大略。

    当多个多个生产者或多个消费者时,那么就需要定义不同的Condition对象了,不同的condition对象,唤醒的线程也不一样,比如可以定义一个condition对象专门代表消费者,那么使用这个对象.signal()时就会唤醒消费者。

    package runable_线程;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    class RestRoom{
        int count=0;
        boolean panzi=true;
        ReentrantLock lock=new ReentrantLock();
        Condition condition_pro = lock.newCondition();
        Condition condition_con = lock.newCondition();
        public  void  produce() {
            lock.lock();
            try {
                if(!panzi) {
                    condition_pro.await();
                }
                count++;
                System.out.println("我生产了一个面包"+count);
                panzi=false;
    //            this.notifyAll();
                condition_con.signal();//唤醒消费者
                
            }catch(InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
        
        public void consume() {
            lock.lock();
            try {
                if(panzi) {//有空盘子
                    condition_con.await();
                }
                System.out.println("我消费了一个面包"+count);
                panzi=true;
    //            this.notifyAll();
                condition_pro.signal();//唤醒生产者
                
            }catch(InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
        
    }
    
    class Producer  implements Runnable{
        RestRoom r;
        Producer(RestRoom r){
            this.r=r;
        }
        public void run() {
            for(int i=0;i<100;i++) {
                r.produce();//生产100次
            }
        }
    }
    
    class Consumer  implements Runnable{
        RestRoom r;
        Consumer(RestRoom r){
            this.r=r;
        }
        public void run() {
            for(int i=0;i<100;i++) {
                r.consume();//生产100次
            }
        }
    }
    
    
    public class TongbuDemo {
    
        public static void main(String[] args) {
            RestRoom r=new RestRoom();
            Producer pro=new Producer(r);
            Producer pro2=new Producer(r);
            Consumer con=new Consumer(r);
            Consumer con2=new Consumer(r);
            Thread t1=new Thread(pro);
            Thread t2=new Thread(pro2);
            Thread t3=new Thread(con);
            Thread t4=new Thread(con2);
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

    补充:

    • 同步中,线程方法的wait和sleep的区别:wait()会释放自己已经拿到的同步锁,而sleep不会释放自己拿到的同步锁。

  • 相关阅读:
    JSON总结
    protobuf 编码实现解析(java)
    Java Enum解析【转】
    protobuf 原理
    apache CXF wsdl2java工具的使用
    Web Service中的几个重要术语
    Servlet过滤器
    Java中static关键字的作用和用法详细介绍
    浅析Java中的final关键字
    Java中按值传递与按引用传递的区别
  • 原文地址:https://www.cnblogs.com/progor/p/9033147.html
Copyright © 2011-2022 走看看