黑马程序员—【Java基础篇】之多线程

------- android培训 java培训 、期待与您交流! ---------

    这篇如标题,介绍和总结多线程。

    多线程内容总结,主要分一下几大块:一、进程;二、线程(例:FlashGet);三、多线程存在的意义四、线程的创建方式多线程的特性。

一、进程

1、定义

/**
进程:是一个正在执行中的程序。
	每一个进程执行都有一个执行顺序;该顺序是一个执行路径,或者叫一个控制单元。
*/

二、线程

1、定义

/**
    线程:就是进程中的一个独立的控制单元。线程在控制着进程的执行。通俗说就是进程是发生的一件事情,而线程就是控制着或者掌控这件事情的发生,一个线程中至少有一个线程。
*/

三、多线程

1、定义

/**
    Java VM  启动的时候会有一个进程java.exe.
    该进程中至少一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程。
    扩展:
    其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。
*/

2、如何创建多线程

/**
    多线程的出现能让程序产生同时运行效果。可以提高程序执行效率。
    例如:在java.exe进程执行主线程时,如果程序代码特别多,在堆内存中产生了很多对象,而同时对象调用完后,就成了垃圾。    如果垃圾过多就有可能是堆内存出现内存不足的现象,只是如果只有一个线程工作的话,程序的执行将会很低效;而如果有另一个线程帮助处理的话,如垃圾回收机制线程来帮助回收垃圾的话,程序的运行将变得更有效率。
*/

3、创建方式

(1)继承Thread类

/**
第一步:子类覆盖父类中的run方法,将线程运行的代码存放在run中。
第二步:建立子类对象的同时线程也被创建,通过调用start方法开启线程。
*/

    示例:

<span style="font-size:14px;">class Demo extends Thread//创建第一种方式,继承Thread类。
{
	public void run()//覆盖Thread类run()方法。
	{
		for(int x=0; x<60; x++)
			System.out.println("demo run----"+x);
	}
}
class ThreadDemo //主函数
{
	public static void main(String[] args) 
	{
		Demo d = new Demo();//创建好了一个线程。
		d.start();//开启线程并执行该线程的run方法。
		//d.run();仅仅是对象调用方法。而线程创建了,并没有运行。
	}
}</span>

    练习题:创建2个或以上线程交替运行。

class Test extends Thread	//继承了Thread类。
{
	Test(String name)
	{
		super(name);	//引用父类构造函数。
	}
	public void run()	//覆盖Thread中run()方法。
	{
		for(int x=0; x<60; x++)
		{
			System.out.println((Thread.currentThread()==this)+"..."+this.getName()+" run..."+x);
		}
	}
}
class ThreadTest 
{
	public static void main(String[] args) 
	{
		Test t1 = new Test("one---");
		Test t2 = new Test("two+++");
		t1.start();//线程t1运行;
		t2.start();//线程t2运行;
		//t1和t2同时运行。
		for(int x=0; x<60; x++)//运行一个主线程
		{
			System.out.println("main....."+x);
		}
	}
}	

(2)实现Runnable接口

/**
1、子类覆盖接口中的run方法。
2、通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。
3、Thread类对象调用start方法开启线程。
*/
    示例:
<pre name="code" class="java">class Ticket implements Runnable//实现Runnable接口。
{
	private  int tick = 100;
	public void run()//覆盖方法
	{
		while(true)
		{
			if(tick>0)
			{
				System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
			}
		}
	}
}
class  TicketDemo
{
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();
		Thread t1 = new Thread(t);//创建了一个线程;
		Thread t2 = new Thread(t);//创建了一个线程;
		Thread t3 = new Thread(t);//创建了一个线程;
		Thread t4 = new Thread(t);//创建了一个线程;
		t1.start();
		t2.start();
		t3.start();
		t4.start();
        }
}
 
     
 思考:为什么要给Thread类的构造函数传递Runnable的子类对象? 
/**
    因为自定义的run方法所属的对象是Runnable接口的子类对象。
    所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。
*/

(3)两种创建方式区别

 /**
   1.当某个类已经有父类了,这时无法用继续Thread方法建立线程,因为JAVA中无法多继承
   2.当某个类已经有父类了,这时可以实现Runnable方法建立线程,这样既继承了父类的属性又能建立线程。
   3.Runnable建立的多线程可以使用同一对象中封装的数据,而Thread要实现这样的功能必须将数据static化,但会导致数据的生命周期过长。
*/

4、线程的几种状态

    如图:


/**
 被创建:等待启动,调用start启动。
         运行状态:具有执行资格和执行权。
         临时状态(阻塞):有执行资格,但是没有执行权。
         冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
         消忙状态:stop()方法,或者run方法结束。
注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常。
*/
    图解:

5、线程的安全问题

(1)多线程安全问题引出

/**
    当多个线程操作同一个共享数据时,一个线程对共享数据的多条代码只执行了一部分,还没执行完,而其他线程参与进来,导致共享数据错误。<pre name="code" class="java">    刚才示例中出现了,卖票程序卖到了-1、-2张票,这是怎么出来的呢?原因如下:
    当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还未执行完,另一线程就参与进来执行了,导致共享数据发生错误。也就是说,由于cpu的快速切换,当执行线程一时,tic为1了,执行到if (tic > 0)的时候,cpu就可能将执行权给了线程二,那么线程一就停在这条语句了,tic还没减1,仍为1;线程二也判断if (tic > 0)是符合的,也停在这,以此类推。当cpu再次执行线程一的时候,打印的是1号,执行线程二的时候,是0号票,以此类推,就出现了错票的结果。其实就是多条语句被共享了,如果是一条语句,是不会出现此种情况的。
*/
 

    那么如何解决这个问题,如何呢?ok,不要着急,Java开发者们,考虑到了这种情况,于是提供了一种解决方案,且听我娓娓道来。

(2)解决问题

/**
    对于多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可参与执行,就不会出现问题了。Java对于多线程的安全问题,提供了专业的解决方式,即同步代码块,可操作共享数据。
a、同步代码块
        用法:
                  synchronized(对象)
                  {需要被同步的代码}<pre name="code" class="java">b、同步函数
    同步函数就是将修饰符synchronized放在返回类型的前面,下面通过同步函数给出多线程安全问题的具体解决方案:

1)目的:判断程序中是否有安全问题,若有,该如何解决。
2)解决:第一、明确哪些代码是多线程的运行代码
                  第二、明确共享数据
                  第三、明确多线程运行代码中,哪些语句是操作共享数据的。
*/
 
 

(3)示例

//1.同步代码块示例:
class Ticket implements Runnable
{
	private  int tick = 1000;
	Object obj = new Object();//任意对象
	public void run()
	{
		while(true)
		{
			synchronized(obj)//同步代码块,obj是一把锁
			{
				if(tick>0)
				{
					//try{Thread.sleep(10);}catch(Exception e){}//等待10秒钟
					System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
				}
			}
		}
	}
}

class  TicketDemo2
{
	public static void main(String[] args) 
	{

		Ticket t = new Ticket();

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}<pre name="code" class="java">//2.同步函数示例:
class Ticket implements Runnable  
{  
    private int tick=100;  
    Object obj = new Object();  
    public void run()  
    {  
        while(true)  
        {  
            show();  
        }  
    }  
  //直接在函数上用synchronized修饰即可实现同步  
public synchronized void show()  
{  
        if(tick>0)  
        {  
        try  
        {     
            //使用线程中的sleep方法,模拟线程出现的安全问题  
            //因为sleep方法有异常声明,所以这里要对其进行处理  
            Thread.sleep(10);  
        }  
        catch (Exception e)  
        {  
        }  
        //显示线程名及余票数  
        System.out.println(Thread.currentThread().getName()+"..tick="+tick--);  
    }  
}     
}  
 
 

(4)小结

/**
   1. 同步代码块示例中:
 其中的对象如同锁,持有锁的线程可在同步中执行,没有锁的线程,即使获得cpu的执行权,也进不去,因为没有获取锁,是进不去代码块中执行共享数据语句的。
1)同步的前提:

a.必须有两个或两个以上的线程

b.必须保证同步的线程使用同一个锁,必须保证同步中只能有一个线程在运行。

    好处与弊端:解决了多线程的安全问题,多个线程需要判断锁,较为消耗资源。
   2. 同步函数
        格式:
                在函数上加上synchronized修饰符即可。
        那么同步函数用的是哪一个锁呢?
        函数需要被对象调用。那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。
*/

(5)静态函数的同步方式

/**
    如果同步函数被静态修饰后,经验证,使用的锁不是this了,因为静态方法中不可定义this,所以,这个锁不再是this了。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象:类名.class;该对象的类型是Class。
    所以静态的同步方法使用的锁是该方法所在类的字节码文件对象,即类名.class。
*/
    示例:
/* 
加同步的单例设计模式————懒汉式 
*/  
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();  
            }  
        }  
        return s;  
    }  
}  

6、死锁

    当同步中嵌套同步时,就有可能出现死锁现象。

    示例:

/*
死锁。
同步中嵌套同步。

*/
class Ticket implements Runnable
{
	private  int tick = 1000;
	Object obj = new Object();
	boolean flag = true;
	public  void run()
	{
		if(flag)
		{
			while(true)
			{
				synchronized(obj)
				{
					show();
				}
			}
		}
		else
			while(true)
				show();
	}
	public synchronized void show()//this
	{
		synchronized(obj)
		{
			if(tick>0)
			{
				try{Thread.sleep(10);}catch(Exception e){}
				System.out.println(Thread.currentThread().getName()+"....code : "+ tick--);
			}
		}
	}
}
class  DeadLockDemo
{
	public static void main(String[] args) 
	{

		Ticket t = new Ticket();

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		try{Thread.sleep(10);}catch(Exception e){}
		t.flag = false;
		t2.start();
	}
}
     原因分析
/**
    分析死锁的原因:当t1、t2两个线程同时运行时,t1执行到代码到lockb时,t2抢夺了CPU的执行权,t1执行过程暂停,而t2在执行到locka时又被t1抢夺了执行权,但当t1执行到lockb时因为锁在t2手里没有释放,t1无法继续执行,同理t2,这样造成了程序死锁。
*/

7、线程间通信

(1)线程间通信初步解决

<span style="font-size:14px;">/**</span>
    多线程间的通信:将不同功能的代码封装在不同的线程代码中操作共同的资源。
    实现的思想:
        1.两线程实现Runnable接口,必须传入同一个对象
        2.建立一资源类,设置成员变量,就是线程中的共享数据
        3.在实现Runnable的子类中,将资源类名设置为成员变量,设置构造函数传入的参数为资源类型的变量,复写run()方法,操作资源数据。
*/
    示例1:
<span style="font-size:12px;">class Resource
{
	private String name;
	private int count = 1;
	private boolean flag = false;
			//  t1    t2
	public synchronized void set(String name)
	{
		while(flag)
			try{this.wait();}catch(Exception e){}//t1(放弃资格)  t2(获取资格)
		this.name = name+"--"+count++;

		System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
		flag = true;
		this.notifyAll();
	}
	//  t3   t4  
	public synchronized void out()
	{
		while(!flag)
			try{wait();}catch(Exception e){}//t3(放弃资格) t4(放弃资格)
		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;
	}
	public void run()
	{
		while(true)
		{
			res.set("+商品+");
		}
	}
}

class Consumer implements Runnable
{
	private Resource res;

	Consumer(Resource res)
	{
		this.res = res;
	}
	public void run()
	{
		while(true)
		{
			res.out();
		}
	}
}
class ResourceDemo2   
{  
    public static void main(String[] args)   
    {  
        Resource r = new Resource();//表示操作的是同一个资源  
  
        new Thread(new Input(r)).start();//开启存线程  
  
        new Thread(new Output(r)).start();//开启取线程  
    }  
}  
/**
     解决方式: Wari()——notifyall(),判断条件为while
判断条件为while后,每次都要判断后才能wait,而notifyall可以将等待的输出线程全部唤醒。
分析示例发现了几个小问题:
        1)wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
                a,这些方法存在与同步中。
                b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。
                c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
        2)wait(),sleep()有什么区别?
              wait():释放cpu执行权,释放锁。
              sleep():释放cpu执行权,不释放锁。
        3)为甚么要定义notifyAll?
        因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。
*/</span>

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

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

    示例2:

/* 
线程间通信:  等待唤醒机制:升级版 ;生产者消费者  多个 
 
*/  
import java.util.concurrent.locks.*;  
  
class ProducerConsumerDemo{  
    public static void main(String[] args){  
        Resouse r = new Resouse();  
        Producer p = new Producer(r);  
        Consumer c = new Consumer(r);  
        Thread t1 = new Thread(p);  
        Thread t2 = new Thread(c);  
        Thread t3 = new Thread(p);  
        Thread t4 = new Thread(c);  
        t1.start();  
        t2.start();  
        t3.start();  
        t4.start();  
    }  
}  
  
class Resouse{  
    private String name;  
    private int count = 1;  
    private boolean flag =  false;   
    private Lock lock = new ReentrantLock();  
    private Condition condition_P = lock.newCondition();  
    private Condition condition_C = lock.newCondition();  
//要唤醒全部,否则都可能处于冻结状态,那么程序就会停止。这和死锁有区别的。  
    public void set(String name)throws InterruptedException{  
        lock.lock();  
        try{  
            while(flag)//循环判断,防止都冻结状态  
                condition_P.await();  
            this.name = name + "--" + count++;  
            System.out.println(Thread.currentThread().getName() + "..生成者--" + this.name);  
            flag = true;  
            condition_C.signal();  
        }finally{  
            lock.unlock();//释放锁的机制一定要执行  
        }         
    }  
    public void out()throws InterruptedException{  
        lock.lock();  
        try{  
            while(!flag)//循环判断,防止都冻结状态  
                condition_C.await();  
            System.out.println(Thread.currentThread().getName() + "..消费者." + this.name);  
            flag = false;  
            condition_P.signal();//唤醒全部  
        }finally{  
            lock.unlock();  
        }  
    }  
}  
  
class Producer implements Runnable{  
    private Resouse r;  
    Producer(Resouse r){  
        this.r = r;  
    }  
    public void run(){  
        while(true){  
            try{  
                r.set("--商品--");  
            }catch (InterruptedException e){}  
        }  
    }  
}  
  
class Consumer implements Runnable{  
    private Resouse r;  
    Consumer(Resouse r){  
        this.r = r;  
    }  
    public void run(){  
        while(true){  
            try{  
                r.out();  
            }catch (InterruptedException e){}  
        }  
    }  
}  <pre name="code" class="java">/**
示例说明:
1)、为何定义while判断标记:
原因是让被唤醒的线程再判断一次。
避免未经判断,线程不知是否应该执行,就执行本方的上一个已经执行的语句。如果用if,消费者在等着,两个生成着一起判断完flag后,cpu切换到其中一个如t1,另一个t3在wait,当t1唤醒冻结中的一个,是t3(因为它先被冻结的,就会先被唤醒),所以t3未经判断,又生产了一个。而没消费。

2)这里使用的是signal方法,而不是signalAll方法。是因为通过Condition的两个对象,分别唤醒对方,这就体现了Lock锁机制的灵活性。可以通过Contidition对象调用Lock接口中的方法,就可以保证多线程间通信的流畅性了。
*/
 
 

8、Thread中的其它方法

/**
1、停止线程:
    在java 1.5之后,就不再使用stop方法停止线程了。那么该如何停止线程呢?只有一种方法,就是让run方法结束。
    开启多线程运行,运行代码通常为循环结构,只要控制住循环,就可以让run方法结束,也就可以使线程结束。
  注:
    特殊情况:当线程处于冻结状态,就不会读取标记,那么线程就不会结束。如下:
*/
class StopThread implements Runnable{  
    private boolean flag = true;  
    public synchronized void run(){  
        while (flag){  
            try{  
                wait();  
            }catch (InterruptedException e) {  
                System.out.println(Thread.currentThread().getName() + "----Exception");  
                flag = false;  
            }  
            System.out.println(Thread.currentThread().getName() + "----run");  
        }  
    }  
    public void changeFlag(){  
        flag = false;  
    }  
}  
  
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 n = 0;  
        while (true){  
            if (n++ == 60){  
                st.changeFlag();  
                break;  
            }  
            System.out.println("Hello World!");  
        }  
    }  
}
   /**
     这时,当没有指定的方式让冻结的线程回复打破运行状态时,就需要对冻结进行清除。强制让线程回复到运行状态来,这样就可以操作标记让线程结束。
    在Thread类中提供了此种方法:interrupt()。此方法是为了让线程中断,但是并没有结束运行,让线程恢复到运行状态,再判断标记从而停止循环,run方法结束,线程结束。
*/
class StopThread implements Runnable{  
    private boolean flag = true;  
    public synchronized void run(){  
        while (flag){  
            try{  
                wait();  
            }catch (InterruptedException e){  
                System.out.println(Thread.currentThread().getName() + "----Exception");  
                flag = false;  
            }  
            System.out.println(Thread.currentThread().getName() + "----run");  
        }  
    }  
}  
  
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 n = 0;  
        while (true){  
            if (n++ == 60){  
                t1.interrupt();  
                t2.interrupt();  
                break;  
            }  
            System.out.println("Hello World!");  
        }  
    }  
}  
/**
2、守护线程:---setDaemon()
    可将一个线程标记为守护线程,直接调用这个方法。此方法需要在启动前调用守护线程在这个线程结束后,会自动结束,则Jvm虚拟机也结束运行。 
  守护线程(后台线程),在启动前调用。后台线程自动结束。
*/  
    t1.setDaemon(true);  
    t2.setDaemon(true);  
    t1.start();  
    t2.start();  

/**
    3、临时加入线程:--join()
特点:当A线程执行到B线程方法时,A线程就会等待,B线程都执行完,A才会执行。join可用来临时加入线程执行。
*/
class Demo implements Runnable{  
    public void run(){  
        for(int x=0;x<90;x++){  
            System.out.println(Thread.currentThread().getName() + "----run" + x);  
        }  
    }  
}  
  
class  JoinDemo{  
    public static void main(String[] args)throws Exception{  
        Demo d = new Demo();  
        Thread t1 = new Thread(d);  
        Thread t2 = new Thread(d);  
        t1.start();       
        t2.start();  
        t1.join();//等t1执行完了,主线程才从冻结状态恢复,和t2抢执行权。t2执不执行完都无所谓。  
        int n = 0;  
        for(int x=0;x<80;x++){  
            System.out.println(Thread.currentThread().getName() + "----main" + x);  
        }  
        System.out.println("Over");  
    }  
}  
/**
4、优先级:setPriority():
    在Thread中,存在着1~10这十个执行级别,最高的是 MAX_PRIORITY 为10,最低是 MIN_PRIORITY 为1,默认优先级是 NORM_PRIORITY 为5;但是并不是优先级越高,就会一直执行这个线程,只是说会优先执行到这个线程,此后还是有其他线程会和此线程抢夺cpu执行权的。
    优先级是可以设定的,可通过setPriority()设定,如:setPriority(Thread.MAX_PRIORITY)设优先级为最大。
    yield():
    此方法可暂停当前线程,而执行其他线程。通过这个方法,可稍微减少线程执行频率,达到线程都有机会平均被执行的效果。如下:
*/
class Demo implements Runnable{  
    public void run(){  
        for(int x=0;x<90;x++){  
            System.out.println(Thread.currentThread().toString() + "----run" + x);  
            Thread.yield();//稍微减少线程执行频率。可达到线程都有机会达到平均运行的效果  
        }  
    }  
}  
  
class  YieldDemo{  
    public static void main(String[] args)throws Exception{  
        Demo d = new Demo();  
        Thread t1 = new Thread(d);  
        Thread t2 = new Thread(d);  
        t1.start();  
        t1.setPriority(Thread.MAX_PRIORITY);//设置线程优先级最大       
        t2.start();  
        System.out.println("Over");  
    }  
}
    这篇就到这里吧,下篇咱们再见。


------- android培训 java培训 、期待与您交流! ---------



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值