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不会释放自己拿到的同步锁。

  • 相关阅读:
    octotree神器 For Github and GitLab 火狐插件
    实用篇如何使用github(本地、远程)满足基本需求
    PPA(Personal Package Archives)简介、兴起、使用
    Sourse Insight使用过程中的常使用功能简介
    Sourse Insight使用教程及常见的问题解决办法
    github 遇到Permanently added the RSA host key for IP address '192.30.252.128' to the list of known hosts问题解决
    二叉查找树的C语言实现(一)
    初识内核链表
    container_of 和 offsetof 宏详解
    用双向链表实现一个栈
  • 原文地址:https://www.cnblogs.com/progor/p/9033147.html
Copyright © 2011-2022 走看看