1.程序、进程、线程的概念
1.1 程序(program)是为完成特定任务、用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。
1.2 进程(process) 是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。如:运行中的QQ,运行中的MP3播放器,程序是静态的,进程是动态的。
1.3 线程进程可进一步细化为线程,是一个程序内部的一条执行路径。若一个程序可同一时间执行多个线程,就是支持多线程的。
2.何时需要多线程?
2.1 程序需要同时执行两个或多个任务。
2.2 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
3.3 需要一些后台运行的程序时。
3.如何实现一个多线程程序?
3.1 继承Thread类的写法
1 package com.usagi.thread; 2 /** 3 * 案例:创建一个子线程,完成1-100的输出,主线程执行同样的操作 4 */ 5 public class TestThread { 6 public static void main(String[] args) { 7 SubThread subThread = new SubThread(); 8 subThread.start(); 9 System.out.println("main 已经执行完成~"); 10 } 11 } 12 // 1.创建一个继承于Thread的子类 13 class SubThread extends Thread{ 14 // 2.重写Thread的run方法,方法内实现此子线程要完成的功能 15 @Override 16 public void run() { 17 for (int i = 0; i < 100; i++) { 18 System.out.println(i); 19 } 20 System.out.println("子类执行完成~"); 21 } 22 }
PS:一个线程只能执行一次 start() 方法,不能通过thread实现类对象的run方法去启动。
3.2 Thread类的常用方法
1.start():启动线程并执行相应的run()方法
2.run():子线程要执行的代码放入run()方法中
3.currentThread():静态的,调取当前的线程
4.getName():获取此线程的名字
5.setName():设置此线程的名字
6.yield():调用此方法的线程释放当前cpu的执行权
7.join():在a线程中调用b线程的join方法,表示:当执行到此方法,a线程停止执行,直至b线程执行完毕,a线程再接着join之后的代码执行
8.isAlive():判断一个线程是否继续存活
9.sleep(Long L):显式的让当前线程睡眠L毫秒
10.线程通信:
wait() :令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问
notify() : 唤醒正在排队等待同步资源的线程中优先级最高者结束等待
notifyAll() : 唤醒正在排队等待资源的所有线程结束等待.
11.设置线程的优先级: getPriority():返回线程优先值,setPriority():改变线程的优先级
3.3 实现一个多窗口售票案例
1 package com.usagi.thread; 2 class Window extends Thread{ 3 private static Integer ticket = 100; 4 @Override 5 public void run() { 6 while(true){ 7 if(ticket > 0){ 8 System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket -- ); 9 }else{ 10 break; 11 } 12 } 13 } 14 } 15 public class TestWindow { 16 public static void main(String[] args) { 17 Window window1 = new Window(); 18 Window window2 = new Window(); 19 Window window3 = new Window(); 20 window1.setName("窗口1"); 21 window2.setName("窗口2"); 22 window3.setName("窗口3"); 23 window1.start(); 24 window2.start(); 25 window3.start(); 26 } 27 }
3.4 使用实现接口的方式实现多线程
1 package com.usagi.thread; 2 /** 3 * 案例:创建一个子线程,完成1-100的输出,主线程执行同样的操作 4 */ 5 public class TestRunnable { 6 public static void main(String[] args) { 7 SubRunnable subRunnable = new SubRunnable(); 8 Thread thread = new Thread(subRunnable); 9 thread.start(); 10 System.out.println("main 执行完成"); 11 } 12 } 13 /** 14 * 实现Runnable 接口,重写 run 方法 15 */ 16 class SubRunnable implements Runnable{ 17 public void run() { 18 for (int i = 0; i < 100; i++) { 19 System.out.println(i); 20 } 21 System.out.println("子类执行完成~"); 22 } 23 }
3.5 实现的方法实现多窗口案例
1 package com.usagi.thread; 2 public class TestRunnableWindow { 3 public static void main(String[] args) { 4 RunnableWindow window = new RunnableWindow(); 5 Thread thread1 = new Thread(window); 6 Thread thread2 = new Thread(window); 7 Thread thread3 = new Thread(window); 8 thread1.setName("窗口1"); 9 thread2.setName("窗口2"); 10 thread3.setName("窗口3"); 11 thread1.start(); 12 thread2.start(); 13 thread3.start(); 14 } 15 } 16 class RunnableWindow implements Runnable{ 17 Integer ticket = 100; 18 public void run() { 19 while(true){ 20 if(ticket > 0){ 21 System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket -- ); 22 }else{ 23 break; 24 } 25 } 26 } 27 }
3.6 上面的输出1 - 100 会存在线程安全问题,如何暴露?
1 class RunnableWindow implements Runnable{ 2 Integer ticket = 100; 3 public void run() { 4 while(true){ 5 if(ticket > 0){ 6 try { 7 Thread.currentThread().sleep(10); 8 } catch (InterruptedException e) { 9 e.printStackTrace(); 10 } 11 System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket -- ); 12 }else{ 13 break; 14 } 15 } 16 } 17 }
PS:此程序存在线程安全问题:打印车票时,会出现重票、错票。
3.7 线程安全问题存在的原因:由于一个线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据而出现了安全问题。
3.8 如何来解决线程的安全问题:必须让一个线程操作共享数据完毕以后,其他线程才有机会参与共享数据的操作。
4.Java如何实现线程安全
4.1 同步代码块
synchronized(同步监视器){
// 需要被同步的代码块(即为操作共享数据的代码)
}
4.2 共享数据:多个线程共同操作的同一个数据(变量)。
4.3 同步监视器:由一个类的对象来充当。哪个线程获取此监视器,谁就执行大括号里被同步的代码。锁的机制,要求共用一把锁(可以考虑用this),在实现的方式中,考虑同步的话,可以使用this来充当锁,但是在继承的方式中,慎用this。
1 class RunnableWindow implements Runnable{ 2 Integer ticket = 100; 3 Object object = new Object(); 4 public void run() { 5 while(true){ 6 //synchronized (object) { 7 synchronized (this) { 8 if (ticket > 0) { 9 try { 10 Thread.currentThread().sleep(10); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--); 15 } else { 16 break; 17 } 18 } 19 } 20 } 21 }
4.4 同步方法:将操作共享数据的方法声明为synchronized,即此方法为同步方法,能够保证其中一个线程执行此方法时,其他线程在外等待直至此线程执行完此方法。同步方法的锁:this
1 class RunnableWindow implements Runnable{ 2 Integer ticket = 100; 3 public void run() { 4 while (true){ 5 show(); 6 } 7 } 8 public synchronized void show(){ 9 if (ticket > 0) { 10 try { 11 Thread.currentThread().sleep(10); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--); 16 } 17 } 18 }
5.互斥锁
5.1 在Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
5.2 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
5.3 关键字synchronized 来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
5.4 同步的局限性:导致程序的执行效率要降低
5.5 同步方法(非静态的)的锁为this。
5.6 同步方法(静态的)的锁为当前类本身。
6.死锁问题
6.1 死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
6.2 解决方法:专门的算法、原则;尽量减少同步资源的定义。