基本概念
进程: 进程就是正在运行的应用程序。 进程了负责了内存空间划分。
线程: 一个进程中的 代码是由线程去执行的,线程也就是进程中一个执行路径。
多线程: 一个进程中有多个线程可以同时执行任务。
注意:任何一个java程序中至少存在两个线程:main线程和垃圾回收线程。
多线程的好处于弊端
多线程 的好处:
1. 解决一个进程中可以同时执行多个任务的问题。
2. 提高了资源利用率。
多线程的弊端:
1. 增加了cpu的负担。
2. 降低了一个进程中线程 的执行概率。
3. 出现了线程 安全问题。
4. 会引发死锁现象。
线程的声明周期
创建线程的方式
方式一 : 继承Thread类
1. 自定义一个类继承Thread类。
2. 重写Thread类的run方法,把自定义线程的任务代码写在run方法上。
3. 创建Thread的子类对象,并且调用start方法启动一个线程。
注意:千万不要直接调用run方法,调用start方法的时候线程就会开启,线程一旦开启就会执行run方法中代码,如果直接调用run方法,那么就 相当于调用了一个普通的方法而已。
代码示例:
1 //模拟QQ边聊天边视频的场景 2 class TalkThread extends Thread{ 3 @Override 4 public void run() { 5 while (true) { 6 System.out.println("正在聊天!!!!"); 7 } 8 } 9 } 10 11 class VideoThread extends Thread{ 12 @Override 13 public void run() { 14 while (true) { 15 System.out.println("正在视频!!!!"); 16 } 17 } 18 } 19 20 public class TestThread { 21 public static void main(String[] args) { 22 TalkThread talkThread = new TalkThread(); 23 talkThread.start(); 24 VideoThread videoThread = new VideoThread(); 25 videoThread.start(); 26 }
方式二:实现Runnable接口
1. 自定义一个类实现Runnable接口。
2. 实现Runnable接口 的run方法,把自定义线程的任务定义在run方法上。
3. 创建Runnable实现类对象。
4. 创建Thread类的对象,并且把Runnable实现类的对象作为实参传递。
5. 调用Thread对象的start方法开启一个线程。
注意:Runnable实现类的对象并 不是一个线程对象,只不过是实现了Runnable接口 的对象而已。只有是Thread或者是Thread的子类才是线程 对象。
1 //模拟QQ边聊天边视频的场景 2 class Talk implements Runnable{ 3 public void run() { 4 while (true) { 5 System.out.println("正在聊天!!!!"); 6 } 7 } 8 } 9 10 class Video implements Runnable{ 11 public void run() { 12 while (true) { 13 System.out.println("正在视频!!!!"); 14 } 15 } 16 } 17 18 public class TestThread { 19 public static void main(String[] args) { 20 Talk talk = new Talk(); 21 Thread talkThread = new Thread(talk); 22 talkThread.start(); 23 Video video = new Video(); 24 Thread videoThread = new Thread(video); 25 videoThread.start(); 26 } 27 }
常用方法
Thread(String name) 初始化线程的名字
setName(String name) 设置线程对象名
getName() 返回线程的名字
sleep() 线程睡眠指定的毫秒数。 静态的方法, 那个线程执行了sleep方法代码那么就是那个线程睡眠。
currentThread() 返回当前的线程对象,该方法是一个静态的方法, 注意: 哪个线程执行了currentThread()代码就返回那个线程的对象。 getPriority() 返回当前线程对象的优先级 默认线程的优先级是5
setPriority(int newPriority) 设置线程的优先级 虽然设置了线程的优先级,但是具体的实现取决于底层的操作系统的实现(最大的优先级是10 ,最小的1 , 默认是5)。
安全问题
出现线程安全问题的根本原因:
1. 存在两个或者两个以上 的线程对象,而且线程之间共享着一个资源。
2. 有多个语句操作了共享资源。
线程安全问题的解决方案:sun提供了线程同步机制让我们解决这类问题的。
方式一:同步代码块
同步代码块的格式:
synchronized(锁对象){
需要被同步的代码...
}
同步代码块要注意事项:
1. 任意的一个对象都可以做为锁对象。
2. 在同步代码块中调用了sleep方法并不是释放锁对象的。
3. 只有真正存在线程安全问题的时候才使用同步代码块,否则会降低效率的。
4. 多线程操作的锁对象必须是唯一共享的。否则无效。
代码示例如下:
1 //模拟3个窗口同时在售50张 票 。 2 class SellTacketThread extends Thread{ 3 static int num = 50; 4 public SellTacketThread(String name) { 5 super(name); 6 } 7 @Override 8 public void run() { 9 while (true) { 10 synchronized ("同步锁") { 11 if (num>0) { 12 System.out.println(Thread.currentThread().getName()+"售出了弟"+num+"张票"); 13 num--; 14 }else{ 15 System.out.println("售罄了.."); 16 break; 17 } 18 } 19 } 20 } 21 } 22 23 public class TestThread { 24 public static void main(String[] args) { 25 SellTacketThread seller1 = new SellTacketThread("窗口1"); 26 SellTacketThread seller2 = new SellTacketThread("窗口2"); 27 SellTacketThread seller3 = new SellTacketThread("窗口3"); 28 seller1.start(); 29 seller2.start(); 30 seller3.start(); 31 } 32 }
方式二:同步函数
所谓同步函数就是使用synchronized修饰一个函数。
同步函数要注意的事项 :
1. 如果是一个非静态的同步函数的锁对象是this对象,如果是静态的同步函数的锁 对象是当前函数所属的类的字节码文件(class对象)。
2. 同步函数的锁对象是固定的,不能由随意指定的。
代码示例:
1 //模拟3个窗口同时在售50张 票 。 2 class SellTacketThread extends Thread{ 3 static int num = 50; 4 public SellTacketThread(String name) { 5 super(name); 6 } 7 @Override 8 public synchronized void run() { 9 sellTacket(); 10 } 11 public synchronized void sellTacket(){ 12 while (true) { 13 synchronized ("同步锁") { 14 if (num>0) { 15 System.out.println(Thread.currentThread().getName()+"售出了弟"+num+"张票"); 16 num--; 17 }else{ 18 System.out.println("售罄了.."); 19 break; 20 } 21 } 22 } 23 } 24 } 25 26 public class TestThread { 27 public static void main(String[] args) { 28 SellTacketThread seller1 = new SellTacketThread("窗口1"); 29 SellTacketThread seller2 = new SellTacketThread("窗口2"); 30 SellTacketThread seller3 = new SellTacketThread("窗口3"); 31 seller1.start(); 32 seller2.start(); 33 seller3.start(); 34 } 35 }
推荐使用: 同步代码块。
原因:
1. 同步代码块的锁对象可以由我们随意指定,方便控制。同步函数的锁对象是固定 的,不能由我们来指定。
2. 同步代码块可以很方便控制需要被同步代码的范围,同步函数必须是整个函数的所有代码都被同步了。
死锁问题
java中同步机制解决了线程安全问题,但是也同时引发死锁现象。
死锁现象出现 的根本原因:
1. 存在两个或者两个以上的线程。
2. 存在两个或者两个以上的共享资源。
死锁现象的解决方案: 没有方案。只能尽量避免发生而已。
代码示例:
1 class DeadLock extends Thread{ 2 3 public DeadLock(String name){ 4 super(name); 5 } 6 7 public void run() { 8 if("张三".equals(Thread.currentThread().getName())){ 9 synchronized ("遥控器") { 10 System.out.println("张三拿到了遥控器,准备 去拿电池!!"); 11 synchronized ("电池") { 12 System.out.println("张三拿到了遥控器与电池了,开着空调爽歪歪的吹着..."); 13 } 14 } 15 }else if("李四".equals(Thread.currentThread().getName())){ 16 synchronized ("电池") { 17 System.out.println("狗娃拿到了电池,准备去拿遥控器!!"); 18 synchronized ("遥控器") { 19 System.out.println("狗娃拿到了遥控器与电池了,开着空调爽歪歪的吹着..."); 20 } 21 } 22 } 23 } 24 } 25 26 public class Demo2 { 27 28 public static void main(String[] args) { 29 DeadLock thread1 = new DeadLock("张三"); 30 DeadLock thread2 = new DeadLock("李四"); 31 //开启线程 32 thread1.start(); 33 thread2.start(); 34 } 35 }
线程通信
线程通讯: 一个线程完成了自己的任务时,要通知另外一个线程去完成另外一个任务.
相关函数:
wait(): 等待 如果线程执行了wait方法,那么该线程会进入等待的状态,等待状态下的线程必须要被其他线程调用notify方法才能唤醒。
notify(): 唤醒 唤醒线程池等待线程其中的一个。
notifyAll() : 唤醒线程池所有等待线程。
wait与notify方法要注意的事项:
1. wait方法与notify方法是属于Object对象 的。
2. wait方法与notify方法必须要在同步代码块或者是同步函数中才能 使用。
3. wait方法与notify方法必需要由锁对象调用。
看一个经典的例子:生产者和消费者
代码如下:
1 //产品类 2 class Product{ 3 String name; //名字 4 double price; //价格 5 boolean flag = false; //产品是否生产完毕的标识,默认情况是没有生产完成。 6 } 7 8 //生产者 9 class Producer extends Thread{ 10 Product p ; //产品 11 public Producer(Product p) { 12 this.p = p ; 13 } 14 @Override 15 public void run() { 16 int i = 0 ; 17 while(true){ 18 synchronized (p) { 19 if(p.flag==false){ 20 if(i%2==0){ 21 p.name = "苹果"; 22 p.price = 6.5; 23 }else{ 24 p.name="香蕉"; 25 p.price = 2.0; 26 } 27 System.out.println("生产者生产出了:"+ p.name+" 价格是:"+ p.price); 28 p.flag = true; 29 i++; 30 p.notify(); //唤醒消费者去消费 31 }else{ 32 //已经生产 完毕,等待消费者先去消费 33 try { 34 p.wait(); //生产者等待 35 } catch (InterruptedException e) { 36 e.printStackTrace(); 37 } 38 } 39 } 40 } 41 } 42 } 43 44 45 //消费者 46 class Customer extends Thread{ 47 Product p; 48 public Customer(Product p) { 49 this.p = p; 50 } 51 52 @Override 53 public void run() { 54 while(true){ 55 synchronized (p) { 56 if(p.flag==true){ //产品已经生产完毕 57 System.out.println("消费者消费了"+p.name+" 价格:"+ p.price); 58 p.flag = false; 59 p.notify(); // 唤醒生产者去生产 60 }else{ 61 //产品还没有生产,应该 等待生产者先生产。 62 try { 63 p.wait(); //消费者也等待了... 64 } catch (InterruptedException e) { 65 e.printStackTrace(); 66 } 67 } 68 } 69 } 70 } 71 } 72 73 public class Demo5 { 74 75 public static void main(String[] args) { 76 Product p = new Product(); //产品 77 //创建生产对象 78 Producer producer = new Producer(p); 79 //创建消费者 80 Customer customer = new Customer(p); 81 //调用start方法开启线程 82 producer.start(); 83 customer.start(); 84 } 85 }
线程的停止
线程的停止:
1. 停止一个线程我们一般都会通过一个变量去控制的。
2. 如果需要停止一个处于等待状态下的线程,那么我们需要通过变量配合notify方法或者interrupt()来使用。
代码如下:
1 public class Demo6 extends Thread { 2 3 boolean flag = true; 4 5 public Demo6(String name){ 6 super(name); 7 } 8 9 10 @Override 11 public synchronized void run() { 12 int i = 0 ; 13 while(flag){ 14 try { 15 this.wait(); //狗娃等待.. 16 17 } catch (InterruptedException e) { 18 System.out.println("接收到了异常了...."); 19 } 20 System.out.println(Thread.currentThread().getName()+":"+i); 21 i++; 22 } 23 } 24 public static void main(String[] args) { 25 Demo6 d = new Demo6("狗娃"); 26 d.setPriority(10); 27 d.start(); 28 for(int i = 0 ; i<100 ; i++){ 29 System.out.println(Thread.currentThread().getName()+":"+i); 30 if(i==80){ 31 d.flag = false; 32 d.interrupt(); //把线程的等待状态强制清除,被清除状态的线程会接收到一个InterruptedException。 33 /*synchronized (d) { 34 d.notify(); 35 }*/ 36 } 37 } 38 } 39 40 }
守护线程(后台线程)
一个线程默认都不是守护线程。需要使用setDaemon(boolean b)来设置。
注意:在一个进程中如果只剩下了守护线程,那么守护线程也会死亡。
代码如下:
1 public class Demo7 extends Thread { 2 3 public Demo7(String name){ 4 super(name); 5 } 6 7 @Override 8 public void run() { 9 for(int i = 1 ; i<=100 ; i++){ 10 System.out.println("更新包目前下载"+i+"%"); 11 if(i==100){ 12 System.out.println("更新包下载完毕,准备安装.."); 13 } 14 try { 15 Thread.sleep(100); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 } 20 } 21 22 public static void main(String[] args) { 23 Demo7 d = new Demo7("后台线程"); 24 d.setDaemon(true); //setDaemon() 设置线程是否为守护线程,true为守护线程, false为非守护线程。 25 // System.out.println("是守护线程吗?"+ d.isDaemon()); //判断线程是否为守护线程。 26 d.start(); 27 28 for(int i = 1 ; i<=100 ; i++){ 29 System.out.println(Thread.currentThread().getName()+":"+i); 30 } 31 32 } 33 34 }
join方法
在现有线程的基础上加入新的线程,并且现有线程必须让步于新的线程先完成任务, 才能继续执行。
代码示例如下:
1 //老妈 2 class Mon extends Thread{ 3 4 public void run() { 5 System.out.println("妈妈洗菜"); 6 System.out.println("妈妈切菜"); 7 System.out.println("妈妈准备炒菜,发现没有酱油了.."); 8 //叫儿子去打酱油 9 Son s= new Son(); 10 s.start(); 11 try { 12 s.join(); //加入。 一个线程如果执行join语句,那么就有新的线程加入,执行该语句的线程必须要让步给新加入的线程先完成任务,然后才能继续执行。 13 } catch (InterruptedException e) { 14 e.printStackTrace(); 15 } 16 System.out.println("妈妈继续炒菜"); 17 System.out.println("全家一起吃饭.."); 18 } 19 } 20 21 class Son extends Thread{ 22 23 @Override 24 public void run() { 25 System.out.println("儿子下楼.."); 26 try { 27 Thread.sleep(1000); 28 } catch (InterruptedException e) { 29 e.printStackTrace(); 30 } 31 System.out.println("儿子一直往前走"); 32 System.out.println("儿子打完酱油了"); 33 System.out.println("上楼,把酱油给老妈"); 34 } 35 } 36 37 public class Demo8 { 38 39 public static void main(String[] args) { 40 Mon m = new Mon(); 41 m.start(); 42 } 43 }