黑马程序员——Java基础---多线程

Posted 冬有雪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了黑马程序员——Java基础---多线程相关的知识,希望对你有一定的参考价值。

——Java培训、Android培训、iOS培训、UI培训、PHP培训、期待与您交流! ——-

一、线程生命周期

共五个状态:

  • 新建状态:
    Thread t = new Thread()

  • 就绪状态
    调用start()方法,

  • 运行状态
        使用yield()方法可以使线程主动放弃CPU。线程也可能由于执行结束或执行stop()方法进入死亡状态。每个线程对象都有一个run()方法,当线程对象开始执行时,系统就调用该对象的run()方法。
  • 阻塞状态
        线程从阻塞状态恢复到就绪状态有三种途径:自动恢复(例如:sleep 时间到、I/O 操作完成);用resume()方法恢复;用notify()或notifyAll()方法通知恢复。也可能因为别的线程强制某个处于阻塞状态的线程终止,该线程就从阻塞状态进入死亡状态。
  • 死亡状态
        进入死亡状态。一是线程完成了全部工作,即执行完run()方法的最后一条语句。另一种是线程被提前强制性终止。

二、线程优先级

    线程的优先级用数值表示,数值越大优先级越高(范围1~10)。每个线程根据继承特性自动从父线程获得一个线程的优先级,也可在程序中重新设置。对于任务较紧急的重要线程,可安排较高的优先级。相反,则给一个较低的优先级。

    每个Java 程序都有一个默认的主线程,就是通过JVM 启动的第一个线程。对于应用程序,主线程执行的是main()方法。

    对于Applet,主线程是指浏览器加载并执行小应用程序的那一个线程。子线程是由应用程序创建的线程。另有一种线程称为守护线程(Daemon),这是一种用于监视其他线程工作的服务线程,它的优先级最低。

三、Thread类

    Java 程序实现多线程应用有两种途径:一是继承Thread 类声明Thread 子类,用Thread 子类创建线程对象。二是在类中实现Runnable 接口,在类中提供Runnable 接口的run()方法。无论用哪种方法,都需要java 基础类库中的Thread类及其方法的支持。程序员能控制的关键性工作有两个方面:一是编写线程的run()方法;二是建立线程实例。

1. Thread 类为创建线程和线程控制提供以下常用的方法:

(1) Thread(),创建一个线程。线程名按创建顺序为Thread_1、Thread_2…等。
(2) Thread(String m),创建一个以m 命名的线程。
(3) Thread(Runnable target),创建线程,参数target 是创建线程的目标。目标是一个对象,对象的类要实现Runnable
接口,类中给出接口的run()方法。
(4) public Thread(Runnable target,String m),创建线程,参数target 是创建线程的目标,m 是线程名。
(5) Thread(ThreadGroup g,String m),创建一个以m 命名的线程,该线程属于指定线程组g。
(6) Thread(ThreadGroup g,Runnable target),创建一个属于指定线程组g 的线程,target 是线程的目标。
(7) Thread(ThreadGroup g,Runnable target,String m),创建一个线程,名为m,属于指定的线程组g,target 是线
程的目标。
(8) getPriority(),获得线程的优先级。(范围1~10)。
(9) setPriority(int p),设定线程的优先级为p。线程创建时,子线程继承父线程的优先级。
优先级的数值越大优先级越高(缺省为5)。常用以下3个优先级:
Thread.MIN_PRIORITY(最低:1),Thread.MAX_PRIORITY(最高:10)和Thread.NORMAL_PRIORITY(标准)。
(10) start(),启动线程,让线程从新建状态到就绪状态。
(11) run(),实现线程行为(操作)的方法。
(12) sleep(int dt),让线程休眠dt 时间,单位是毫秒。例如,代码
sleep(100);
让线程休眠100 毫秒时间段。在以后的这个时间段中,其他线程就有机会被执行。当休眠时间一到,将重新到就绪
队列排队。由于sleep()方法可能会产生Interrupted Exception 异常,应将sleep()方法写在try 块中,并用catch
块处理异常。sleep()方法是static 方法,不可重载。
(13) currentThread(),获得当前正在占有的CPU 的那个线程。
(14) getName(),获得线程的名字。
(15) setName(),设置线程的名字。
(16) isAlive(),返回boolean 类型值,查询线程是否还活跃。
(17) destroy(),强制线程生命期结束。
(18) stop(),强制线程生命期结束,并完成一些清理工作,及抛出异常。
有时,小应用程序的界面已从屏幕消失,但并没有停止线程的执行,会一直到浏览器关闭才结束。要在小应用程序
的stop()方法中终止线程的执行。
(19) suspend(),挂起线程,处理不可运行的阻塞状态。
(20) resume(),恢复挂起线程,重新进入就绪队列排队。
(21) yield(),暂停当前正执行的线程,若就绪队列中有与当前线程同优先□线程,则当前线程让出CPU 控制权,移到就绪队列的队尾。若队列中没有同优先级或更高优先级的线程,则当前线程继续执行。

2.用Thread 子类实现多线程

用Thread 子类实现多线程,得先声明一个Thread 类的子类,并在子类中重新定义run()方法。当程序需要建立线
程时,就可创建Thread 子类的实例,并让创建的线程调用start()方法,这时,run()方法将自动执行。

示例1:

//创建3个线程,和主线程交替运行
//执行是随机、交替执行的,每一次运行的结果都会不同。
class Thread3 extends Thread {
    public Thread3(String ThreadName) {
        super(ThreadName);
    }

    public void run() {
        for(int i = 0; i < 10; i++) {
            System.out.println("Thread " + Thread.currentThread().getName() + " is executing " + i);
        }
    }
}

public class Thread3T {
    public static void main(String[] args) {
        for(int i = 0; i < 3; i++) {
            System.out.println("Thread " + Thread.currentThread().getName() + " is executing " + i);
        }
        new Thread3("dong").start();
        new Thread3("shuai").start();
        new Thread3("dongshuai").start();
    }
}

示例2:

import java.util.Date;
public class ThreadTest {
    static ThreadA a;
    static ThreadB b;

    public static void main(String[] args) {
        a = new ThreadA();
        b = new ThreadB();
        a.start();
        b.start();
    }
}

class ThreadA extends Thread {

    public void run() {
        Date nowTime;
        for(int i = 0; i < 5; i++){
        nowTime = new Date();
        System.out.println("I am ThreadA, It is " + nowTime.toString());
        try {
            sleep(2000);
        }catch(InterruptedException e) {}
        }
    }
}

class ThreadB extends Thread {

    public void run() {
        Date nowTime;
        for(int i = 0; i < 5; i++){
        nowTime = new Date();
        System.out.println("I am ThreadB, It is " + nowTime.toString());
        try {
            sleep(1000);
        }catch(InterruptedException e) {}
        }
    }
}

四、Runnable接口

    Java.lang.Runnable 接口,只有run()方法需要实现。一个实现Runnable 接口的类实际上定义了一个在主线程之
外的新线程的操作。
    用Runnable 接口实现多线程的主要工作是:声明实现Runnable 接口的类,在类内实现run()方法;并在类内声明
线程对象,在init(),方法或start()方法中创建新线程,并在start()方法中启动新线程。
示例:小应用程序通过Runnable 接口创建线程。在类的start()方法中构造方法Thread(this)创建了一个新的
线程。this 代表着该类的对象作为这个新线程的目标,因此类必须为这个新创建的线程实现Runnable 接口,即提供run ()方法。在start()方法中构造了一个名myThread 的线程,并调用Thread 类start()方法来启动这个线程。这样,run()方法被执行,除小应用程序的主线程外,又开始了一个新线程myThread.在例子中run()方法在睡眠1秒后,调用repaint()方法重绘Applet 窗口。在paint()方法中,在文本区输出线程睡眠的次数,并用随机产生的颜色和半径涂一个圆块。

import java.applet.*;
import java.awt.*;
import javax.swing.*;
public class RunnableTest extends java.applet.Applet implements Runnable {
    Thread t = null;
    JTextArea j;
    int i;

    public void start() {
        j = new JTextArea(20,20);
        add(j); i = 0; setSize(500,500);

        if(t == null) {
            t = new Thread(this);//创建新线程
            t.start();
        }
    }

    public void run() {
        while(t != null) {
            try {t.sleep(1000); i ++;}
            catch(InterruptedException e) {}
            repaint();
        }
    }

    public void paint(Graphics g) {
        double d = Math.random();
        if(d < 0.5) g.setColor(Color.yellow);
        else g.setColor(Color.green);

        g.fillOval(10,10,(int)(100 * d),(int)(100 * d));
        j.append("I am working, I have " + i + " rest");
    }

    public void stop() {
        if(t != null) {
            t.stop();
            t = null;
        }
    }
}

示例2

//多窗口卖包子
class PorkDumpling implements Runnable {
    private int pd = 100;
    public void run() { //覆盖Runnable接口中的run方法。目的也是为了将线程要运行的代码存放在该run方法中。
        while(true) {   
            if(pd > 0) {
                    System.out.println(Thread.currentThread().getName() + " Window is saling Pork Dumpling. There are " + pd-- + " Pork Dumpling");
            }
        }   
    }
}

public class MultiThreading {
    public static void main(String[] args) {
        PorkDumpling p = new PorkDumpling();
        Thread t1 = new Thread(p);  //通过Thread类创建线程对象。
        Thread t2 = new Thread(p);
        Thread t3 = new Thread(p);
        Thread t4 = new Thread(p);
        Thread t5 = new Thread(p);

        t1.start();//启动线程
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

五、线程同步synchronized

同步的两种方式:
    一种是同步代码块,还有就是同步函数。都是利用关键字synchronized来实现。

用法:
synchronized(对象) {需要被同步的代码}

1.同步代码块

示例

public class Synchronized {
    public static void main(String[] args) {
        SynchronizedTest s = new SynchronizedTest();

        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        Thread t3 = new Thread(s);
        Thread t4 = new Thread(s);
        Thread t5 = new Thread(s);

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

class SynchronizedTest implements Runnable {
    private int pork = 80;
    Object obj = new Object();
    public void run() {
        while(true) {
            synchronized(obj){  //给程序加同步,即锁 
                if(pork > 0) {
                    try {
                        Thread.sleep(100);   //使用线程中的sleep方法,模拟线程出现的安全问题
                    }catch(InterruptedException e) {}
                    System.out.println(Thread.currentThread().getName() + " is running " + pork--);
                }
            }
        }
    }
}

2.同步函数

    在函数上加上synchronized修饰符即可。
    函数需要被对象调用。那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。
示例:

public class SynchronizedFunction {
    public static void main(String[] args){
        ThreadTest tt = new ThreadTest();
        Thread t1 = new Thread(tt);
        Thread t2 = new Thread(tt);
        Thread t3 = new Thread(tt);
        Thread t4 = new Thread(tt);
        Thread t5 = new Thread(tt);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

class ThreadTest implements Runnable {
    private int pork = 50;
    public void run() {
        while(true) {
            synchronizedFunction();
        }
    }

    public synchronized void synchronizedFunction() {
        if(pork > 0) {
            try {
                Thread.sleep(10);
            }catch(InterruptedException e) {}
            System.out.println(Thread.currentThread().getName() + " saling , remain "  + pork--);
        }
    }
}

4.静态函数同步方式

        由于在调用静态方法时,对象实例不一定被创建,因此,就不能使用this来同步静态方法,而必须使用Class对象来同步静态方法。

如果同步函数被静态修饰后,使用的锁是什么呢?
        通过验证,发现不再是this。因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。如:类名.class 该对象的类型是Class。这就是静态函数所使用的锁。而静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class

注意:在使用synchronized块来同步方法时,非静态方法可以通过this来同步,而静态方法必须使用class对象来同步,但是非静态方法也可以通过使用class来同步静态方法。但是静态方法中不能使用this来同步非静态方法。这点在使用synchronized块需要注意。

//加同步的单例设计模式——懒汉式
class Single {
    private static Single s = null;
    private Single() {}
    public static void getInstance() {
        if(s == null) {
            synchronized(Single.class) {
                if(s == null) s = new Single();
            }
        }
    }
}

示例1:

public class Test{

 pubic static void method1(){
   synchronized(Test.class){
    }
 }
  public static synchronized void method2(){

   }
}

        在同步静态方法时可以使用类的静态字段class来得到class对象,在上例中method1和method2方法只有一个方法执行,除了使用class字段可以得到class对象,还可以通过实例的getClass()方法获取class对象,代码如下:

public class Test{
 public static Test test;
 public Test(){
 test=this;
 }
 public static void method1(){
 synchronized(test.getClass()){
 }
 }
}

        在上面的代码中,我们通过一个public的静态对象得到Test的一个实例,并通过这个实例的getClass方法获取一个class对象(注意一个类的所有实例通过getClass方法得到的都是同一个Class对象)。我们也可以通过class使不同类的静态方法同步,代码如下:

public class Test1{
 public static void method1(){
  synchronized(Test.class){
   }
 }
}

5.死锁

        假如有2个线程,一个线程想先锁对象1,再锁对象2,恰好另外有一个线程先锁对象2,再锁对象1。在这个过程中,当线程1把对象1锁好以后,就想去锁对象2,但是不巧,线程2已经把对象2锁上了,也正在尝试去锁对象1。什么时候结束呢,只有线程1把2个对象都锁上并把方法执行完,并且线程2把2个对象也都锁上并且把方法执行完毕,那么就结束了,但是,谁都不肯放掉已经锁上的对象,所以就没有结果,这种情况就叫做线程死锁。

        其中一个解决方法就是加大锁定的粒度,也就是尽量锁大的对象,不要锁得太小,还有尽量不要同时锁2个或2个以上的对象,但是还有待于进一步研究。

示例:

//死锁示例
class LockAB {  //定义两个锁
    static Object la = new Object();
    static Object lb = new Object();
}

class LockRun implements Runnable { //定义一个类来实现Runnable,并复写run方法  
    private boolean b;
    public LockRun(boolean bo) {
        this.b = bo;
    }

    public void run() {
        if(b) {
            while(true) {
                synchronized(LockAB.la) {
                    System.out.println(Thread.currentThread().getName() + " LockAB.la");
                    synchronized(LockAB.lb) {
                        System.out.println(Thread.currentThread().getName() + " LockAB.lb");
                    }
                }
            }
        }else {
            while(true) {
                synchronized(LockAB.lb) {
                    System.out.println(Thread.currentThread().getName() + " LockAB.lb");
                    synchronized(LockAB.la) {
                        System.out.println(Thread.currentThread().getName() + " LockAB.la");
                    }
                }
            }
        }
    }
}

public class DeadLockTest { //创建2个进程,并启动  
    public static void main(String[] args) {
        new Thread(new LockRun(false)).start();
        new Thread(new LockRun(true)).start();
    }
}

六、线程间通信

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

/*
有一个资源
一个线程往里存东西,如果里边没有的话
一个线程往里取东西,如果里面有得话
*/

//资源
class Resource
{
    private String name;
    private String sex;
    private boolean flag=false;

    public synchronized void setInput(String name,String sex)
    {
        if(flag)
        {
            try{wait();}catch(Exception e){}//如果有资源时,等待资源取出
        }
        this.name=name;
        this.sex=sex;

        flag=true;//表示有资源
        notify();//唤醒等待
    }
    public synchronized void getOutput()
    {       
        if(!flag)
        {
            try{wait();}catch(Exception e){}//如果木有资源,等待存入资源
        }
        System.out.println("name="+name+"---sex="+sex);//这里用打印表示取出

        flag=false;//资源已取出
        notify();//唤醒等待
    }
}
//存线程
class Input implements Runnable
{
    private Resource r;
    Input(Resource r)
    {
        this.r=r;
    }
    public void run()//复写run方法
    {
        int x=0;
        while(true)
        {
            if(x==0)//交替打印张三和王羲之
            {
                r.setInput("张三",".....man");
            }
            else
            {
                r.setInput("王羲之","..woman");
            }
            x=(x+1)%2;//控制交替打印
        }
    }
}

//取线程
class Output implements Runnable
{
    private Resource r;
    Output(Resource r)
    {
        this.r=r;
    }
    public void run()//复写run方法
    {
        while(true)
        {
            r.getOutput();
        }
    }
}



class ResourceDemo2 
{
    public static void main(String[] args) 
    {
        Resource r = new Resource();//表示操作的是同一个资源

        new Thread(new Input(r)).start();//开启存线程

        new Thread(new Output(r)).start();//开启取线程
    }
}

几个小问题:

1)wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?

a,这些方法存在与同步中。
b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。
c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。

2)wait(),sleep()有什么区别?

wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。

3)为甚么要定义notifyAll?

        因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。

JDK1.5中提供了多线程升级解决方案。

        将同步synchronized替换成显示的Lock操作。将Object中wait,notify,notifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。

/*
生产者生产商品,供消费者使用
有两个或者多个生产者,生产一次就等待消费一次
有两个或者多个消费者,等待生产者生产一次就消费掉
*/
import java.util.concurrent.locks.*;

class Resource 
{   
    private String name;
    private int count=1;
    private boolean flag = false;

    //多态
    private Lock lock=new ReentrantLock();

    //创建两Condition对象,分别来控制等待或唤醒本方和对方线程
    Condition condition_pro=lock.newCondition();
    Condition condition_con=lock.newCondition();

    //p1、p2共享此方法
    public void setProducer(String name)throws InterruptedException
    {
        lock.lock();//锁
        try
        {
            while(flag)//重复判断标识,确认是否生产
                condition_pro.await();//本方等待

            this.name=name+"......"+count++;//生产
            System.out.println(Thread.currentThread().getName()+"...生产..."+this.name);//打印生产
            flag=true;//控制生产\消费标识
            condition_con.signal();//唤醒对方
        }
        finally
        {
            lock.unlock();//解锁,这个动作一定执行
        }

    }

    //c1、c2共享此方法
    public void getConsumer()throws InterruptedException
    {
        lock.lock();
        try
        {
            while(!flag)//重复判断标识,确认是否可以消费
                condition_con.await();

            System.out.println(Thread.currentThread().getName()+".消费."+this.name);//打印消费
            flag=false;//控制生产\消费标识
            condition_pro.signal();
        }
        finally
        {
            lock.unlock();
        }

    }
}

//生产者线程
class Producer implements Runnable 
{
    private Resource res;
    Producer(Resource res)
    {
        this.res=res;
    }
    //复写run方法
    public void run()
    {
        while(true)
        {
            try
            {
                res.setProducer("商品");
            }
            catch (InterruptedException e)
            {
            }
        }
    }
}

//消费者线程
class Consumer implements Runnable
{
    private Resource res;
    Consumer(Resource res)
    {
        this.res=res;
    }
    //复写run
    public void run()
    {
        while(true)
        {
            try
            {
                res.getConsumer();
            }
            catch (InterruptedException e)
            {
            }
        }
    }

}

class  ProducerConsumer
{
    public static void main(String[] args) 
    {
        Resource res=new Resource();

        new Thread(new Producer(res)).start();//第一个生产线程 p1
        new Thread(new Consumer(res)).start();//第一个消费线程 c1

        new Thread(new Producer(res)).start();//第二个生产线程 p2
        new Thread(new Consumer(res)).start();//第二个消费线程 c2
    }
}

七、停止线程

        在JDK 1.5版本之前,有stop停止线程的方法,但升级之后,此方法已经过时。
那么现在我们该如果停止线程呢?
        只有一种办法,那就是让run方法结束。
1、开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。

如:run方法中有如下代码,设置一个flag标记。
        那么只要在主函数或者其他线程中,在该线程执行一段时间后,将标记flag赋值false,该run方法就会结束,线程也就停止了。

public  void run()  
{  
    while(flag)  
    {     
                   System.out.println(Thread.currentThread().getName()+"....run");  
    }  
}  

2、上面的1方法可以解决一般情况,但是有一种特殊情况:就是当线程处于冻结状态。就不会读取到标记。那么线程就不会结束。
         当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。Thread类提供该方法interrupt();

class InterruptThread implements Runnable {
    private boolean flag = true;
    public void run() {
        while(flag) {
            System.out.println(Thread.currentThread().getName() + " is running!");
        }
    }

    public void changeFlag() {
        this.flag = false;
    }
}

public class ThreadInterrupt {
    public static void main(String[] args) {
        InterruptThread it = new InterruptThread();
        Thread t1 = new Thread(it);
        Thread t2 = new Thread(it);
        t1.start();
        t2.start();
        int i = 0;
        while(true) {

            if(i++ == 10) {
                t1.interrupt();
                t2.interrupt();
                it.changeFlag();

                break;
            }
            System.out.println(Thread.currentThread().getName() + i);
        }
        System.out.println("Over!");
    }
}

八、什么时候使用多线程

         当某些代码需要同时被执行时,就用单独的线程进行封装。

class  WhenUseThread
{
    public static void main(String[] args) 
    {
        //一条线程
        new Thread()
        {
            public void run()
            {
                for (int x=0;x<10 ;x++ )
                {
                    System.out.println(Thread.currentThread().toString()+"....."+x);
                }
            }
        }.start();

        //又是一条线程
        Runnable r= new Runnable()
        {
            public void run()
            {
                for (int x=0;x<10 ;x++ )
                {
                    System.out.println(Thread.currentThread().getName()+"....."+x);
                }
            }
        };
        new Thread(r).start();

        //可以看作主线程
        for (int x=0;x<10;x++ )
        {
            System.out.println("Hello World!");
        }

    }
}

——Java培训、Android培训、iOS培训、UI培训、PHP培训、期待与您交流! ——-

以上是关于黑马程序员——Java基础---多线程的主要内容,如果未能解决你的问题,请参考以下文章

黑马程序员_java08_多线程

(黑马Java多线程与并发库高级应用)05 线程范围内共享变量的概念与作用

(黑马Java多线程与并发库高级应用)02 传统定时器技术回顾

黑马程序员————多线程单例设计模式线程间通信,JDK1.5互斥锁

黑马程序员——Java基础---StringBufferArrays包装类

(黑马Java多线程与并发库高级应用)01 传统线程技术回顾