黑马程序员-java 多线程

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------

由于以前工作不曾使用过多线程技术,而通过最近一段时间的学习,可能学的东西还很片面。使用起来也很生疏,为了能更好的记忆和理解,我来把我最近一段时间学习的东西做一些整理。

首先,对于多线程技术,我们要明白如下的定义:

1.什么是进程?

进程就是正在进行中的程序,也就是对应一个应用程序在内存中所使用的空间。

2. 什么是线程?

线程就是进程中用于控制程序执行的控制单元(执行路径,执行情景),而一个进程中至少要有一个线程。

3.什么是多线程

多线程就是一个进程中有多个(两个或两个以上)执行路径(控制单元)。

注意:对于JVM,启动时,就有两个线程:

1.jvm的主线程,也就是执行mian函数的线程。

2.jvm的垃圾回收线程。

创建线程的目的:就是开启一条线程,去运行指定的代码(线程要执行的代码),并且和其他代码同时运行,以达到多个线程来实现多任务并发,提高程序的执行效率。

那么,我们怎么来创建线程呢?在Java中提供了两种线程的方式:

一.继承Thread类

注:java提供的对线程描述的类是Thread类, 创建线程对象的方法是构造函数,提供了要被线程执行的代码存储的位置是在run()里面。

步骤:

1.继承Thread类。

2.覆盖run方法,将线程要运行的代码定义其中。(要覆盖run方法的原因是,定义线程要运行的代码存储在run()里面)

3.创建Thread类的子类对象,其实就是在创建线程,调用start方法(其中,start()有两个功能:1.开启线程 2.执行run()方法)。

class ThreadDemo extends Thread// 方法局限性,不能再继承其他父类.
{
    private String name;

    ThreadDemo(String name){

       //super(name);Thread中的构造函数,可以给线程自定义名字。

       this.name=name;

    }

    publicvoid run() {

       System.out.println(this.name+"  Thread run...");

    }

    publicstaticvoid main(String[]args){

       ThreadDemo t1=new ThreadDemo("线程一");

       ThreadDemo t2=new ThreadDemo("线程二");

       t1.start(); //开启线程,调用run方法

       t2.start();

    }

}

二.实现Runnable接口

注:下面的3种情况,就不可以再使用继承Thread类。

1.自定义的类中有多线程要运行的代码。但是该类有自己的父类。

2.要求程序具有功能的可扩展性。

3.多个功能使用一个共享数据。

那么,程序就提供了一个规则。通过实现Runnable接口。并将多线程要运行的代码存放在实现了Runnable类的子类的run方法中,然后调用对应的线程,进行处理。

 

步骤:

1,定义了实现Runnable接口。

2,覆盖接口的run方法。将多线程要运行的代码存入其中。

3,创建Thread类的对象(创建线程),并将Runnable接口的子类对象作为参数传递给Thread的构造函数。

       为什么要传递?因为线程要运行的代码都在Runnable子类的run方法中存储。所以要将该run方法所属的对象传递给Thread。让Thread线程去使用该对象调用其run方法。

4,调用Thread对象的start方法。开启线程。

 class ThreadDemo implements Runnable//实现Runnable接口

{

   publicvoid run(){
       //Thread.currentThread() 获取当前线程的对象,相当于this
       //Thread类的getName()   获取当前线程的名称
       System.out.println(Thread.currentThread().getName()+"  Runnablerun...");

    }

    publicstaticvoid main(String[] args){
       ThreadDemo demo=new ThreadDemo();

       Thread t1=new Thread(demo);

       Thread t2=new Thread(demo);

       t1.start(); //开启线程,调用run方法

       t2.start();

    }

}

通过上面的知识点,我们可以知道:

1.   线程都有自己默认的名称,格式是:Thread-编号,该编号是从0开始。

2.  使用多线程的好处是:解决了多部分同时运行的问题。

3.   继承Thred类和实现Runnable接口这两种方式,都能创建线程,但是综合比较,创建线程建议使用实现Runnable接口这种方式,原因如下:

A:通过Thread继承的类,有局限性,不能再继承其他类.,这是java单继承的特性。

B:实现Runnable接口实现多线程,可以实现数据共享,根据接口可以多个实现,并且可以继承其他类,所以功能的扩展性能好。

线程的生命周期

线程可以划分为5个状态:创建、运行、阻塞(临时状态)、冻结、消亡。线程启动后,并不是一直占用cpu独自运行,而是多个线程在cpu中进行切换运行,所以多线程具备随机性。


临时状态的特点:具备了执行资格,但不具备执行权。

冻结状态的特点:放弃了执行资格。

注:启动线程使用的start方法,而不是run方法,调用start方法启动线程,系统会把该run方法当成线程执行体来处理。如果直接调用线程对象的run方法,就相当在执行普通的方法。

多线程运行有什么问题么?

由于多线程的运行具备随机性,就有可能会产生多线程的安全问题。

问题的产生的原因:

几个关键点:

1,多线程代码中有操作共享数据。

2,多条语句操作该共享数据。

当出现上面的情况时,就有可能出现下面问题:

有一个线程对多条操作共享数据的代码执行的一部分。还没有执行完,另一个线程开始参与执行。就会发生数据错误。

解决的办法:

当一个线程在执行多条操作共享数据代码时,其他线程即使获取了执行权,也不可以参与操作。

根据上面的思路,那么可以使用Java中提供的同步的原理:就是将部分操作功能数据的代码进行加锁,就可以保证在同一时间只允许一个线程进行操作。

使用同步的前提是:

1,必须是两个或者两个以上的线程。

2,必须是多个线程使用同一个锁。

同步可以分为两种表现形式:

一.同步代码块

synchronized(对象) //该对象可以是任何类的对象.如Object类的对象

{

    //...操作共享数据的语句

}
二.同步函数

函数类型前+synchronized关键字,如下:

publicstaticsynchronizedvoid func(){

    //...方法体

}
它们的区别是:

1,同步代码块使用的锁是任意对象。

2,同步函数使用的锁是this。

注意:对于static的同步函数,使用的锁不是this。是 类名.class 是该类的字节码文件对象。

同步的优点和缺点:

优点:解决了线程的安全问题

缺点:要判断同步锁,较为消耗资源;并且同步嵌套后,容易死锁。

死锁:当两个线程相互等待对方释放同步监视器时就会发生死锁。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

 注:同步中可以有多条线程,但是只有一个线程在执行,谁拿锁谁执行

class Ticket implements Runnable
{
    private  inttick = 100;
    Object obj = new Object();
    publicvoid run()
    {
       //show(); //同步方法
       while(tick<=100)
       {
           synchronized(obj) //同步代码块
           {
              if(tick>0)
              {
                  System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);

              }
           }
       }
    }

    publicsynchronizedvoid show(){//同步方法
       for(int i=0;i<100;i++){
           System.out.println(Thread.currentThread().getName()+"....同步函数_"+i);
       }
    }
}
class  TicketDemo2
{
    publicstaticvoid main(String[] args)
    {
       Ticket t = new Ticket();
       Thread t1 = new Thread(t);
       Thread t2 = new Thread(t);
       Thread t3 = new Thread(t);
       t1.start();
       t2.start();
       t3.start();
    }
}

线程间通讯:其实就是多个线程在操作同一个资源,但是操作的动作不同。

为了让线程协调运行,Object类提供了如下三个方法:

wait():导致当前线程处于冻结状态,被wait的线程,会被存储到线程池中,可以通过其他线程使用notify()方法或notifyAll()方法来唤醒该线程。

notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意性的。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。

notifAll():唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可能执行被唤醒的线程。

注意:wait()和notify()等待和唤醒的锁,必须是同一个锁。

为什么这些操作线程的方法要定义Object类中呢?

       上面的方法只能在持有监视器(锁)的线程中操作,而监视器(锁)可以是任意对象,所以可以被任意对象调用的方法,要定义在Object类中。

其中上面的三个方法都使用在同步中,因为要在持有监视器(锁)的线程操作,但是同步才具有锁,所以只能使用在同步中。

/*
对于多个生产者和消费者。
*/
class Resource{
    private String name;
    privateintcount = 1;
    privatebooleanflag = false;
    publicsynchronizedvoid set(String name){     

       /*为什么要定义while判断标记。
       原因:让被唤醒的线程再一次判断标记。*/
       while(flag)
           try{this.wait();}catch(Exception e){}
       this.name = name+"--"+count++;

       System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
       flag = true;
       /*为什么定义notifyAll,
       因为需要唤醒对方线程。
       因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。*/
       this.notifyAll();
    }
    publicsynchronizedvoid out(){
       while(!flag)
           try{wait();}catch(Exception e){}
       System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);
       flag = false;
       this.notifyAll();
    }
}
class Producer implements Runnable{
    private Resource res;
    Producer(Resource res)
    {
       this.res = res;
    }
    publicvoid run()
    {
       while(true)
       {
           res.set("+商品+");
       }
    }
}

class Consumer implements Runnable{
    private Resource res;
    Consumer(Resource res){
       this.res = res;
    }
    publicvoid run(){
       while(true){
           res.out();
       }
    }
}
class ProducerConsumerDemo {
    publicstaticvoid main(String[] args) {
       Resource r = new Resource();
       Producer pro = new Producer(r);
       Consumer con= new Consumer(r);
       Thread t1 = new Thread(pro);
       Thread t2 = new Thread(con);
       t1.start();
       t2.start();

    }
}

其他常用方法:

interrupt():强制让线程恢复到运行状态中,清除冻结状态。

setDaemon():当正在运行的线程都是守护线程时,虚拟机退出,如果所有的前台线程都结束,后台线程无论处于何种状态都会自动结束。

join():哪个线程执行到join方法,哪个线程会释放执行资格,处于冻结状态。

setPriority(级别):设置线程的优先级,默认优先级是5。其中:线程优先级在1到10之间10个数。优先级越大被执行到的几率越大。

yield():可以使线程暂时释放执行权。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值