多线程基础

1、概述

Jvm虚拟机启动的时候就启动了多个线程,至少有两个线程可以分析出来,他们分别是执行main函数的线程,该线程的任务代码都定义在mian函数中;第二个就是负责垃圾回收的线程。

创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行。而运行的指定代码就是这个执行路径的任务。另外,Jvm创建的主线程的任务都定义在了主函数中。

2、多线程的两种创建方式

    多线程的创建方式一共有两种,第一种是继承Thread类,第二种是实现Runable接口。在这两种方法中都有一个 run方法,这个方式就是具体要运行的多线程的内容。也就是说每运行一个run方法,就开启了一个线程。

    我们先定义一个资源类Resource,代码如下:

public classResource {
    Stringname;
    int age;
 
    @Override
    publicString toString() {
       return "Resource[name=" + name + ",age=" + age + "]";
    }
}

然后定义两个资源操作类(Thread01和Thread02):

第一种:继承Thread类

【Thread01】

public classThread01 extendsThread {
 
    privateResource r;
 
    publicThread01(Resource r) {
       this.r = r;
    }
 
    @Override
    public voidrun() {
       r.name = "name01";
       r.age = 1;
       System.out.println(r.toString());
    }
}

【Thread02】

public classThread02 extendsThread {
 
    privateResource r;
 
    publicThread02(Resource r) {
       this.r = r;
    }
 
    @Override
    public voidrun() {
       r.name = "name02";
       r.age = 2;
       System.out.println(r.toString());
    }
}

【Main】

public static voidmain(String[] args) {
       // 实例化资源
       Resourcer = newResource();
 
       // 创建实现runable接口的资源控制器
       Thread01 t01 = new Thread01(r);
       Thread02t02 = newThread02(r);
 
       // 开启线程运行
       t01.start();
       t02.start();
    }

第二种:实现Runable接口

【Thread01】

public classThread01 implements Runable{
 
    privateResource r;
 
    publicThread01(Resource r) {
       this.r = r;
    }
 
    @Override
    public voidrun() {
       r.name = "name01";
       r.age = 1;
       System.out.println(r.toString());
    }
}

【Thread02】

public classThread02 implements Runable{
 
    privateResource r;
 
    publicThread02(Resource r) {
       this.r = r;
    }
 
    @Override
    public voidrun() {
       r.name = "name02";
       r.age = 2;
       System.out.println(r.toString());
    }
}

【Main】

public static voidmain(String[] args) {
       // 实例化资源
       Resourcer = newResource();
 
       // 创建实现runable接口的资源控制器
       Thread01t01 = newThread01(r);
       Thread02t02 = newThread02(r);
 
       // 将资源控制器添加到线程
       Threadt1 = newThread(t01);
       Threadt2 = newThread(t02);
 
       // 开启线程运行
       t1.start();
       t2.start();
    }

    这样就完成了多线程的创建。概况一下,对于资源Reaource,两个资源控制类分别在各自的线程中去操作这个资源,实现了任务的分离进行。

3、资源共享问题和同步代码块及同步函数

    在上面的例子中,两个线程去同时操作一个资源,会产生资源共享问题,现在要解决的就是如何在一个线程操作资源的时候另一个线程不去操作呢。这里用到了同步代码块或是同步函数。

    同步代码块的解决方法是在运行的代码上加上synchronized关键词即

@Override
    public voidrun() {
       synchronized (r) {
           r.name = "name01";
           r.age = 1;
           System.out.println(r.toString());
       }
    }

    这里需要注意的是,对象锁必须是两个线程共享的对象才可以,而尽量不能是不同的对象,这里一般用资源对象作为对象锁。

    第二种方法是synchronized 用在方法签名上,当某个线程调用此方法时,会获取该实例的对象锁,方法未结束之前,其他线程只能去等待。当这个方法执行完时,才会释放对象锁。其他线程才有机会去抢占这把锁,但是发生这一切的基础应当是所有线程使用的同一个对象实例,才能实现互斥的现象。否则synchronized关键字将失去意义。

    由于以上实例用的不是同一个操控资源对象实例,因此在方法签名上加上synchronized是没有用的。

4、线程之间的通讯

    对于线程之间的通讯只要用到两个方法,this.notify(),这个方法是释放本线程的锁。而另一个方法是this.wait(),这个方法正好相反,则是终止自己的运行。

实例如下

【Main】

public static voidmain(String[] args) {
       Resourcesource = newResource();
       Inputin = newInput(source);
       Outputout = newOutput(source);
       Threadt1 = newThread(in);
       Threadt2 = newThread(out);
       t1.start();
       t2.start();
    }

【Resource】

public classResource {
    privateString name;
    privateString sex;
    private boolean flag = false;
 
    public synchronized voidset(String name, String sex) {
       if (flag)
           try {
              this.wait();
           }catch (InterruptedException e) {
           }
       if (!flag) {
           this.name =name;
           this.sex =sex;
           flag = true;
       }
       this.notify();
    }
 
    public synchronized voidout() {
       if (!flag)
           try {
              this.wait();
           }catch (InterruptedException e) {
           }
       System.out.println(name + ".........." + sex);
       flag = false;
       this.notify();
    }
}

【Input】

public classInput implementsRunnable {
    Resourcer;
 
    Input(Resourcer) {
       this.r = r;
    }
 
    @Override
    public voidrun() {
       int x =0;
       while (true) {
           if (x== 0) {
              r.set("mike", "nan");
           }else {
              r.set("丽丽", "女女女");
           }
           x= (x + 1) % 2;
       }
    }
 
}

【Output】

public classOutput implementsRunnable {
    Resourcer;
 
    Output(Resourcer) {
       this.r = r;
    }
 
    @Override
    public voidrun() {
       while (true) {
           r.out();
       }
    }
}

5、生产者消费者模型(经典案例)

    首先注意:在第3段和第4段的内容中,同步代码块(synchronized)或同步函数是对对象资源的操作,是为了防止多个线程同时操作一个对象资源,这个时候线程都是有机会得到cpu的执行权的,但是wait()notify()等之类的方法是对线程是否具有cpu的执行权限进行操作的,比如调用的wait()方法后,cpu就不会再去执行这个线程了,只能等待去唤醒它才会再执行。

    JDK1.5之前的模型,是while和notifyAll联合使用,一个锁只能有一组操作方法,实例如下:

【Main】

  public static voidmain(String[] args) {
       Resourcer = newResource();
       Producepro = newProduce(r);
       Consumercon = newConsumer(r);
       Threadt1 = newThread(pro);
       Threadt2 = newThread(pro);
       Threadt3 = newThread(con);
       Threadt4 = newThread(con);
       t1.start();
       t2.start();
       t3.start();
       t4.start();
    }

【Resource】

public classResource {
    privateString name;
    private int count = 1;
    private boolean flag = false;
 
    public synchronized voidset(String name) {
       while (flag)
           try {
              this.wait();
           }catch (InterruptedException e) {
           }
       this.name =name + count;
       count++;
       System.out.println(Thread.currentThread().getName()+ "...生产者..."
              +this.name);
       flag = true;
       notifyAll();// 唤醒所有的进程。
    }
 
    public synchronized voidout() {
       while (!flag)
           try {
              this.wait();
           }catch (InterruptedException e) {
           }
       System.out.println(Thread.currentThread().getName()
              +"........消费者........." + this.name);
       flag = false;
       notifyAll();
    }
}

【Producer】

public classProduce implementsRunnable {
   
    privateResource r;
 
    Produce(Resourcer) {
       this.r = r;
    }
 
    @Override
    public voidrun() {
       while (true) {
           r.set("烤鸭");
       }
    }
}

【Consumer】

public classConsumer implementsRunnable {
 
    privateResource r;
 
    Consumer(Resourcer) {
       this.r = r;
    }
 
    @Override
    public voidrun() {
       while (true) {
           r.out();
       }
    }
}

但是由于一个锁只能拥有一组方法,这样使得效率很低下,所以在JDK1.5之后有了新的改变:

Jdk1.5之后将同步和锁封装成了对象。并将操作锁的隐式方式定义到了该对象中,将隐式动作编程了显示动作。并且,Lock接口的出现替代了同步代码块或者同步函数。将同步的隐式操作变成了显式所操作。同时更为灵活,可以一个锁加上多组监视器。

lock();获取锁。

unlock:释放锁。通常需要定义finally代码块。

Condition接口:出现代替了Object中的wait,notify,notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象。可以任意锁进行组合。

【Main】

    public static voidmain(String[] args) {
       Resourcer = newResource();
       Producepro = newProduce(r);
       Consumercon = newConsumer(r);
       Thread t1 = new Thread(pro);
       Thread t2 = new Thread(pro);
       Thread t3 = new Thread(con);
       Thread t4 = new Thread(con);
       t1.start();
       t2.start();
       t3.start();
        t4.start();
    }

【Resource】

public classResource {
    // 创建一个锁对象,ReentrantLock是一个互斥锁
    Locklock = newReentrantLock();
    // 通过已有的锁创建两组监视器,一组监视生产者,一组监视消费者。
    Conditionpro_con = lock.newCondition();
    Conditioncon_con = lock.newCondition();
 
    privateString name;
    private int count = 1;
    private boolean flag = false;
 
    public voidset(String name) {
       // 建立资源锁
       lock.lock();
       try {
           while (flag)
              try {
                  pro_con.await();
              }catch (InterruptedException e) {
                  e.printStackTrace();
              }
           this.name =name + count;
           count++;
           System.out.println(Thread.currentThread().getName()+ "...生产者..."
                  +this.name);
           flag = true;
           // 释放执行权锁,并唤醒另一个线程
           con_con.signal();
       }finally {
           lock.unlock();
       }
    }
 
    public voidout() {
       lock.lock();
       try {
           while (!flag)
              try {
                  con_con.await();
              }catch (InterruptedException e) {
              }
           System.out.println(Thread.currentThread().getName()
                  +"........消费者........." + this.name);
           flag = false;
           pro_con.signal();
       }finally {
           lock.unlock();
       }
    }
}

【Producer】

public classProduce implementsRunnable {
   
    privateResource r;
 
    Produce(Resourcer) {
       this.r = r;
    }
 
    @Override
    public voidrun() {
       while (true) {
           r.set("烤鸭");
       }
    }
}

【Consumer】

public classConsumer implementsRunnable {
 
    privateResource r;
 
    Consumer(Resourcer) {
       this.r = r;
    }
 
    @Override
    public voidrun() {
       while (true) {
           r.out();
       }
    }
}

6、其他

(1)wait方法和sleep方法的区别

   wait可以指定时间,也可以不指定,sleep必须指定时间。并且,在同步中时,对cpu的执行权和锁的处理不同。

Wait:释放执行权,释放锁。

Sleep:释放执行权,不释放锁。

(2)守护线程(即后台线程):setDaemon()方法。后台线程开启后,如果前台线程全结束,它也跟着结束。

(3)join()方法:

    这个方法可以让cpu优先照顾它,例如,在main线程中加入t1.join();那么,在t1线程执行完之后,才会继续执行main方法,但是对其他的线程没有影响。

(4)interrupt()方法是用来中断线程执行的,但是要抛出异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值