Java多线程
一.基本概念
- 程序、进程、线程
- 程序(program)可以理解为静态的代码
- 进程(process)可以理解为执行中的程序
- 线程(thread)可以理解为进程的进一步细分, 程序的一条执行路径
- 何时需要多线程
- 程序需要同时执行两个或多个任务
- 程序需要实现一些等待的任务时, 如: 用户输入、文件读写、网络操作顿、搜索等
- 需要一些后台运行的任务时
二.创建多线程
通过java.lang.Thread类实现
方式一:继承Thread类
- 继承Thread类,并重写run()方法,run()方法内实现此子线程想要实现的功能
- 在主线程内, 创建一个子线程的对象.
- 调用子线程的start()方法, 启动此线程; 调用相应线程的run()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class PrintNum extends Thread{ public void run(){ for(int i = 1;i <= 100;i++){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } public PrintNum(String name){ super(name); } } public class TestThread { public static void main(String[] args) { PrintNum p1 = new PrintNum("线程1"); PrintNum p2 = new PrintNum("线程2"); p1.setPriority(Thread.MAX_PRIORITY); p2.setPriority(Thread.MIN_PRIORITY); p1.start(); p2.start(); } }
|
注意: ①一个线程只能start()一次; ②不能用run()启动线程
Thread类的常用方法:
One More Thing 👇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
|
例子: 三个售票窗口售卖100张票(一共100张)
继承Thread类的方式实现:
注意: 此程序存在隐患(线程安全问题)
One More Thing 👇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class Window extends Thread{ static int ticket = 100; public void run() { while(true) { if(ticket>0) {
System.out.println(Thread.currentThread().getName() + "售票, 票号为:"+ticket--); } else break; } } } public class TestWindow { public static void main(String[] args) { Window w1 = new Window(); Window w2 = new Window(); Window w3 = new Window(); w1.setName("窗口一:"); w2.setName("窗口二:"); w3.setName("窗口三:"); w1.start(); w2.start(); w3.start(); } }
|
方式二:实现Runnable接口
- 创建一个实现Runnable接口的类
- 实现接口的抽象run()方法
- 创建一个实现Runnable接口实现类的对象
- 将此对象作为形参传给Thread类的构造器, 创建Thread类的对象, 此对象即为一个线程
- 调用start()启动线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class SubThread implements Runnable{ public void run(){ for(int i = 1;i <= 100;i++){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class TestThread{ public static void main(String[] args){ SubThread s = new SubThread(); Thread t1 = new Thread(s); Thread t2 = new Thread(s); t1.setName("线程1"); t2.setName("线程2"); t1.start(); t2.start(); } }
|
两种方式的对比: 联系:class Thread implements Runnable, 两种方式实际上都与Runnable接口发生了关系
哪个比较好?
- 实现的方式(方式二)较好:
- ①解决了单继承的局限性
- ②如果多个线程有共享数据的话, 建议使用实现方式, 同时, 共享数据所在的类可以作为Runnable接口的实现类.
实现Runnable接口的方式实现(上述售票例题):
注意: 此程序存在隐患(线程安全问题)
One More Thing 👇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class Window1 implements Runnable{ int ticket = 100; public void run(){ while(true) { if(ticket>0) {
System.out.println(Thread.currentThread().getName() + "售票, 票号为:"+ticket--); } else break; } } } public class TestThread{ public static void main(String[] args){ Window1 w = new Window1(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
|
三.Java的多线程
1.多线程的优点
- 提高应用程序的响应
- 提高计算机系统的CPU利用率
- 改善程序接口
2.Java线程的分类
- 守护线程VS用户线程
- 若JVM中都是守护线程, 当前JVM将退出
3.线程的生命周期
新建、就绪、运行、阻塞、死亡
四.线程同步
上述售票的例子:
程序存在线程安全问题: 打印车票时, 会出现重票、错票
- 线程安全问题存在的原因?
由于一个线程在操作共享数据的过程中, 未执行完的情况下, 另外一个线程参与进来, 导致共享数据存在了安全问题.
- 如何解决该问题?
必须让一个线程操作共享数据完毕以后, 其他线程才有机会进入参与共享数据的操作.
- Java如何实现线程的安全: 线程的同步机制
方式一: 同步代码块:
1 2 3
| synchronized(同步监视器){ }
|
①同步监视器: 俗称锁, 任何一个类的对象都可以才充当锁. 要想保证线程的安全, 必须要求所有的线程共用同一把锁!
②共享数据: 多个线程需要共同操作的变量. 明确哪部分是操作共享数据的代码.
③使用实现Runnable接口的方式创建多线程的话, 同步代码块中的锁, 可以考虑是this. 如果使用继承Thread类的方式, 慎用this!
修改原来的售票代码:
One More Thing 👇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class Window1 implements Runnable{ int ticket = 100; Object obj = new Object(); public void run() { while(true) { synchronized (this) { if(ticket>0) { System.out.println(Thread.currentThread().getName() + "售票, 票号为:"+ticket--); } else break; } } } } public class TestThread{ public static void main(String[] args){ Window1 w = new Window1(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
|
方式二: 同步方法:
- 将需要共享的数据的操作放到一个方法里, 并给该方法添加 synchronized 修饰
- 同步方法也有锁, 即为当前对象 this
- 如果使用在继承的方式实现多线程的话, 慎用同步方法!因为它们的this锁不一样
One More Thing 👇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Window implements Runnable{ int ticket = 100; @Override public void run() { while(true) { this.show(); } } public synchronized void show() { if(ticket>0) { System.out.println(Thread.currentThread().getName() + "售票, 票号为:"+ticket--); } } }
|
懒汉式单例模式的线程安全
- 使用线程同步机制解决问题
- 对于一般的方法内, 使用同步代码块的方式, 可以考虑用this当锁
- 对于静态方法而言, 使用当前类本身充当锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Singleton{ private Singleton(){} private static Singleton instance = null; public static Singleton getInstance(){ if(instance == null){ synchronized(Singleton.class){ if(instance == null){ instance = new Singleton(); } } } return instance; } }
|
线程同步的弊端: 由于同一时间只能有一个线程访问共享数据, 效率变低了
何时释放锁?
- 当前线程的同步方法、同步代码块执行结束时
- 当前线程的同步方法、同步代码块中出现了未处理的Error或Exception, 导致异常结束
- 当前线程的同步方法、同步代码块中执行了线程对象的wait()方法, 当前线程暂停并释放锁
线程的死锁:
不同的线程分别占用对方需要的同步资源不放弃, 都在等待对方放弃自己需要的同步资源, 就形成了线程的死锁.
死锁是我们在使用同步时, 需要避免的问题!
线程的通信
涉及到三个方法 wait() notify() notifyAll() 在java.lang.Object类中
- wait(): 令当前线程挂起, 放弃CPU、同步资源…
- notify(): 唤醒正在排队等待同步资源的线程中优先级最高的线程
- notifyAll(): 唤醒正在排队等待同步资源的所有线程
注意: 这三个方法只能在同步方法或同步代码块中使用, 否则会报异常
例, 用两个线程交替打印1-100:
One More Thing 👇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class PrintNum implements Runnable{ int num = 1; @Override public void run() { while(true) { synchronized(this) { notify(); if(num<100) { System.out.println(Thread.currentThread().getName() + ":" + num); num++; }else break; try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } public class TestCommunication { public static void main(String[] args) { PrintNum p = new PrintNum(); Thread t1 = new Thread(p); Thread t2 = new Thread(p); t1.setName("甲"); t2.setName("乙"); t1.start(); t2.start(); } }
|
经典例题: 生产者/消费者问题
One More Thing 👇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
|
class Producer implements Runnable { Clerk clerk; Producer(Clerk clerk) { this.clerk = clerk; } public void run() { System.out.println("生产者开始生产产品"); while (true) { try { Thread.currentThread().sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } clerk.addProduct(); } } } class Consumer implements Runnable { Clerk clerk; Consumer(Clerk clerk) { this.clerk = clerk; } public void run() { System.out.println("消费者开始消费产品"); while (true) { try { Thread.currentThread().sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } clerk.subProduct(); } } } class Clerk { int product; public synchronized void addProduct() { if (product >= 20) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { product++; notifyAll(); System.out.println(Thread.currentThread().getName() + "生产了第" + this.product + "个产品"); } } public synchronized void subProduct() { if (product > 0) { System.out.println(Thread.currentThread().getName() + "消费了第" + this.product + "个产品"); product--; notifyAll(); } else { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Test { public static void main(String[] args) { Clerk clerk = new Clerk(); Producer p1 = new Producer(clerk); Consumer c1 = new Consumer(clerk); Thread t1 = new Thread(c1); Thread t2 = new Thread(p1); Thread t3 = new Thread(p1); t1.setName("消费者1"); t2.setName("生产者1"); t3.setName("生产者2"); t2.start(); t1.start(); t3.start(); } }
|