黑马程序员——多线程

来源:互联网 发布:java 汉字转英文缩写 编辑:程序博客网 时间:2024/06/14 03:36

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-

1.概述

  进程:一个程序的执行。
  线程:程序中单个顺序的流控制。
  一个进程中至少要有一个线程,可以含有多个线程。一个进程中的多个线程分享CPU(并发的或以时间片的方式),共享内存(如多个线程访问同一对象)。Java支持多线程,如:Object中wait(),notify()。
  多线程解决了多部分代码同时运行的问题。但线程太多,会导致效率的降低。
  JVM启动时启动了多条线程,至少有两个线程可以分析的出来:
  1)执行main函数的线程,该线程的任务代码都定义在main函数中。
   2)负责垃圾回收的线程。System类的gc方法告诉垃圾回收器调用finalize方法,但不一定立即执行。
  线程体是由run()方法来实现的。线程启动后,系统就自动调用run()方法。通常,run()方法执行一个时间较长的操作,如一个循环,显示一系列图片或下载一个文件等。
  对线程的基本控制:
  1)线程的启动:start()
  2)线程的结束:设定一个标记变量,以结束相应的循环及方法。
  3)暂时阻止线程的执行:try{ Thread.sleep( 1000 );} catch( InterruptedException e ){ }
  4)设定线程的优先级:setPriority(int priority)方法:MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY

线程的状态:

2.创建线程的两种方法

1.继承Thread类
  a.定义一个类继承Thread类。
  b.覆盖Thread类中的run方法。
  c.直接创建Thread的子类对象创建线程。
  d.调用start方法开启线程并调用线程的任务run方法执行。

class Demo extends Thread{    private String name;    Demo(String name){        this.name = name;    }    public void show(){        for( int x = 0; x < 10; x++){            //通过Thread的getName方法获取线程的名称。            System.out.println(name + "...x = " + x + "...ThreadName = " + Thread.currentThread().getName());        }    }}public class ThreadDemo {    public static void main(String[] args){        Demo d1 = new Demo("Amy");        Demo d2 = new Demo("Lucy");        //开启线程,调用run方法        d1.start();        d2.start();        for (int x = 0; x < 20; x++){            System.out.println("x = " + x + "...over..." + Thread.currentThread().getName());        }    }}

2.实现Runnable接口
  a.定义类实现Runnable接口;
  b.覆盖接口中的run方法,将线程的任务代码封装到run方法中。
  c.通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
  d.调用线程对象的start方法开启线程。
实现Runnable接口的好处:
 1.将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
 2.避免了Java单继承的局限性。所以,创建线程的第二种方式较为常用。

class Demo2 implements Runnable{    //覆盖接口中的run方法,将线程的任务代码封装到run方法中。    public void run(){        show();    }    public void show(){        for (int x = 0; x < 20 ; x++){            System.out.println(Thread.currentThread().getName()+"..."+x);        }    }}public class ThreadDemo2 {    public static void main(String[] args){        Demo2 d = new Demo2();        //通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。        Thread t1 = new Thread(d);        Thread t2 = new Thread(d);        t1.start();        t2.start();    }}

3.线程同步

  线程安全问题产生的原因:同时运行的线程需要共享数据。
  线程安全问题的解决方案:Java引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个monitor(监视器),它上面一个称为“互斥锁(lock, mutex)”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。关键字synchronized 用来与对象的互斥锁联系。
  同步代码块:
  synchronized(对象){
    需要被同步的代码;
  }
  同步函数:
  synchronized 放在方法声明中。
  
  同步的好处:解决了线程的安全问题。
  同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
  同步的前提:必须有多个线程并使用同一个锁。

/*售票问题*/class Ticket implements Runnable{    private int num = 100;    boolean flag = true;    public void run(){        if (flag){            while(true){                //同步代码块,锁是任意的对象,synchronized(this)表示整个方法为同步方法。                synchronized(this){                    if (num > 0){                        try{                            Thread.sleep(10);                        }catch(InterruptedException e){                            e.printStackTrace();                        }                        System.out.println(Thread.currentThread().getName()+"...obj..." + num--);                                                                   }                       }        }    }else        while(true)            show();    }    //同步函数,锁是固定的this    public synchronized void show(){        if (num > 0){            try{                Thread.sleep(10);            }catch(InterruptedException e){                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName()+"...function..." + num--);        }    }}public class TicketDemo {    public static void main(String[] args){        Ticket t = new Ticket();        Thread t1 = new Thread(t);        Thread t2 = new Thread(t);        t1.start();        try{//下面这条语句一定要执行。因为可能线程t1尚未真正启动,flag已经设置为false,            //那么当t1执行的时候,就会按照flag为false的情况执行,线程t2也按照flag为false的情况            //执行,实验就达不到目的了。            Thread.sleep(10);        }catch(InterruptedException e){            e.printStackTrace();        }        t.flag = false;        t2.start();    }}

静态的同步代码块使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。

多线程下的单例模式
  饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况。

/* * 加同步的单例模式——懒汉式 */public class Single {    private static Single s = null;    private Single(){}    //若使用同步函数,则效率较低,因为每次都需要判断    public static Single getInstance(){        if(s == null){            synchronized(Single.class){ //synchronized(this.getClass)                if (s == null)                    s = new Single();            }        }        return s;    }}

4.死锁

死锁常见情景之一:同步的嵌套。

class Test implements Runnable{    private boolean flag;    Test(boolean flag){        this.flag = flag;    }    public void run(){        if(flag){            while(true)                synchronized(MyLock.locka){                    System.out.println(Thread.currentThread().getName()+"...if locka...");                    synchronized(MyLock.lockb){                        System.out.println(Thread.currentThread().getName()+"...if lockb...");                    }                }        }else{            while(true)                synchronized(MyLock.lockb){                    System.out.println(Thread.currentThread().getName()+"...else lockb...");                    synchronized(MyLock.locka){                        System.out.println(Thread.currentThread().getName()+"...else locka...");                    }                }        }    }}class MyLock{    public static final Object locka = new Object();    public static final Object lockb = new Object();}class DeadLockDemo {    public void main(String[] args){        Test a = new Test(true);        Test b = new Test(false);        Thread t1 = new Thread(a);        Thread t2 = new Thread(b);        t1.start();        t2.start();    }}

5.线程间通信

wait():让线程处于阻塞状态,被wait的线程会被存储到线程池中。
notify():唤醒线程池中的一个线程(任何一个都有可能)。
notifyAll():唤醒线程池中的所有线程。

/* * 文件架实例,一个存一个取。 */class CubbyHole{    private int index = 0;    private int[] data = new int[3];    //向data中输入数,当data满时阻塞,否则向里面存入一个数,并唤醒另一个线程    public synchronized void put(int value){        while(index == data.length){            try{                //阻塞线程                this.wait();            }catch(InterruptedException e){}        }        data[index] = value;        index ++;        //唤醒线程        this.notify();    }    //从data中取数,当data为空时阻塞,否则从中取出一个数,并唤醒另一个线程    public synchronized int get(){        while(index <= 0){            try{                //阻塞线程                this.wait();            }catch(InterruptedException e){}        }        index --;        int val = data[index];        //唤醒线程        this.notify();        return val;    }}

  JDK1.5中增加了更多的类,以便更灵活地使用锁机制:java.util.concurrent.locks包,Lock接口、ReentrantLock类,ReadWriteLock接口、ReentrantReadWriteLock类。
  JDK1.5中将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。
  Lock接口:替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵活,可以一个锁上加上多组监视器。
  lock():获取锁。
  unlock():释放锁,为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。
  Condition接口:替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。
  await() —> wait()
  signal() —> notify()
  signalAll() —> notifyAll()

/* *多生产者-多消费者问题  */import java.util.concurrent.locks.*;class Resource{    private String name;    private int count = 1;    private boolean flag = false;    //创建一个锁对象    Lock lock = new ReentrantLock();    //通过已有的锁获取该锁上的监视器对象    Condition con = lock.newCondition();    public void set(String name){        lock.lock();        try{            while(flag) //重复判断生产者是否生产                try{                    con.await();//生产者等待                }catch(InterruptedException e){                    e.printStackTrace();                }            this.name = name + count;            count++;            System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);            flag = true;            con.signalAll();//唤醒消费者        }finally{            lock.unlock();//解锁        }    }    public void out(){        lock.lock();        try{            while(!flag) //重复判断是否可以消费                try{                    con.await();//消费者等待                }catch(InterruptedException e){                    e.printStackTrace();                }            System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);            flag = false;            con.signalAll();//唤醒生产者        }finally{            lock.unlock();//解锁        }    }}//生产者class Producer implements Runnable{    private Resource r;    Producer(Resource r){        this.r = r;    }    //复写run()    public void run(){        while(true){            r.set("Item");        }    }}//消费者class Consumer implements Runnable{    private Resource r;    Consumer(Resource r){        this.r = r;    }    //复写run()    public void run(){        while(true){            r.out();        }    }}public class ResourceDemo {    public static void main(String[] args){        Resource r  = new Resource();        Producer pro = new Producer(r);        Consumer con = new Consumer(r);        Thread t0 = new Thread(pro); //生产者线程t0        Thread t1 = new Thread(pro); //生产者线程t1        Thread t2 = new Thread(con); //消费者线程t2        Thread t3 = new Thread(con); //消费者线程t3        t0.start();        t1.start();        t2.start();        t3.start();    }}

  停止线程:
  1)运行状态的线程:结束循环
  2)阻塞状态的线程:使用interrupt()方法,让线程具备CPU的执行资格。强制动作会发生InterruptedException。

class StopThread implements Runnable{    private boolean flag = true;    public void run(){        while(flag){            try{                wait();            }catch(InterruptedException e){                System.out.println(Thread.currentThread().getName()+"..."+e);                flag = false;            }            System.out.println(Thread.currentThread().getName()+"......");        }    }    public void setFlag(){        flag = false;    }}public class StopThreadDemo {    public static void main(String[] args){        StopThread st = new StopThread();        Thread t1 = new Thread(st);        Thread t2 = new Thread(st);        t1.start();        t2.start();        int num = 1;        for(;;){            if(++num == 50){                //使用interrupt()方法来结束阻塞状态的线程                t1.interrupt();                t2.interrupt();                break;            }            System.out.println("main..."+num);        }        System.out.println("over");    }}

6.线程类的其他方法

  线程有两类:一类是普通线程(非Daemon线程)。在Java程序中,若还有非Daemon线程,则整个程序就不会结束。另一类是Daemon线程(守护线程,后台线程)。如果普通线程结束了,则后台线程自动终止。如垃圾回收线程就是后台线程,使用setDaemon(true)方法。
  join():等待该线程终止
  toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
  yield():暂停当前正在执行的线程对象,并执行其他线程。

class Demo1 implements Runnable{    public void run(){        for (int x = 0; x < 50; x++){            System.out.println(Thread.currentThread().getName()+"..."+x);            Thread.yield(); //释放执行权        }    }}public class JoinDemo {    public static void main(String[] args){        Demo1 d = new Demo1();        Thread t1 = new Thread(d);        Thread t2 = new Thread(d);        t1.start();        try{            t1.join();//t1线程要申请加入进来,运行。然后,主线程等待t1执行完。        }catch(InterruptedException e){            e.printStackTrace();        }        t2.start();        t2.setPriority(Thread.MAX_PRIORITY);        for(int x = 0; x < 50; x++){            System.out.println(Thread.currentThread().toString()+"..."+x);        }    }}
0 0
原创粉丝点击