Java基础(九)——多线程

时间:2023-02-16 08:03:02
1,多线程概述     进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
    线程:就是进程中的一个控制单元。线程控制着进程的执行。一个进程至少有一个线程。
    多线程:是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
    例如,JVM启动时会有一个java.exe的进程,该进程至少有一个线程负责java程序的运行,而且该线程位于main方法中,被称为主线程。
          同时,jvm会另外启动一条超级线程,这个线程就是负责垃圾回收的。
    好处:可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。

2,线程的创建和启动
2.1,继承Thread类来创建线程类
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就是代表了该线程要完成的任务。因此,run方法又称为方法执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)用线程对象的start方法来启动该线程。
示例:
class Demo extends Thread {  
public void run() {
for (int i = 0; i < 60; i++) {
System.out.println("demo run......." + i);
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Demo d = new Demo();
d.start();
for (int x = 0; x < 60; x++) {
System.out.println("Hello World!..." + x);
}
}
}

运行结果:
Java基础(九)——多线程
Thread类中的部分方法:
getName()获取线程的名称,默认线程的名称:Thread-编号;该编号从0开始,主线程默认为main。
static Thread currentThread():获取当前线程对象
String getName():获取线程名称
设置线程名称:void setName(String name)或者构造函数

2.2,实现Runnable接口创建线程类
(1)定义类实现Runnable接口;
(2)覆盖Runnable接口中的run()方法;将线程要运行的代码存放在该run()方法中
(3)通过Thread类创建线程对象;
(4)将Runnable接口的子类对象作为实例参数传递Thread类的构造函数;
     为什么要将Runnable接口的子类对象传递给Thread类的构造函数?
     因为,自定义的run()方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run()方法,就必须明确该run()方法所属对象。
(5)调用Thread类的start()方法开启线程并调用Runnable接口中的run()方法;
示例:
class Demo implements Runnable {  
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "...." + i);
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}


2.3,两种线程创建方式对比:
采用实现Runnable接口的方式的多线程:
(1)线程类只是实现了Runnable接口,该类还可以继承其它类;
(2)多个线程可以共享同一个target对象,非常适合多个线程来处理同一份资源,可以将CPU、代码和数据分开,形成清晰的模型,较好的体现了OOP思想;
(3)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。
采用继承Thread类方式的多线程:
(1)优点:编写简单,如果需要访问当前线程,可以直接使用this即可获取当前线程;
(2)缺点:继承了Thread类,就不能继承其它的父类。
实际上几乎所有的多线程程序都采用实现Runnable接口的方式。

3,线程安全问题
首先看如下代码:

//实现Runnable接口的方式,模拟卖票小程序  
class Ticket implements Runnable {
private int ticket = 100;
public void run() {
while (true) {
if (ticket > 0)
try {Thread.sleep(10);} catch (Exception e) {e.printStackTrace();}
System.out.println(Thread.currentThread().getName() + "..."+ ticket--);
}
}
}
class TicketDemo2 {
public static void main(String[] args) {
Ticket t = new Ticket();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}


运行结果:

Java基础(九)——多线程

分析:发现打印出负数结果,多线程运行出现了安全问题。当多条语句在操作同一条线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一条线程参与进来执行,导致共享数据错误。为此Java引入了同步代码块来解决这个问题。

3.1,同步代码块
synchronized(对象)
{
    需要被同步的代码
}
    对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获得了cpu的执行权,也进不去,因为没有获取锁。
任何时刻只能有一条线程可以获得对同步监视器的锁定,当同步代码块执行结束后,该线程自然释放了对该同步监视器的锁定。
同步的前提:
(1)必须有两个或者两个以上的线程。
(2)必须是多个线程使用同一个锁。
(3)必须保证同步中只能有一个线程在运行。
如何确定哪些代码需要同步:
(1)明确哪些代码是多线程运行代码。
(2)明确共享数据。
(3)明确多线程运行代码中哪些语句是操作共享数据的。

好处:解决了多线程的安全问题。
弊端:多个线程必须判断锁,较为消耗资源。

3.2,同步方法
[修饰符] synchronized 返回值类型 方法名(形参列表)
{
    方法执行体...
}
    同步方法就是使用synchronized关键字来修饰的某个方法,则该方法称为同步方法。对于同步方法而言,无须显式指定同步监视器,同步方法的同步监视器是this,也就是对象的本身。

注意:(1)synchronized关键字可以修饰方法,可以修饰代码块,但是不能修饰构造器和属性。
           (2)存放线程执行体的run()方法不能用synchronized关键字修饰,否则这样线程依然不安全,只能修饰另外一个方法,然后在run()方法中调用该方法。
           (3)当同步代码块和同步方法同时维护多线程安全时,要保证他们持有的同步监视器必须相同,即this,否则线程不安全。

静态同步方法:
    以上的结论只是针对一般同步方法而言的,如果同步方法被静态static修饰后,那么该方法使用的同步监视器是什么呢?
静态方法随着类的加载而加载,内存中并没有对象,但是一定有该类的字节码文件,java中字节码文件也是对象,所以静态同步方法使用的同步监视器是“类名.class”。
    当同步代码块和静态同步方法同时维护多线程安全时,要保证他们持有的同步监视器必须相同,即class文件,否则线程不安全。

4,线程安全的懒汉式单例设计模式

   单例设计模式模式的主要特点是,只生成一个对象,代码只能操作本类的同一个对象,单例设计模式,有2种,恶汉式和懒汉式。
   恶汉式:(线程安全)
class Single {  
private static final Single s = new Single();
private Single() {}
public static Single getInstance() {
return s;
}
}

懒汉式:(线程不安全)
解决线程不安全方式一(同步方法):
class Single {  
private static Single s = null;
private Single() {}
public static synchronized Single getInstance() {
if (s == null)
s = new Single();
return s;
}
}


解决线程不安全方式二(同步代码块):
class Single {  
private static Single s = null;
private Single() {
}
public static Single getInstance() {
if (s == null) {
synchronized (Single.class) {
if (s == null)
s = new Single();
}
}
return s;
}
}

常见问题:
(1)懒汉式有什么特点?答:延迟加载,但是线程不安全,多线程操作容易出现安全隐患。
(2)安全隐患怎么解决?答:有两种方式,一是用同步方法,二是用同步代码块。
(3)哪种效率高?答:因为使用同步方法,每个线程执行到该方法时,必须判断同步监视器,这种操作会导致运行效率低下。
使用同步代码块,也要多次执行判断同步监视器,代码效率略高于同步方法,但是不很理想。解决方法是,加上双重判断,第一重
判断代码加上同步代码块,这样可以有效地降低判断同步监视器的次数,效率略高。实际开发中,该选择恶汉式。
(4)加同步的时候,使用的锁是哪一个?答:该类所属的字节码文件对象。

5,死锁

        当两个线程互相等待对方释放同步监视器时会发生死锁,即同步嵌套。Java虚拟机没有监测,也没有采用措施来处理死锁情况,所以多线程编程时应该采用措施避免死锁的出现。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
       死锁是很容易发生的,尤其在系统中出现多个同步监视器的情况下,下面的程序将会演示死锁:  
class DeadLock implements Runnable {  
private boolean flag;
DeadLock(boolean flag) {
this.flag = flag;
}
public void run() {
if (flag) {
// 同步代码块嵌套
synchronized (MyLock.lockb) {
System.out.println("else...lockb");
synchronized (MyLock.locka) {
System.out.println("else...locka");
}
}
} else {
synchronized (MyLock.locka) {
System.out.println("if...locka");
synchronized (MyLock.lockb) {
System.out.println("if...lockb");
}
}
}
}
}
class MyLock {
static Object locka = new Object();
static Object lockb = new Object();
}
public class DeadLockDemo {
public static void main(String[] args) {
/*
* 开启两条线程,一个跑if代码块,另一个跑else代码块,这样在 嵌套中获取同步监视器的时候,产生冲突,造成程序运行阻塞
*/
new Thread(new DeadLock(true)).start();
new Thread(new DeadLock(false)).start();
}
}


运行结果:
Java基础(九)——多线程
注意:由于Thread类的suspend()方法也很容易导致死锁所以java不再推荐使用该方法来暂停线程的执行了。

6,线程间通信:其实就是多个线程操作同一个资源,但是操作的动作不同。
6.1,看示例代码:

/* 
编写一段程序模拟线程间通信,Res类描述共享资源,
Input类描述向资源输入数据,Output类描述从资源中输入数据
*/
class Res {
String name;
String sex;
}
class Input implements Runnable {
private Res r;
Input(Res r) {
this.r = r;
}
public void run() {
boolean flag = true;
while (true) {
if (flag == true) {
r.name = "Mike";
r.sex = "male";
flag = false;
} else {
r.name = "李丽";
r.sex = "女";
flag = true;
}
}
}
}
class Output implements Runnable {
private Res r;
Output(Res r) {
this.r = r;
}
public void run() {
while (true) {
System.out.println(r.name + "..." + r.sex);
}
}
}
class InputOutputDemo {
public static void main(String[] args) {
// 因为Input和Output操作的是同一资源,所以必须只产生一个对象,分别传递给这2个类
Res r = new Res();
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
}
}


运行结果:

Java基础(九)——多线程

分析:看看运行结果,明显出现了数据紊乱,线程不安全
解决方案:要从多线程的三个前提开始考虑,得到的结论是,Input类和Output类都各有一线程在运行,不能保证数据安全,此为线程不安全,
解决的办法是加同步代码块,并且必须同时加在Input类和Output同一个类线程运行代码中,必须保证使用同一个同步监视器。
根据观察,不能使用同步方法,因为同步方法使用的同步监视器是本类对象,也就是this,而上述两条线程在不同类中,使用的同步监视器不是同一个对象。
Input类:
while (true) {  
synchronized (r) {
if (flag == true) {
r.name = "Mike";
r.sex = "male";
flag = false;
} else {
r.name = "李丽";
r.sex = "女";
flag = true;
}
}
}

Output类:
while (true) {  
synchronized (r) {
System.out.println(r.name + "..." + r.sex);
}
}


运行结果如图:
Java基础(九)——多线程
6.2,等待唤醒机制
    通过上图所示的运行结果可以看出,我们已经解决了数据紊乱的问题,但是还有一个问题没有解决,我们需要解决的是
通过2条独立线程,实现输入一条数据,然后再打印数据,数据交替变得有规律,不能重复输入输出。示例代码:
/* 
编写一段程序模拟线程间通信,Res类描述共享资源,
Input类描述向资源输入数据,Output类描述从资源中输入数据
要求输入一次,然后打印一次,不得多次输入多次输出
*/
class Res3 {
String name;
String sex;
boolean tag = false;// 定义标记,标记代码是否有数据等待输入/出
}
class Input3 implements Runnable {
private Res3 r;
Input3(Res3 r) {
this.r = r;
}
public void run() {
boolean flag = true;
while (true) {
synchronized (r) {
if (r.tag)// tag为true代表有数据要输出,输入线程要等待
try {
r.wait(); // 有数据,等待输出
} catch (Exception e) {
}
if (flag == true) {
r.name = "Mike";
r.sex = "male";
flag = false;
} else {
r.name = "李丽";
r.sex = "女";
flag = true;
}
r.tag = true; // 标记改为true,说明有数据要输出
r.notify(); // 唤醒输出线程
}
}
}
}
class Output3 implements Runnable {
private Res3 r;
Output3(Res3 r) {
this.r = r;
}
public void run() {
while (true) {
synchronized (r) {
if (!r.tag)
try {
r.wait(); // 没有数据,等待输入
} catch (Exception e) {
}
System.out.println(r.name + "..." + r.sex);
r.tag = false; // 修改标记,通知输入线程
r.notify(); // 唤醒输入线程
}
}
}
}
public class InputOutputDemo3 {
public static void main(String[] args) {
// 因为Input和Output操作的是同一资源,所以必须只产生一个对象,分别传递给这2个类
Res3 r = new Res3();
new Thread(new Input3(r)).start();
new Thread(new Output3(r)).start();
}
}


运行结果:
Java基础(九)——多线程
    当线程在系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行,但我们可以通过一些机制来保证线程的协调运行。
    为了实现这种功能,可以借助Object类中的wait(),notify()和notifyAll()方法,这三个方法不属于Thread类,而是属于Object类。但这三个方法必须由同步监视器对象来调用,可以分成两种情况:
(1)对于使用synchronized修饰的同步方法,因为该类的this就是同步监视器,所以可以在同步方法中直接调用这三个方法。
(2)对于使用synchronized修饰的同步代码块,同步监视器是synchronized后括号里的对象,所以必须使用该对象调用这三个方法。
 关于这三个方法的解释:
(1)wait():导致当前线程等待,直到其它线程调用该同步监视器的notify()方法和notifyAll()方法来唤醒该线程。调用wait()方法的当前线程会释放对该同步监视器的锁定。
(2)notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程,选择是任意的。
               只有当前线程放弃对该同步监视器的锁定后(使用wait()),才可以执行被唤醒的线程。
(3)notifyAll():唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。

6.3,同步锁(Lock)
        从JDK1.5之后,Java提供了另外一种线程同步机制:它通过显式定义同步锁对象来实现同步,在这种机制下,同步锁应该使用Lock对象充当。
        通常认为:Lock提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock实现允许更灵活的结构,可以具有差别很大的属性,并且可以支持多个相关的Condition对象。
        Lock是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。不过,某些锁可能允许对共享资源的并发访问,如ReadWriteLock(读写锁)。当然,在实现线程安全的控制中,通常喜欢使用ReentrantLock(可重入锁)。
       如果程序部使用synchronized关键字来保证同步,而是直接使用Lock对象来保证同步,则系统中不存在隐式的同步监视器对象,也不能使用wait、notify、notifyAll方法来协调进程的运行。
        当使用Lock对象来保证同步时,Java提供了一个Condition类来保持协调,使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,Condition对象也可以唤醒其它处于等待的线程。
        Condition将同步监视器方法(wait、notify、notifyAll)分解成截然不同的对象,以便通过将这些对象与Lock对象组合使用,为每个对象提供多个等待集(wait-set)。在这种情况下,Lock替代了同步方法或同步代码块,Condition替代了同步监视器的功能。
        实例实质上被绑定到一个Lock对象上。要获得特定Lock实例的Condition实例,调用Lock对象newCondition()方法即可。Condition类提供了如下三个方法:
void await():类似隐式同步监视器上的wait()方法,导致当前线程等待,直到其他线程调用该Condition的signal()方法或signalAll()方法来唤醒该线程。
void signal():唤醒在此Lock对象上等待的单个线程。如果所有线程都在该Lock对象上等待,则会选择唤醒其中一个线程。选择是任意性的。只有当前线程放弃对该Lock对象的锁定后(使用await()方法),才可以执行被唤醒的线程。
void signalAll():唤醒在此Lock对象上等待的所有线程。只有当前线程放弃对该Lock对象的锁定后,才可以执行被唤醒的线程。
        Lock接口常用的方法:
void lock():获取锁,注意该方法会抛出InterruptedException异常,程序中必须要声明抛出或者捕捉。
void unlock():释放锁,注意该方法是释放资源操作,要必须执行,所以通过将该方法放在finally块中。
    下面将使用JDK1.5中的Lock接口实现一个生产者与消费者的问题:
/*生产者消费者问题: 
描述生产者与消费者的关系,即生产者没生产一件商品,
消费就消费一件商品,不允许生产者多次生产商品,消费者同时消费多件商品
*/
import java.util.concurrent.locks.*;

class Resource {
private String name;
private int count = 1;
private boolean flag = false;
// 实例化一个Lock对象,来操作多线程
private final Lock lock = new ReentrantLock();
// 创建一个Condition对象来替代生产者的同步监视器
private final Condition condition_pro = lock.newCondition();
// 创建一个Condition对象来替代生产者的同步监视器
private final Condition condition_cus = 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_cus.signal(); // 唤醒所有的消费者线程
} finally {
lock.unlock(); // 生产者线程释放锁
}
}
public void out() throws InterruptedException {
lock.lock(); // 消费者线程加锁
try {
while (!flag)
condition_cus.await();
System.out.println(Thread.currentThread().getName()
+ ".....消费者....." + this.name);
flag = false;
condition_pro.signal(); // 唤醒所有生产者的线程
} finally {
lock.unlock(); // 消费者线程释放锁
}

}
}
class Producer implements Runnable {
private Resource r;
Producer(Resource r) {
this.r = r;
}
public void run() {
while (true) {
// lock()方法抛出异常,在这里要捕捉
try {r.set("商品");} catch (InterruptedException e) {}
}
}
}
class Customer implements Runnable {
private Resource r;
Customer(Resource r) {
this.r = r;
}
public void run() {
while (true) {
try {r.out();} catch (InterruptedException e) {}
}
}
}
class ProducerCustomerDemo {
public static void main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Customer cus = new Customer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(cus);
Thread t4 = new Thread(cus);
t1.start();
t2.start();
t3.start();
t4.start();
}
}


运行结果可以看出,符合要求,很和谐:
Java基础(九)——多线程
7,控制线程
7.1,线程死亡
线程会以一下三种方式之一结束,结束后就处于死亡状态:
(1)run()方法执行完成,线程正常结束。
(2)线程抛出一个未捕获的Excption或Error。
(3)直接调用该线程的stop()方法来结束线程,该方法容易导致死锁,通常不推荐使用。
注意:当主线程结束时候,其它线程不受任何影响,并不会随之结束。一旦子线程启动起来后,它就拥有和主线程相同的地位,它不会受主线程的影响。

7.2,停止线程
         通过查阅jdk1.6API文档,我发现停止当前线程有两个方法,stop()方法和suspend()方法;但是发现者两个方法存在弊端,现已过时,原因是,stop()方法具有不安全性。suspend()方法具有死锁倾向,也是不安全的。那么该怎样停止一个正在运行的线程呢?
(1)开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。
(2)特殊情况:当线程处于了冻结状态;就不会读取到标记,那么线程就不会结束,当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除,强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。
        Thread类提供了该方法,interrupt()方法来终端线程。如果线程在调用Object类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。
请看如下的示例程序:

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();
new Thread(st).start();
new Thread(st).start();
int num = 0;
while (true) {
if (num++ == 60) {
/*
* 此处若不调用t1、t2的interrupt()方法,那么t1、t2会一直等待,也就是
* 两者都被冻结,程序卡死,线程控制的内容没有得到执行,不能自行结束运行。
* 加上interrupt()方法强制线程恢复运行状态,程序可以结束,但抛出异常。
*/
// t1.interrupt();
// t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName() + "......."+ num);
}
System.out.println("over");
}
}


线程没有调用interrupt()方法时的运行结果:

Java基础(九)——多线程

线程已经调用interrupt()方法时的运行结果:
Java基础(九)——多线程
7.3,守护线程
public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程或后台线程。当正在运行的线程都是守护线程时,
Java 虚拟机退出。 该方法必须在启动线程前调用。JVM的垃圾回收线程就是典型的守护线程。
    后台线程有个特征:如果所有的前台线程都死亡,后台线程会自动死亡。
    调用Thread对象的setDaemon(true)方法可以将指定线程设置成守护线程。

7.4,join线程
    Thread类提供了让一个线程等待另一个线程完成的方法:join()方法。当在某个线程执行流中调用其他线程的join()方法时,
调用者线程将被阻塞,直到被join方法加入的join线程执行完成为止。
原型:public final void join() throws InterruptedException
作用:(1)join()可以用来临时加入线程执行。
     (2)join()方法通常由使用线程的程序调用(如main线程),以将大问题划分成许多小问题,每个小问题分配一个线程。当所有小问题都得到处理后,再调用主线程来进一步操作。

7.5,线程睡眠:sleep
    如果我们需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread类的静态sleep方法,sleep方法有两种重载形式:
    static void sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
    static void sleep(long millis, int nanos):在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
    当当前线程调用sleep方法进入阻塞状态后,在其sleep时间段内,该线程不会获得执行的机会,即使系统中没有其他可运行的线程,处于sleep中的线程也不会运行,因此sleep方法常用来暂停程序的执行。
    注意:Thread类的sleep()方法调用时会抛出InterruptedException,程序中需捕捉或者抛出。

7.6,线程让步:yield
    原型:public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
    yield()方法和sleep()方法有点相似的方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将线程转入就绪状态。yield只是让当前
线程暂停一下,让系统的线程调度器重新调度一次,可能的情况是:当线程调用了yield方法暂停后,线程调度器又将其调度出来重新执行。
    实际上,当某个线程调用了yield方法暂停之后,只有优先级与当前线程相同或者更高的就绪状态的线程才可以获得执行机会。
    总结:sleep方法和yield方法的区别:
(1)sleep方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级。但yield方法只会给优先级相同或者优先级更高的线程执行机会。
(2)sleep方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态。而yield不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。因此完全有可能某个线程调用yield方法暂停之后,立即再次获得执行机会。
(3)sleep方法声明抛出了InterruptedException异常,所以调用sleep方法时要么捕捉该异常,要么显式声明抛出该异常。而yield方法则没有声明抛出任何异常。
(4)sleep方法比yield方法有更好的可移植性,通常不要依靠yield来控制并发线程的执行。

7.7,线程优先级
      每个线程执行时都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的线程则获得较少的执行机会。
      每个线程默认的优先级都与创建它的父线程具有相同的优先级,在默认情况下,main线程具有普通优先级,由main线程创建的子线程也具有普通优先级。
      关于线程优先级的方法:
      public final void setPriority(int newPriority):设置线程的优先级。
      public final int getPriority():返回指定线程的优先级。
      关于线程优先级的常量:
     static int MAX_PRIORITY:线程可以具有的最高优先级,其值是10。 
     static int MIN_PRIORITY:线程可以具有的最低优先级,其值是1。 
     static int NORM_PRIORITY:分配给线程的默认优先级,其值是5。 

总结:
        本篇介绍的是Java中多线程机制,Java语言提供了非常优秀的多线程支持。
       1、理解进程和线程的概念,首先进程是指正在运行的程序。线程是正在运行程序的一个独立控制单元。一个进程包含至少一条或者多条线程。
       2、掌握了Java中多线程的创建方式。有两种:一种是继承Thread类来实现多线程,另一种是实现Runnable接口的方式实现多线程。
       3、了解两种创建线程方式的区别:继承Thread类的方式编写简单,但是继承了Thread类之后就不能继承其它类了,因为Java只支持单继承。实现Runnable接口则可以继承于其它类,多个线程可以共享同一个target,适合操作多线程控制同一份资源的情况,但是编写稍微繁琐。
       4、多线程操作时,容易引发一些安全问题,为了解决这些安全问题,Java提供了同步的概念,关键字是synchronized,同步分为同步代码块和同步方法。值得注意的是,同步要求操作的线程必须持有同步监视器,若多线程间持有的同步监视器不同,则不能解决线程安全问题。对于同步代码块需要显示的指定同步监视器,该监视器可以是对象,也可以是当前所在类的字节码文件。同步方法不需要显示的指定同步监视器,一般同步方法的同步监视器是this,也就是对象本身。而静态同步方法的同步监视器不可能是this,因为它是先于对象而存在的,它的同步监视器也是字节码文件。
      5、单例设计模式:以前学过的单例设计模式——懒汉式,是线程不安全的。现在,我们得让其变成线程安全的。同样可以使用同步代码块和同步方法解决。但是注意,同步代码块必须加双重否则才能保证它安全,否则它依旧是线程不安全的代码。
      6、死锁。我们得知道线程发生死锁的原因,才能避免死锁。原因:当两个线程互相等待对方释放同步监视器时会发生死锁,即同步嵌套。 理解了死锁发生的原因,我们就可以在开发中尽量避免死锁的发生。而且,我们可以自己写一个死锁程序,为了应付面试。
      7、掌握线程间的通信。一般发生在多线程操作同一资源的时候,可以引发一些安全问题。所以我们需要采取相应的措施保证线程安全。第一种是采取Object类给我们提供的等待唤醒机制,理解wait(),notify()和notifyAll()方法,并且灵活使用。第二种方式是JDK1.5之后给我们提供的方法——同步锁。同步锁处理线程间的通信是非常方便的,而且效率也高。所以必须要掌握好。更需要了解等待唤醒机制和同步锁的区别和优缺点。
      8、最后,我们得掌握Thread类给我提供的多个方法,这些方法在以后应用中很常见。例如,join()方法,sleep()方法,yield()方法,setDaemon()方法设置守护线程。 还有设置或获取线程的优先级,setPriotity(int)和getPriotity()。
    
      Java多线程机制是一个不太好理解和掌握的知识,但是要通过勤奋的练习,通过多敲代码,才能掌握好多线程。关于这篇内容的重要性呢,在我看来是很重要的。
对于JavaEE开发人员来说,这些或轻或重,不是很实用,作为了解就行了,甚至不了解也可以开发JavaEE程序。但是对于GUI和Android开发来说,多线程的理解就显得
格外重要了,作为Android或者即将走向Android的开发人员,必须掌握好多线程机制,明白原理,可以敲代码实现。关于Java多线程机制,远远不止这些,后来我又通过

网上查找相关资料和一些书籍,自学了一些内容,由于篇幅安排的问题。我准备单独写一篇笔记记录那些这次没总结到的知识。