线程篇


一、概述
    线程的前提是有进程,所以说线程之前的了解进程的概念及其与线程的联系。
进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
线程:是进程中的一个独立的控制单元,线程在控制着进程的执行。一个进程中至少有一个线程,每个独立线程代表一个独立操作。线程隶属于某个进程,它自身没有入口和出口;也不能自动运行,要由进程启动执行,进行控制。
两者的区别:
    1)进程在执行过程中拥有独立的内存单元,而多个线程共享内存。
    2)线程都有自己默认的名称。Thread-编号,该编号从 0 开始。

多线程:一个进程中有多个线程,称为多线程。例如:虚拟机启动的时候就是多线程,JVM 启动时至少有一个主线程和一个负责垃圾回收的线程。
主线程:在 JVM 启动时会有一个进程 Java.exe。该进程中至少一个线程负责 Java程序的执行,而这个线程运行的代码存在于main 方法中,则该线程称为主线程。
多线程的意义:多个程序同时执行,从而提高程序运行效率。
线程的弊端:线程太多会导致效率的降低,因为线程的执行依靠的是 CPU 的来回切换。
多线程原理:
    当运行多线程程序时发现运行结果每次都不同。因为多个线程都在获取CPU的执行权。CPU执行到谁,谁就运行。明确一点,在某一时刻,只能有一个程序在运行。(多核除外)CPU在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象把多线程运行行为看做在互相抢夺CPU的执行权。这就是多线程的一个特性,随机性。谁抢到谁执行,至于执行多长时间,CPU说了算。后期可以控制,但是比较困难。系统可以同时执行多个任务,应用程序内的多任务并行就是依靠多线程实现的。
二、线程创建
创建线程有两种方式:继承Thread类、实现Runnable接口。
1、继承Thread类:
步骤:
    1)定义类继承 Thread。
    2)复写 Thread 中的 run()方法(将线程要运行的代码存放在该 run()方法中) 。
    3)调用线程的 start()方法启动线程,从而调用 run()方法。
Note:
    Thread 类用于描述线程。该类定义了一个功能,用于存储线程要运行的代码。该存储功能就是 run()方法。因此,run()方法的目的就是将自定义的代码存储在 run()方法,让线程运行。在 main 方法中调用 start()方法作用是开启线程并执行线程的 run ()方法。而在 main 方法中直接调用 run ()方法,仅仅是对象调用方法,创建了线程,并没有运行。
2、实现Runnable接口:
步骤:
    1)定义类实现 Runnable 接口。
    2)覆盖 Runnable 接口中的 run()方法(将线程要运行的代码存放在该 run()方法中 )。
    3)通过 Thread 类建立线程对象。
    4)将 Runnable 接口的子类对象作为实际参数传递给 Thread 类的构造函数。
    5)调用 Thread 类的 start 方法开启线程从而调用 Runnable 接口子类的 run()方法。
Note:
    创建(声明)一个实现 Runnable 接口的类对象。类必须定义一个称为 run 的无参方法。此外 Runnable 为 Thread 的子类的类提供了一种激活方式。然后该类实现 run()方法,可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
下面演示两种方式创建线程:

class Demo extends Thread {
    public void run() {
        for (int i = 0; i < 60; i++) {
            System.out.println(Thread.currentThread().getName() + "::Demo run ---");
        }
    }
}

class Demo1 implements Runnable {
    public void run() {
        for (int i = 0; i < 60; i++) {
            System.out.println(Thread.currentThread().getName() + "::Demo1 run ---");
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        Demo d = new Demo();
        d.start();
        
        Demo1 d1 = new Demo1();
        new Thread(d1).start();;

        for (int i = 0; i < 60; i++) {
            System.out.println(Thread.currentThread().getName() + "::Main run ---");
        }
    }
}

结果如下所示:


3、两种创建方式的区别
    继承Thread:线程代码存放在Thread子类run()方法中。
    实现Runnable:线程代码存放在接口子类run()方法中。    
    实现方式的好处:避免了单继承的局限性。定义线程时,建议使用实现方式。
4、线程运行状态
    被创建:等待启动,调用start启动。
    运行状态:具有执行资格和执行权。
    临时状态(阻塞):有执行资格,但是没有执行权。
    冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep()方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
    消忙状态:stop()方法,或者run()方法结束。
图解如下:


三、线程同步机制
1、多线程的安全问题原因:当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完, 另一个线程参与进来执行。导致共享数据的错误。
2、解决办法:对多条操作共享数据的语句,只能让一个线程执行完。在执行过程中,其他线程不可以参与执行———同步。
Note:
    同步的前提:必须要有两个或者两个以上的线程,必须是多个线程使用同一个锁,必须保证同步中只能有一个线程在运行。
    同步优点:解决了多线程的安全问题。
    同步弊端:多个线程需要判断锁,较为消耗资源。
在多线程操作共享数据的运行代码中,需要加锁的两种情况:
    1)含有选择判断语句
    2)含 try(){}catch(){}语句
理解:对象如同锁,持有锁的线程可以在同步中执行。没有持有锁的线程即使获取CPU 执行权,也进不去,因为没有获取锁。Java 对于多线程的安全问题提供了专业的解决方式:同步代码块、同步函数。
3、同步代码块:
格式:
    synchronized(对象)
    {
          //需要被同步的代码
    }
示例:

/*
 * 创建4个线程同时卖100张票
 * 
 */
class Ticket implements Runnable {
	private int num = 100;

	public void run() {
		while (true) {
			//同步代码块,利用this作为锁,或者用Class对象作为锁,只要保证锁唯一即可
			synchronized (this) {
				if (num > 0) {
					try {
						//延时是为了让4个线程执行权趋于均衡
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("Ticket" + num--);
				}
			}
		}
	}
}
public class ThreadDemo3 {
	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		Thread t1 = new Thread(ticket);
		Thread t2 = new Thread(ticket);
		Thread t3 = new Thread(ticket);
		Thread t4 = new Thread(ticket);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

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

/*
 * 创建4个线程同时卖100张票
 * 
 */

class Ticket implements Runnable {
	private int num = 100;

	public synchronized void run() {
		while (true) {
			// 同步代码块,利用this作为锁,或者用Class对象作为锁,只要保证锁唯一即可
			// synchronized (this) {
			if (num > 0) {
				try {
					// 延时是为了让4个线程执行权趋于均衡
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("Ticket" + num--);
			}
			// }
		}
	}
}

public class ThreadDemo3 {
	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		Thread t1 = new Thread(ticket);
		Thread t2 = new Thread(ticket);
		Thread t3 = new Thread(ticket);
		Thread t4 = new Thread(ticket);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}

}
上述两个示例结果都会随着运行而改变,但是基本完成100张车票的售卖,结果如下:


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

class Single {
	private static Single s = null;

	private Single() {
	}

	public static Single getInstance() {
		//解决效率问题
		if (s == null) {
			//加锁
			synchronized (Single.class) {
				//判断s是否有非空指向
				if (s == null) {
					s = new Single();
				}
			}
		}
		return s;
	}
}
6、死锁
多线程程序中,当同步中嵌套同步时,就有可能出现死锁现象。
示例:
class DeadLock implements Runnable {
	private boolean flag;

	DeadLock(boolean flag) {
		this.flag = flag;
	}

	public void run() {
		if (flag) {
			synchronized (MyLock.locka) {
				System.out.println("...if locka");
				synchronized (MyLock.lockb) {
					System.out.println("...else lockb");
				}
			}
		} else {
			synchronized (MyLock.lockb) {
				System.out.println("...else lockb");
				synchronized (MyLock.locka) {
					System.out.println("...else locka");
				}
			}
		}
	}
}
//自定义锁类
class MyLock {
	static MyLock locka = new MyLock();
	static MyLock lockb = new MyLock();
}

public class ThreadDemo7 {

	public static void main(String[] args) {

		DeadLock dlt = new DeadLock(true);
		DeadLock dlf = new DeadLock(false);

		Thread t1 = new Thread(dlt);
		Thread t2 = new Thread(dlf);

		t1.start();
		t2.start();
	}
}
结果如下所示:

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

/*
 * 练习:一个线程存取姓名和性别,另一个线程打印出该两项
 * 
 */

//资源
class Res {
	String name;
	String sex;
	boolean flag;//判断资源是否存在的标识符

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getSex() {
		return sex;
	}

	public void setSex(String sex) {
		this.sex = sex;
	}

	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}

}
//存放线程
class Input implements Runnable {
	Res r = new Res();

	Input(Res r) {
		this.r = r;
	}

	public void run() {
		int x = 0;
		while (true) {
			synchronized (r) {
				//判断有资源就等待
				if (r.flag) {
					try {
						r.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				//交替存入caven和琴琴这两个人的信息
				if (x == 0) {
					r.name = "caven";
					r.sex = "man";
				} else {
					r.name = "琴琴";
					r.sex = "女女女女女";
				}
				x = (x + 1) % 2;
				r.flag = true;
				r.notify();//唤醒其他线程
			}
		}
	}
}
//取出线程
class Output implements Runnable {
	private Res r;

	Output(Res r) {
		this.r = r;
	}

	public void run() {
		while (true) {
			synchronized (r) {
				//判断资源为空就等待
				if (!r.flag) {
					try {
						r.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println(r.name + "...." + r.sex);
				r.flag = false;
				r.notify();
			}
		}
	}
}

public class ThreadDemo1 {

	public static void main(String[] args) {

		Res r = new Res();

		Input in = new Input(r);
		Output out = new Output(r);

		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);

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

}
部分结果如下所示:

五、等待唤醒机制
1、原因:因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁。
2、说明:Object 中的方法 notify()、notifyAll()、wait()等都在同步中使用。为什么这些操作线程的方法要定义 Object 类中呢?
    答:因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,只有同一个锁上的被等待线程,可以被同一个锁上 notify 唤醒。不可以对不同锁中的线程进行唤醒。 等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法定义 Object 类中。
3、wait():在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行wait(0)调用一样。当前线程必须拥有此对象锁(监视器)。该线程发布对此监视器的所有权并等待,直到其他线程通过调用notify()方法,或notifyAll()方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。
Note:
    wait()方法在使用时只能 try(处理异常)不能抛异常。当出现多个线程同时操作一个对象时时,要用 while 循环和 notifyAll();比较通用的方式。定义while判断标记是为了让被唤醒的线程再一次判断标记。定义 notifyAll(), 是因为需要唤醒对方线程。只用 notify(),容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。

六、JDK5.0 升级版线程功能
1、Lock 接口:
    实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构, 可以具有差别很大的属性, 可以支持多个相关的 Condition对象(可以理解为 lock 替代了 synchronized)。
2、ReentrantLock类:
    Lock 的子类。一个可重入的互斥锁,它具有与使用  synchronized  方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
Note:JDK1.5  中提供了多线程升级解决方案:显示的锁机制,以及显示的锁等待唤醒机制。
    1)将同步 Synchronized 替换成现实 Lock 操作。
    2)将 Object 中的 wait,notify notifyAll,替换了 Condition 对象。该对象可以 Lock 锁进行获取。
    3)释放锁的动作一定要执行。
JDK5.0新特型改写后的生产者消费者案例:

/*
 * JDK5.0 升级后的生产者消费者案例演示
 */
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//资源
class Resources {
	private String name;
	private int count = 1;
	private boolean flag = false;

	<span><span class="comment">//创建两Condition对象,分别来控制等待或唤醒本方和对方线程</span><span> </span></span>
	private Lock lock = new ReentrantLock();
	private Condition conSet = lock.newCondition();
	private Condition conOut = lock.newCondition();

	public/* synchronized */void set(String name) {
		lock.lock();//上锁
		try {
			while (flag) {
				try {
					/<span><span class="comment">/本方等待</span><span>  </span></span>
					conSet.await();
					/* wait(); */
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			this.name = name + "---" + count++;
			System.out.println(Thread.currentThread().getName() + "生产者"
					+ this.name);
			flag = true;
			conOut.signal();// <span><span class="comment">//唤醒对方</span><span>  </span></span>
			/* this.notifyAll(); */
		} finally {
			lock.unlock();//解锁动作,一定要执行
		}
	}

	public/* synchronized */void out() {
		lock.lock();
		try {
			while (!flag) {
				try {
					conOut.await();
					/* wait(); */
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "--消费者--"
						+ this.name);
				flag = false;
				conSet.signal();
				/* this.notifyAll(); */
			}
		} finally {
			lock.unlock();
		}
	}
}

// 生产者
class Producers implements Runnable {
	private Resources res;

	public Producers(Resources res) {
		this.res = res;
	}

	public void run() {
		while (true) {
			res.set("商品");
		}
	}
}

// 消费者
class Comsumers implements Runnable {
	private Resources res;

	public Comsumers(Resources res) {
		this.res = res;
	}

	public void run() {
		while (true) {
			res.out();
		}
	}
}

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

		Resources res = new Resources();

		Producers pro = new Producers(res);
		Comsumers con = new Comsumers(res);

		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();
	}
}
部分结果如下所示:

3、停止线程(JDK5.0之后)
原理:只有让run()方法结束。
两种方式:
    1)定义循环结束标记:因为线程运行代码一般都是循环,只要控制了循环,就可以结束 run(),也就线程结束。
    2)使用 interrupt(中断)方法:该方法结束线程的冻结状态,使线程回到运行状态中来。
Note:stop()方法已过时,不再使用。
示例:

class StopThread implements Runnable {
	private boolean flag = true;

	// 改变flag
	public void changeFlag(boolean flag) {
		this.flag = flag;
	}

	public synchronized void run() {
		int i = 0;
		// 利用flag来标记线程是否继续
		// 特殊情况:
		// 当线程处于冻结状态的时候就不会读取到标记,即线程结束不了
		while (flag) {
			try {
				wait();
			} catch (Exception e) {
				System.out.println(Thread.currentThread().getName()
						+ "Exception...");
				changeFlag(false);
			}
			System.out.println(Thread.currentThread().getName() + "...." + i++);
		}
	}
}
public class ThreadDemo4 {

	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 = 0;
		while (true) {
			if (num++ == 60) {
				st.changeFlag(false);
				// 利用interrupt来结束线程
				t1.interrupt();
				t2.interrupt();
				break;
			}
			System.out.println(Thread.currentThread().getName() + "...." + num);
		}
	}
}
结果如下所示:

七、其他方法
1、守护线程
    setDacmon(Boolean on)    //将该线程标记为守护线程或用户线程。
Note:Thread 类该方法必须在启动线程前调用,当正在运行的线程都是守护线程时,java 虚拟机退出。该方法首先调用 checkAccess()方法,且不带任何参数,可能抛出SecurityException(在当前线程中)。主线程为前台线程,守护线程可以理解为后台线程。后台线程特点:开启后,与前台线程共同抢劫 CPU 的执行权运行。当所有的前台线程结束后,后台线程会自动结束。
2、join()方法
1)概述:Thread 类的final修饰的方法,等待线程终止。该方法抛异常 InterruptedException抢夺 CPU 执行权(主线程要等待 join ()执行的线程执行结束,再恢复继续运行)。一般临时加入线程时,使用 join()。
2)特点:当 A 线程执行到了 B 线程的.join()方法时,A 就会等待。等 B 线程都执行完,A才会执行。join 可以用来临时加入线程执行。
3、优先级和 yield()方法
在Thread类覆盖了toString()方法。
    String toString()    //返回该线程的字符串表现形式。 包括线程名称、优先级和线程组。
    static void yield()  //暂停当前正在执行的线程对象,并执行其他线程。减少线程的执行频率。
ThreadGroup 类:线程组。表示一个线程的集合。
优先级:代表抢资源的频率。范围:1——10。只有 1(MIN_PRIORITY)、5(NORM_PRIORITY)、10(MAX_PRIORITY)最明显。所有线程(包括主线程)默认优先级是 5。
    setPriorty(int newPriority)    //更改线程的优先级。
    eg:   t1.setPriority(Thread.MAX_PRIORITY);    //设置 t1 的线程优先级为 10

   本篇幅所描述的仅代表个人看法,如有出入请谅解。

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

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值