title: 【学习之路】多线程学习
draft: true
tags:


什么是多线程

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,而多线程就是指从软件或者硬件上实现多个线程并发执行的技术,具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。

在Java中创建多线程


方法一

  1. 创建一类继承Thread类
  2. 重写Thread类的run()方法
  3. 创建Thread类的子类对象
  4. 通过此对象调用start()方法
  1. public class ThreadTest extends Thread{
  2. @Override
  3. public void run() {
  4. for (int i = 0; i < 20; i++) {
  5. System.out.println("我在学习多线程");
  6. }
  7. }
  8. }
  1. public class TestMain {
  2. public static void main(String[] args) {
  3. // 创建Thread类的子类对象
  4. ThreadTest test = new ThreadTest();
  5. // 通过此对象调用父类的start()方法:start方法会启动当前线程并且调用当前线程的run()方法
  6. test.start();
  7. // 这下面的操作任然是在main方法中执行的
  8. for (int i = 0; i < 20; i++) {
  9. System.out.println("我在学习英语");
  10. }
  11. }
  12. }

Thread - 图1

不能通过调用run()方法开启多线程

不能同时用一个对象调用多次start()方法不然会抛出异常,此时需要new多个对象实列来调用start方法

Thread - 图2

方法二

创建Thread类的匿名子类

  1. public class TestMain {
  2. public static void main(String[] args) {
  3. new Thread(){
  4. @Override
  5. public void run() {
  6. System.out.println("我在学习多线程");
  7. }
  8. }.start();
  9. }
  10. }

方法三

  1. 实现Runnable
  2. 实现Runnable接口中的抽象方法run()
  3. 创建实现类的对象
  4. 将此对象作为参数传入到Thread类的构造器中,创建Thread类的对象
  5. 通过Thread类的对象调用start()
  1. public class ThreadTest implements Runnable{
  2. @Override
  3. public void run() {
  4. for (int i = 0; i < 20; i++) {
  5. System.out.println("我在学习多线程");
  6. }
  7. }
  8. }
  1. public class TestMain {
  2. public static void main(String[] args) {
  3. ThreadTest test = new ThreadTest();
  4. Thread thread = new Thread(test);
  5. thread.start();
  6. }
  7. }

方式四

  1. 实现Callable接口

  2. 重写call()方法

  3. 创建实现类对象

  4. 创建FutureTask的实现类

  5. 创建Thread实列将FutureTask实列传入

  6. 调用Thread的start()

  1. public class ThreadTest2 implements Callable {
  2. @Override
  3. public Object call() throws Exception {
  4. int sum = 0;
  5. for (int i = 1; i <= 100; i++){
  6. if (i % 2 == 0){
  7. System.out.println(i);
  8. sum += i;
  9. }
  10. }
  11. return sum;
  12. }
  13. }
  1. public class TestMain {
  2. public static void main(String[] args) {
  3. ThreadTest2 ts2 = new ThreadTest2();
  4. FutureTask futureTask = new FutureTask(ts2);
  5. new Thread(futureTask).start();
  6. try {
  7. Object sum = futureTask.get();
  8. System.out.println(sum);
  9. }catch (Exception e){
  10. e.printStackTrace();
  11. }
  12. }
  13. }

方式五——JDK5.0新特性

  1. 使用Executors线程池创建线程返回ExecutorService
  2. 写一个类实现Runnable或Callable
  3. 通过返回的ExecutorService调用execute()(适用于Runnable)或submit()(适用于Callable)将实现Runnable或Callable的实列传入到execute() 或 submit()中
  1. public class ThreadPool implements Runnable{
  2. @Override
  3. public void run() {
  4. for (int i = 0; i <= 100; i++){
  5. System.out.println(i);
  6. }
  7. }
  8. }
  1. public class TestMain {
  2. public static void main(String[] args) {
  3. ExecutorService service = Executors.newFixedThreadPool(10);
  4. service.execute(new ThreadPool());
  5. }
  6. }

Thread中的常用方法

  1. void start():启动线程,并执行对象的run()方法
  2. run():线程在被调度时执行的操作
  3. String getName():返回线程的名称
  4. void setName():设置该线程的名称
  5. static Thread currentThread():返回当前线程,在Thread子类中就是this,通常用于主线程和Runnable实现类
  6. yield():释放当前线程执行权
  7. join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,知道线程b执行完成以后,线程a才结束阻塞状态
  8. sleep(long millis):让当前线程睡眠,单位为毫秒值
  9. isAlive():判断当前线程是否存活

线程的优先级

  1. MAX_PRIORITY=10
  2. MIN_PRIORITY=1
  3. NORM_PRIORITY=5

Thread - 图3

  1. getPriority():获取线程的优先级
  2. setPriority(int p):设置线程的优先级

线程优先级高并不代表一定会执行这个线程,只是从概率来说会执行高优先级的线程可能性更大

线程安全问题

  1. 列:创建三个窗口卖票,总票数为100张。
  1. public class ThreadTest implements Runnable{
  2. private int ticket = 100;
  3. @Override
  4. public void run() {
  5. while (true){
  6. if (ticket > 0){
  7. System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
  8. ticket--;
  9. }else {
  10. break;
  11. }
  12. }
  13. }
  14. }
  1. public class TestMain {
  2. public static void main(String[] args) {
  3. ThreadTest test = new ThreadTest();
  4. Thread t1 = new Thread(test);
  5. Thread t2 = new Thread(test);
  6. t1.start();
  7. t2.start();
  8. }
  9. }

Thread - 图4

从这里我们可以看出有可能同时有两个线程会操作同一张票而导致错票问题

解决方式一:同步代码块

使用synchronized的方式来解决线程安全问题

  1. synchronized(同步监视器){
  2. // 需要被同步的代码
  3. }

同步监视器俗称:锁

  • 锁可以是任何一个类的对象,都可以充当锁

  • 多个线程必须要共同用同一把锁

方式一:new一个唯一对象

  1. public class ThreadTest implements Runnable{
  2. private int ticket = 100;
  3. private Object obj = new Object();
  4. @Override
  5. public void run() {
  6. while (true){
  7. synchronized (obj) {
  8. if (ticket > 0) {
  9. System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
  10. ticket--;
  11. } else {
  12. break;
  13. }
  14. }
  15. }
  16. }
  17. }

如果使用这个方式需要考虑是否是唯一对象,如果不是需要加上static

方式二:使用this关键字

  1. public class ThreadTest implements Runnable{
  2. private int ticket = 100;
  3. @Override
  4. public void run() {
  5. while (true){
  6. synchronized (this) {
  7. if (ticket > 0) {
  8. System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
  9. ticket--;
  10. } else {
  11. break;
  12. }
  13. }
  14. }
  15. }
  16. }

如果使用这个方式需要考虑this是否是唯一对象

方式三:使用当前类

  1. public class ThreadTest implements Runnable{
  2. private int ticket = 100;
  3. @Override
  4. public void run() {
  5. while (true){
  6. synchronized (ThreadTest.class) {
  7. if (ticket > 0) {
  8. System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
  9. ticket--;
  10. } else {
  11. break;
  12. }
  13. }
  14. }
  15. }
  16. }

解决方式二:同步方法

  1. public class ThreadTest implements Runnable {
  2. private int ticket = 100;
  3. @Override
  4. public void run() {
  5. while (true) {
  6. show();
  7. if (ticket == 0) {
  8. break;
  9. }
  10. }
  11. }
  12. public synchronized void show() {
  13. if (ticket > 0) {
  14. System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
  15. ticket--;
  16. }
  17. }
  18. }

使用同步方法的时候同步监视器的对象就是:this

如果使用继承Thread的方式需要把方法改为static,当前同步监视器的对象就是当前类

解决方式三:Lock锁——JDK 5.0新增

  1. public class ThreadTest implements Runnable {
  2. private int ticket = 100;
  3. // 实列化ReentrantLock
  4. private ReentrantLock lock = new ReentrantLock();
  5. @Override
  6. public void run() {
  7. while (true) {
  8. try {
  9. // 进入锁
  10. lock.lock();
  11. if (ticket > 0) {
  12. System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
  13. ticket--;
  14. }else {
  15. break;
  16. }
  17. }finally {
  18. // 手动关闭锁
  19. lock.unlock();
  20. }
  21. }
  22. }
  23. }

死锁问题

死锁概述:不通的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己所需要的同步资源,就会形成死锁。出现死锁后不会出现异常,也不会提示,只是所有线程都会处于阻塞状态,无法继续。

死锁是开发中是需要避免的操作

解决方法

  1. 使用专门的算法
  2. 尽量减少同步资源的定义
  3. 尽量避免嵌套同步