1. 线程的概念
1.1多进程与多线程
进程:一个正在执行的程序.每个进程执行都有一个执行顺序,该顺序是一个执行路径,或叫一个控制单元. 一个进程至少有一个线程.
线程:就是进程中的一个独立的控制单元. 线程控制这进程的执行.
多进程的缺点:进程切换开销大;进程间的通信很不方便。
多线程: 指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务,线程切换的开销小 。
1.2线程的状态
Java语言使用Thread类及其子类的对象来表示线程,新建的线程在它的一个完整的生命周期通常要经历5种状态.
冻结状态:在sleep和wait时, 既没有运行资格,有没有执行权
阻塞状态: 具备运行资格, 没有执行权
1.3线程调度与优先级(多线程的特性:随机性)
Java采用抢占式调度策略,下面几种情况下,当前线程会放弃CPU:
(1)当前时间片用完;
(2)线程在执行时调用了yield() 或sleep() 方法主动放弃;
(3)进行I/O 访问,等待用户输入,导致线程阻塞;或者为等候一个条件变量,线程调用wait()方法;
(4)有高优先级的线程参与调度。
线程的优先级用数字来表示,范围从1~10。主线程的默认优先级为5
Thread.MIN_PRIORITY=1
Thread.NORM_PRIORITY=5
Thread.MAX_PRIORITY=10
2. 多线程编程方法
2.1 Thread类简介
Thread类综合了Java程序中一个线程需要拥有的属性和方法,其构造方法如下:
public Thread (ThreadGroup group,Runnable target,String name);
public Thread();
public Thread(Runnable target);
public Thread(Runnable target,String name);
public Thread(String name);
public Thread(ThreadGroup group,Runnable target);
public Thread(ThreadGroup group,String name);
Thread类的主要方法以及功能如表
2.2 继承Thread类实现多线程
需要重写run方法实现线程的任务.需要注意的是,程序中不要直接调用此方法,而是调用线程对象的start()方法启动线程,让其进入可调度状态,线程获得调度自动执行run()方法.
2.3 实现Runnable接口编写多线程
通过 Thread 类的构造函数public Thread(Runnable target)可以将一个Runnable 接口对象传递给线程,线程在调度时将自动调用Runnable 接口对象的run方法。
实现方式和继承方法的区别:
实现方式的好处:避免了单继承的局限性.在定义线程时,建议使用实现方式.
3. 线程资源的同步处理
3.1 临界资源问题
Java对于多线程的安全问题提供了专业的解决方式: 就是同步代码块
synchronized(对象) {
需要被同步的代码
}
对象如同锁,持有锁的线程可以在同步中执行.
没有持有锁的线程及时获取cpu的执行权,也进不去,因为没有获取锁.
同步的前提:
1.必须要有两个或者以上的线程
2.必须要多个线程使用同一个锁
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,较为消耗资源.
同步函数使用的是哪个锁:
函数需要被对象调用,那么函数都有一个所属对象的引用.就是this.
所以同步函数使用的锁是this锁.静态函数使用的是该方法所在类的字节码文件对象.
懒汉式增强
1 class Single{
2 private Single(){}
3 private static Single s = null;
4 public static Single getInstance(){
5 if(s == null){
6 synchronized(Single.class){
7 if(s == null)
8 s = new Single();
9 }
10 }
11 return s;
12 }
13 }
3.2 wait()和notify()
3.3 避免死锁
多个线程相互等待对方释放持有的锁,并且在得到对方锁之前不释放自己的锁.(自己能写一个死锁代码)
1 /* 2 * 死锁: 同步中嵌套同步容易发生 3 */ 4 5 public class LockDemo { 6 7 public static void main(String[] args) { 8 Test t = new Test(false); 9 Test t2 = new Test(true); 10 new Thread(t).start(); 11 new Thread(t2).start(); 12 13 } 14 15 } 16 17 class Test implements Runnable{ 18 private boolean flag; 19 Test(boolean flag){ 20 this.flag = flag; 21 } 22 public void run(){ 23 if(flag){ 24 try{Thread.sleep(30);}catch(Exception e){} 25 synchronized(KK.oa){ 26 System.out.println("if a"); 27 synchronized(KK.ob){ 28 System.out.println("if b"); 29 } 30 } 31 } 32 else{ 33 synchronized(KK.ob){ 34 System.out.println("else b"); 35 synchronized(KK.oa){ 36 System.out.println("else a"); 37 } 38 } 39 } 40 } 41 } 42 43 //自定义两个锁 44 class KK{ 45 static Object oa = new Object(); 46 static Object ob = new Object(); 47 }
4. 线程间通信
思考1: wait(),notify(),nofifyAll()用来操作线程为什么定义在Object类中?
1.这些方法存在同步中,用来操作同步中的线程,必须要标志它们所操作线程的只有锁.
2.使用这些方法必须标识出所属同步的锁,只有同一个锁上被等待线程,可以被同一个锁上notify唤醒.不可以对不同锁中的线程进行唤醒.
3.锁可以是任意对象,所以任意对象调用的方法一定定义Object类中.
思考2:wait(),sleep()有什么区别?
wait():释放资源,释放锁
sleep():释放资源,不释放锁
1 //线程通信: 取mike, 取丽丽 2 public class Demo2 { 3 4 public static void main(String[] args) { 5 Person p = new Person(); 6 new Thread(new Input(p)).start(); 7 new Thread(new Output(p)).start(); 8 } 9 10 } 11 12 class Person{ 13 private String name ; 14 private String sex ; 15 private boolean flag = false; 16 17 public synchronized void set(String name, String sex){ 18 //flag为true,表示已存在 19 if(flag){ 20 try {wait();}catch (InterruptedException e) {e.printStackTrace();} 21 } 22 //flag为false,表示没人可加 23 this.name = name; 24 this.sex = sex; 25 flag = true; 26 this.notify(); 27 } 28 29 public synchronized void out(){ 30 //flag为false,表示无人没法取 31 if(!flag){ 32 try {wait();}catch (InterruptedException e) {e.printStackTrace();} 33 } 34 //flag为true,表示有人可以取出 35 System.out.println(toString()); 36 flag = false; 37 this.notify(); 38 } 39 40 public String toString(){ 41 return "姓名:"+name+"性别:"+sex; 42 } 43 } 44 45 class Input implements Runnable{ 46 private Person p; 47 48 Input(Person p){ 49 this.p = p; 50 } 51 52 public void run(){ 53 boolean flag = false; 54 while(true){ 55 if(flag){ 56 p.set("likai","男"); 57 flag = false; 58 } 59 else{ 60 p.set("tangll","女"); 61 flag = true; 62 } 63 } 64 } 65 } 66 67 class Output implements Runnable{ 68 private Person p; 69 70 Output(Person p){ 71 this.p = p; 72 } 73 74 public void run(){ 75 while(true){ 76 p.out(); 77 } 78 } 79 }
1 public class Demo3 { 2 3 public static void main(String[] args) { 4 Bridge b = new Bridge(); 5 for(int i=1;i<6;i++){ 6 new Thread(new Person(b,"由北向南第"+i+"人")).start(); 7 } 8 9 for(int i=1;i<7;i++){ 10 new Thread(new Person(b,"由南向北第"+i+"人")).start(); 11 } 12 } 13 } 14 15 class Bridge{ 16 private String name; 17 private boolean flag; 18 public synchronized void UpBridge(){ 19 if(flag){ 20 try { 21 wait(); 22 } catch (InterruptedException e) { 23 e.printStackTrace(); 24 } 25 } 26 flag = true; 27 28 } 29 public synchronized void DownBridge(){ 30 flag = false; 31 notifyAll(); 32 } 33 } 34 35 class Person implements Runnable{ 36 private Bridge bridge; 37 private String pName; 38 39 Person(Bridge bridge, String pName){ 40 this.bridge = bridge; 41 this.pName = pName; 42 } 43 44 public void run() { 45 bridge.UpBridge(); 46 try{ 47 Thread.sleep(80); 48 }catch(InterruptedException e){} 49 bridge.DownBridge(); 50 System.out.println(pName); 51 } 52 53 }
生产消费者问题
生产消费者问题--JDK1.5提供的多线程升级解决方法
将同步synchronized替换成Lock操作.
将Object中的wait,notify,notifyAll方法替换成Condition对象.
该对象可以Lock锁进行获取.
该实例中,实现了本方只唤醒对方的操作.
停止线程
1.定义循环结束标记
因为线程运行代码一般都是循环,只是控制了循环即可.
2.使用interrupt(中断)方法: 该方法是强制结束线程的冻结状态,使线程回到运行状态中来.这样就可以操作标记结束.
(注)stop方法和suspend方法已过时不在再使用.
setDeamon()方法
标记为后台线程, 当所有前台前程结束后会Java虚拟机退出. 该方法必须在启动线程前调用.
join()方法
等待该线程终止, 其实是抢夺CPU执行权.
当A线程执行到B线程的.join()方法, A就会等待. 等B线程都执行完, A才会执行. join可以用来临时加入线程执行.
toString()
返回该线程的字符串表示形式,包括线程名称,优先级和线程组
yield()
暂停当前执行的线程对象,并执行其他线程