黑马软件工程师-Java多线程

黑马程序员---Java多线程

---------------------- Android开发、java培训、期待与您交流! ----------------------  

      线程与进程是程序运行中体现的两个名词,它包含这不同的程序域。进程指的是一个正在运行的程序,如:360,QQ运行时就是不同的两个进程。当你打开windows任务管理器时,看到的每个进程就是此时windows中正在运行的程序。线程则就是进程中的一个个独立的控制单元,线程控制着进程的执行,也就是说每个正在运行的程序中就包括着很多的线程。

主线程:Java虚拟机运行时会有一个Java.exe的进程执行,该进程中至少有一个线程负责Java程序的运行,而这个线程运行的代码在main方法中,此时该线程成为主线程。

多线程:一个进程中可以同时运行多个线程,很明显多线程可以提高程序的运行的速度。(虽然说是同时运行,其实cup在同一时刻只能运行一个线程,cup在做着快速的切换动作,由于速度极其的快,看上去就好像是同时运行,但多核除外)。

创建线程有两种方法

⑴继承Thread类

    ①定义类继承Thread类。

    ②重写Thread类的run()方法。

         ---->将线程要运行的代码定义在run()方法中。

    ③调用Thread类中的start()方法。

        ----->启动线程,须调用start()方法,不用手动调用run()方法,start()方法会调用run()方法。

⑵实现Runnable接口

    ①定义类实现Runnable接口。

         ------>这个实现Runnable接口的类是需要多线程执行的类

    ②重写Runnable接口中的run()方法。

         ------>Runnable接口中只有一个抽象方法run()方法,Thread类也实现了Runnable。

    ③需要多少个线程就创建多少个Thread类对象。(没考虑主线程)

         ------>一个Thread类对象就是一个线程

    ④将Runable接口的子类对象作为实际参数传递给Thread类的构造函数。

         ----->自定义的run方法所属的对象是Runable接口的子类对象,所以要让线程去指定对象的run方法。

    ⑤调用Thread类中start()方法启动线程既而run()方法也就被执行了。

         ----->执行start()方法就会自动执行run()方法。

创建线程的两种方法的区别与优劣:

实现Runnable接口的方法避免了Java单继承的局限性,在创建线程时建议使用实现的方法。

继承Thread:线程代码存在Thread子类run方法中。
实现Runable:线程代码存在接口的子类的run方法。

多线程的安全问题

        当多条语句操作同一个线程共享数据时,一个线程对语句只执行了一部分,另一个线程就进来执行了,导致共享数据的错误。

解决方法:

       对多条共享数据的语句,只能让一个线程都执行完了其它线程才可以去执行,在执行过程中,其他线程不可以参与执行,java对于多线程的安全问题提供了专业的解决方法,那就是同步代码块。

synchronized(对象)
{
    需要被同步的代码
}
      同步代码块使用的关键字是synchronized,所传递的对象为任意对象,这个对象就相当于锁,持有锁的线程才可以执行被同步的代码,否则就算拥有cpu执行权,也进不来。当被同步代码执行完释放锁后下一个待执行的线程才可以进去。

同步的前提:

     ①必须要有两个或两个以上的线程执行共享代码

            ----->只有这样同步才会有意义。

     ②必须是多线程使用同一个锁

            ------>对于多个synchronized(对象){},这个对象必须是同一对象,才可以做到未释放锁之前,只有一个线程在执行同步代码。

同步的优劣

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

劣点:每执行同步时都要判断锁,耗时,较为消耗资源,使用不当时会造成死锁。

同步方法

public synchronized void function(){}

      在非静态方法上加上synchronized关键字,与同步代码块功能相同,只是同步方法的锁是this,指的是当前类对象,而同步代码块的锁可以使任何对象。

public static synchronized void function(){}

       当同步方法为静态时,与同步代码块和非静态方法的区别只是锁,此时静态同步方法的锁为该方法所在类的字节码文件对象,  类名.class。静态进入内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。

Thread类中常用的操作线程的方法介绍

① static void sleep(int millis)

在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

② static Thread currentThread()
返回对当前正在执行的线程对象的引用。通常与getName()使用。

③ String getName() 

返回该线程的名称。 通常与currentThread()使用来获取当前正在执行的线程的名称,Thread.currentThread().getName()。

④ void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。

⑤ String toString()
返回该线程的字符串表示形式,包括线程名称、优先级和线程组。

⑥ void setPriority(int newPriority)
更改线程的优先级。 

⑦ static void yield()
暂停当前正在执行的线程对象,并执行其他线程。 

⑧ void interrupt()
中断线程。

⑨ void join()
等待该线程终止。

⑩ void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。

㈡定义在 Object类中的三类操作线程的方法介绍

① void notify()
唤醒在此对象同步上等待的单个线程。 

② void notifyAll()
唤醒在此对象同步上等待的所有线程。 

③ void wait()
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。

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

       这些方法存在同步中,使用这些方法时必须要标识所属的同步锁,锁可以是任意对象调用的方法一定定义Object类中。

wait()与sleep()的区别:

wait():释放资源,释放锁。
sleep():释放资源,不释放锁。

等待唤醒机制

等待唤醒机制要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁。必须要用到锁,等待和唤醒都必须定义到锁中。同时必然会用到wait()、notify()和notifyAll()三个方法。

第一个实例:

      得到一个人的信息,就打印这个人的信息,不可以得到多人再打印,也不可得到一个人重复打印多次。所以要使用多线程,并且要使用同步才能够解决。

⑴定义一个资源类(Res),描述资源。

⑵定义两个线程类,一个输入(Input)类,一个输出(Output)类,让他们都实现Runnable接口。

⑶定义main方法类,即为主线程。

class Res
{
	private String name;
	private String sex;
	private boolean flag = false;
	public synchronized void set(String name,String sex)
	{
		if(flag)
			try{this.wait();}catch(Exception e){}
		this.name = name;
		this.sex = sex;
		flag = true;
		this.notify();
	}
	public synchronized void out()
	{
		if(!flag)
			try{this.wait();}catch(Exception e){}
		System.out.println(name+"...."+sex);
		flag = false;
		this.notify();
	}
}
class Input implements Runnable
{
	private Res r;
	Input(Res r)
	{
		this.r = r;
	}
	public void run()
	{
		int x = 0;
		while (true)
		{
			if(x==0)
				 r.set("Mike","man");
				
				else
				    r.set("丽丽","女女");
			
					x = (x+1)%2;	
		}
	}
}
class Output implements Runnable
{
	private Res r;
Output(Res r)
{
	this.r = r;
}
	public void run()
	{
		while (true)
		{
			r.out();
		}
	}
}
class InputOutputDemo
{
	public static void main(String[] srgs)
	{
		Res r = new Res();
		new Thread(new Input(r)).start();
                new Thread(new Ontput(r)).start();

	}
}
第二个实例

生产者/消费者的程序来演示多个线程对共享资源的竞争。

(1)ProducerConsumerDemo类:提供程序入口main()方法,负责创建生产者和消费者线程,并且启动这些线程。

(2)producer类:生产者线程,不断向堆栈中加入产品。

(3)Consumer类:消费者线程,不断向堆栈中取出产品。

(4)Resource类:是资源类,允许从堆栈中取出或加入产品。

class ProducerConsumerDemo
{
	public static void 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(pro);
		Thread t3 = new Thread(con);
		Thread t4 = new Thread(con);

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

class Resource
{
	private String name;
	private int count = 1;
	private boolean flag = false;
	public synchronized void set(String name)
	{
		while(flag)
			try{this.wait();}catch(Exception e){}
		this.name = name+"--"+count++;
		System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
		flag = true;
		this.notifyAll();
	}
	public synchronized void out()
	{
		while(!flag)
			try{this.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;
	}
	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("+商品+");
		}
	} 
}
JDK1.5中提供了多线程升级解决方案
Condition接口中的await(),signal(),signalAll()将Object中的wait(),notify(), notifyAll()给替换了。synchronized同步被Lock接口给替换了,Lock接口有一个ReentrantLock实现类,用它创建Lock的对象,Lock接口中有一个newCondition()方法,可以创建Condition类的实例对象。Lock接口中的lock()和unlock()方法分别为获取锁和释放锁的操作,可替换synchronized锁。
将以上实例用多线程升级解决方案改写如下:
class ProducerConsumerDemo
{
	public static void 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(pro);
		Thread t3 = new Thread(con);
		Thread t4 = new Thread(con);

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

class Resource
{
	private String name;
	private int count = 1;
	private boolean flag = false;
	private Lock lock =new ReentrantLock();
	private Condition condition_pro = lock.newCondition();
	private Condition condition_con = lock.newCondition();
	public void set(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();//释放锁的动作一定要执行。
		}
	}
	public void out()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;
	}
	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("+商品+");
		}
	} 
}
线程中的常用操作解决方案:

一、停止线程。

最早用Thread类中stop()方法,但由于stop()方法具有不安全性,所以不建议使用了,成为过时方法。然而想让线程停止另有其它好方法。实际上停止线程就是停止run()方法。

(1)更改循环标记(终止循环)

    ----->因为线程运行代码一般都是循环结构,只要控制了循环即可.

    ----->但就算是要更改循环标记,如果当再更改循环标记之前wait()了,并不能立即结束线程,那该怎么办?此时线程机制任然还有其它好方法。如(2)可见。

(2)使用interrupt()方法,清除如wait()等方法的冻结状态,就会有机会读到标记,既而有机会终止循环终止线程。

    ------>该方法是结束线程的冻结状态,使线程回到运行状态中来。

二、守护线程。

守护线程又称用户线程也称后台线程,Java中用setDaemon()方法设置线程是否为守护线程,此方法参数为boolean类型,当传递true时则设置为守护线程否则为前台线程。

注意:

(1)setDaemon(boolean on)方法设置是否为守护线程,传递true则为守护线程,否则为前台线程。

(2)当所有线程都为守护线程时,则jvm就会退出。

(3)当所有的前台线程结束了,则守护线程也就都结束了。

(4)若前台线程没有结束,后台线程可结束也可不结束,全在设定。

(5)垃圾回收线程为守护线程。

三、join()等待线程终止。

此方法是Thread类中的,用来操作线程,意思是让别的线程等待直到当前执行线程执行结束。

               Resource r = new Resource();
		
		Producer pro = new Producer(r);
		Consumer con = new Consumer(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().join();  //③
		t4.start();         //④
主线程执行①②代码,启动t1和t2两个线程,此时主线程、t1、t2这三个线程交替执行,当执行③代码时,t3线程被启动,遇到join()方法,此时主线程立即冻结,主线程不会执行④代码,t4线程暂时不会被启动。t1、t2、t3这三个线程交替执行,直到t3线程执行结束,这时主线程被激活当抢到执行权时t4线程才会被启动,如果t1和t2还没执行结束,则此时就是t1、t2、t4、主线程抢夺cpu执行权了。

四、Thread类中toString()方法

它执行结果就是打印"线程名   优先级    线程组",线程名为Thread.currentThread().getName()的结果,优先级也就是线程被执行的频率,抢夺到cpu执行权的频率。线程组按我的理解应该是在哪个线程中启动了当前线程,则就是哪个线程的线程名。

五、线程优先级

线程优先级即为线程执行到的频率,优先级定义为int,是1---10之间的整数,1表示被执行到的频率最低,10表示被执行到的频率最高。它有以下5种操作线程优先级的方法。

  (1) public static final int MIN_PRIORITY

        ----->线程可以具有优先级最低,它的优先级实际上为1.

(2)public static final int NORM_PRIORITY

         ----->线程可以具有优先级为中等,是线程默认的优先级,它的优先级实际上为5

(3)public static final int MAX_PRIORITY

         ------>线程可以具有优先级为最高,它的优先级实际上为10.

4)setPriority(int newPriority)

         ------>更改优先级,可向方法中传递1----10的整数代表优先级的高低。

(5)int getPriority()

         ------>返回线程的优先级。

【总结】

在学习多线程的过程中,Java多线程的方方面面都给我们代码带来了灵活性和快捷性,包括创建线程,以及对多个线程进行调度、管理,我们深刻认识到了多线程编程的复杂性,以及线程切换开销带来的多线程程序的低效性,这也促使我们认真地思考一个问题:我们是否需要多线程?何时需要多线程?  
多线程的核心在于多个代码块并发执行,本质特点在于各代码块之间的代码是乱序执行的。我们的程序是否需要多线程,就是要看这是否也是它的内在特点。   
假如我们的程序根本不要求多个代码块并发执行,那自然不需要使用多线程;假如我们的程序虽然要求多个代码块并发执行,但是却不要求乱序,则我们完全可以用一个循环来简单高效地实现,也不需要使用多线程;只有当它完全符合多线程的特点时,多线程机制对线程间通信和线程管理的强大支持才能有用武之地,这时使用多线程才是值得的。

---------------------- Android开发、java培训、期待与您交流! ----------------------详细请查看:www.itheima.com