java多线程

打印 上一主题 下一主题

主题 570|帖子 570|积分 1712

java多线程

进程、线程与多线程


  • 进程是执行程序的一次执行过程,是一个动态的概念,是系统支援分配的单位
  • 通常一个进程可以包含一个或多个线程。线程是CPU调度和执行的单位
  • 线程就是独立执行的路径,由cpu调度
  • 线程会带来额外的开销,如cpu调度时间,并发控制开销
  • 每个线程在自己的工作内存中交互,内存控制不当会造成数据的不一致
  • main()称之为主线程,为系统的入口,用于执行整个程序
  • 程序运行时,即使没有自己创建线程,后台也会有多个线程如主线程,gc线程
  • 很多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即只有一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以有了同时执行的错觉
线程的三种创建方式


  • Thread class:继承Thread类
  • Runnable接口:实现Runnable接口
  • Callable接口:实现Callable接口
通过继承Thread创建线程

​        注意:Thread类实现了Runnable接口
流程:
  1. 1. 自定义线程类继承Thread类
  2. 2. 重写其run()方法,编写程序执行体
  3. 3. 创建线程对象,调用start()方法启动线程
复制代码
  1. public class MyThread extends Thread {
  2.     @Override
  3.     public void run() {
  4.         //run方法线程体
  5.         for (int i = 0; i < 5; i++) {
  6.             System.out.println("run方法线程--"+i);
  7.         }
  8.     }
  9.     public static void main(String[] args) {
  10.         //run方法线程
  11.         MyThread myThread = new MyThread();
  12.         //启动线程,另外线程只能启动一次死亡之后不能重新启动
  13.         myThread.start();
  14.         //主线程
  15.         for (int i = 0; i < 5; i++) {
  16.             System.out.println("主线程--"+i);
  17.         }
  18.     }
  19. }
  20. /*out:
  21. 主线程--0
  22. run方法线程--0
  23. 主线程--1
  24. run方法线程--1
  25. 主线程--2
  26. run方法线程--2
  27. 主线程--3
  28. run方法线程--3
  29. 主线程--4
  30. run方法线程--4
  31. 注意:输出结果不唯一,每次执行结果不一样
  32. 线程开启不一定立即执行,是由cpu调度执行
复制代码
实现Runnable接口创建线程
  1. 1. 自定义线程类实现Runnable接口
  2. 2. 重写其run()方法,编写程序执行体
  3. 3. 创建该实现类对象,创建Thread线程对象(或者创建Thread对象),并且向Thread对象丢入Runnable实现类(代理方法)
复制代码
  1. public class MyThread implements  Runnable {
  2.     @Override
  3.     public void run() {
  4.         //run方法线程体
  5.         for (int i = 0; i < 5; i++) {
  6.             System.out.println("run方法线程--"+i);
  7.         }
  8.     }
  9.     public static void main(String[] args) {
  10.         //run方法线程
  11.         MyThread myThread = new MyThread();
  12.         //静态代理
  13.         new Thread(myThread).start();
  14.         //主线程
  15.         for (int i = 0; i < 5; i++) {
  16.             System.out.println("主线程--"+i);
  17.         }
  18.     }
  19. }
  20. /*
  21. 主线程--0
  22. run方法线程--0
  23. 主线程--1
  24. run方法线程--1
  25. 主线程--2
  26. 主线程--3
  27. run方法线程--2
  28. 主线程--4
  29. run方法线程--3
  30. run方法线程--4
复制代码
相比于前一种更推荐本方法:避免单继承局限,灵活方便,方便一个对象被多个线程使用
例如:
  1. public class MyThread implements  Runnable {
  2.     private  int number=10;
  3.     @Override
  4.     public void run() {
  5.        while (true){
  6.            if(number<=0){
  7.                break;
  8.            }
  9.            //线程休眠,防止cpu速度太快瞬间把票抢完
  10.            try {
  11.                Thread.sleep(10);
  12.            } catch (InterruptedException e) {
  13.                e.printStackTrace();
  14.            }
  15. //当前线程名字
  16.            System.out.println(Thread.currentThread().getName()+"--"+number--);
  17.        }
  18.     }
  19.     public static void main(String[] args) {
  20.         //run方法线程
  21.         MyThread myThread = new MyThread();
  22.         //第二个参数为线程名字
  23.         new Thread(myThread,"李").start();
  24.         new Thread(myThread,"马").start();
  25.         new Thread(myThread,"王").start();
  26.     }
  27. }
  28. /*
  29. 马--9
  30. 王--10
  31. 李--10
  32. 马--8
  33. 李--8
  34. 王--8
  35. 马--7
  36. 王--6
  37. 李--6
  38. 王--5
  39. 马--4
  40. 李--3
  41. 李--0
  42. 马--2
  43. 王--1
  44. 注意:至于为什么出现两个人得到同一个数字,或者出现0,则是一些经典的线程同步问题
复制代码
线程的五大状态



线程方法


停止线程


  • 可以但不推荐使用JDK提供的stop()、destroy()方法,已经废弃
  • 推荐线程自己停下来
  • 建议使用一个标志位进行终止变量,当flag=false终止线程运行
  1. //<>中填写返回值类型(下面call方法的返回值)
  2. public class MyThread implements Callable<Boolean> {
  3.     //重写call方法,返回值保持和<>中的一样
  4.     @Override
  5.     public Boolean call() throws Exception {\
  6.         //具体线程操作写在这里
  7.         return false;
  8.     }
  9.     public static void main(String[] args) throws ExecutionException, InterruptedException {
  10.         MyThread myThread1 = new MyThread();
  11.         MyThread myThread2 = new MyThread();
  12.         MyThread myThread3 = new MyThread();
  13.         //创建执行服务:newFixedThreadPool线程池,参数为线程池大小
  14.         ExecutorService ser = Executors.newFixedThreadPool(3);
  15.         //提交执行:<>中类型就是call的返回类型
  16.         Future<Boolean> r1 = ser.submit(myThread1);
  17.         Future<Boolean> r2 = ser.submit(myThread2);
  18.         Future<Boolean> r3 = ser.submit(myThread3);
  19.         //获取结果:call()返回值
  20.         Boolean rs1 = r1.get();
  21.         Boolean rs2 = r2.get();
  22.         Boolean rs3 = r3.get();
  23.         //关闭服务
  24.         ser.shutdownNow();
  25.     }
  26. }
复制代码
线程休眠


  • sleep()指定当前线程阻塞的毫秒数
  • 运行状态转为阻塞状态
  • sleep存在异常InterruptedException
  • sleep时间到达后进入就绪状态
  • 其可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁
没啥可说的固定写法:Thread.sleep()
线程礼让


  • 即让当前正在执行的线程暂停,但不阻塞
  • 把线程从运行转为就绪状态
  • 让cpu重新调度,礼让不一定成功,看cpu心情
  • 固定写法:Thread.yield();
  1.         //MyThread是Runnable接口的实现类
  2.         MyThread myThread = new MyThread();
  3.         //静态代理
  4.         new Thread(myThread).start();
复制代码
线程强制执行


  • Join合并线程,待此线程完成后,才去执行其他线程
  • 可以看作插队
  1. public interface MyInterface {
  2.     void lambda();
  3. }
  4. class main {
  5.     public static void main(String[] args) {
  6. //        无参且一行
  7.         MyInterface myInterface =() -> System.out.println("hello world");
  8. //        有参数a
  9. //        MyInterface myInterface =a  -> System.out.println("hello world");
  10. //        多行
  11. //        MyInterface myInterface1 = ()->{
  12. //            System.out.println();
  13. //            System.out.println()
  14. //        };
  15.         
  16. //        上述代码完全类似下面的匿名内部类
  17. //        MyInterface myInterface1 = new MyInterface() {
  18. //            @Override
  19. //            public void lambda() {
  20. //                System.out.println("hello world");
  21. //            }
  22. //        };
  23.     }
  24. }
  25. //out:hello world
复制代码
线程状态观测

  1. public class MyThread implements  Runnable {
  2.     private boolean flag = true;
  3.     @Override
  4.     public void run() {
  5.         int i = 0;
  6.         while (flag){
  7.             System.out.println("run........Thread"+i++);
  8.         }
  9.     }
  10.     public void stop(){
  11.         this.flag = false;
  12.         System.out.println("线程已经停止");
  13.     }
  14.     public static void main(String[] args) {
  15.         MyThread myThread = new MyThread();
  16.         new Thread(myThread).start();
  17.         for (int i = 0; i < 10; i++) {
  18.             //不加这一句的话循环速度太快以至于线程还没运行
  19.             System.out.println("main---"+i);
  20.             if(i==5){
  21.                 //调用自己写的stop方法
  22.                 myThread.stop();
  23.             }
  24.         }
  25.     }
  26. }
  27. /*
  28. main---0
  29. run........Thread0
  30. main---1
  31. run........Thread1
  32. main---2
  33. run........Thread2
  34. main---3
  35. run........Thread3
  36. main---4
  37. run........Thread4
  38. main---5
  39. run........Thread5
  40. 线程已经停止
  41. main---6
  42. main---7
  43. main---8
  44. main---9
复制代码
线程优先级


  • 线程优先级用数字表示:Thread.MIN_PRIORITY=1;
    ​                                                                                Thread.MAX_PRIORITY = 10;
    ​                                                                                Thread.NORM_PRIORITY = 5;
    上面为最小最大正常优先级,均为常量
  • 可以使用以下方式改变或者获取优先级:
    1. .getPrioriyt
    1. .setPriority(int xxx)
  • 另外,优先级高并不是一定先执行,而是相对来说权重更高,到底先还是后看cpu调度
  1. public class MyThread implements  Runnable {
  2.     private boolean flag = true;
  3.     @Override
  4.     public void run() {
  5.         System.out.println(Thread.currentThread().getName()+"开始执行");
  6.         Thread.yield();
  7.         System.out.println(Thread.currentThread().getName()+"完成执行");
  8.     }
  9.     public static void main(String[] args) {
  10.         MyThread myThread = new MyThread();
  11.         MyThread myThread1 = new MyThread();
  12.         new Thread(myThread,"a").start();
  13.         new Thread(myThread1,"b").start();
  14.     }
  15. }
  16. /*
  17. a开始执行
  18. b开始执行
  19. b完成执行
  20. a完成执行
复制代码
守护线程


  • daemon
  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程完毕
  • 守护线程如后台记录操作日志、监控内存、垃圾回收等待
  • 守护线程:字面意思守护用户线程,虚拟机无视是否还有守护线程存在,当用户线程结束时虚拟机关闭
  1. public class MyThread implements  Runnable {
  2.     @Override
  3.     public void run() {
  4.         for (int i = 0; i < 5; i++) {
  5.             System.out.println("VIP驾到通通闪开!"+i);
  6.         }
  7.     }
  8.     public static void main(String[] args) throws InterruptedException {
  9.         MyThread myThread1 = new MyThread();
  10.         Thread thread1 = new Thread(myThread1, "vip");
  11.         //主线程
  12.         for (int i = 0; i < 10; i++) {
  13.             if(i==5){
  14.                 thread1.start();
  15.                 thread1.join();
  16.             }
  17.             System.out.println("我是一个主线程"+i);
  18.         }
  19.     }
  20. }
  21. /*
  22. 我是一个主线程0
  23. 我是一个主线程1
  24. 我是一个主线程2
  25. 我是一个主线程3
  26. 我是一个主线程4
  27. VIP驾到通通闪开!0
  28. VIP驾到通通闪开!1
  29. VIP驾到通通闪开!2
  30. VIP驾到通通闪开!3
  31. VIP驾到通通闪开!4
  32. 我是一个主线程5
  33. 我是一个主线程6
  34. 我是一个主线程7
  35. 我是一个主线程8
  36. 我是一个主线程9
复制代码
线程同步


  • 就是多个线程操作同一个资源
  • 线程同步其实就是一个等待机制,多个需要同时访问对象的线程进入对象等待池形成队列
  • 并发:同一个对象被多个线程操作。如买票
  • 队列和锁保障线程安全性
  • 为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized
  • 与此同时当一个线程获得对象的排它锁,独占资源,其他线程必须等待使用后释放锁即可,可能存在以下问题:

线程同步操作多种不安全问题,具体可以看上面实现Runnable接口创建线程的例子
synchronized(锁)


  • 针对上面所说的问题:我们可以用锁来解决,即synchronized关键字,它包括方法和块两大用法
  • 同步方法,synchronized方法控制对“对象”的访问,每个对象对应一把锁。每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则阻塞,方法一旦执行就会独占该锁,直到该方法返回。缺陷:若将一个大方法申明为synchronized将会影响效率
  • 同步块:synchronized(obj){}  obj称之为同步监视器,其可以是任何对象,但是推荐共享资源作为同步监视器。另外同步方法不用同步监视器因为其同步监视器就是this,就是这个对象本身,或者class
  • 同步监视器执行过程:
    1. 第一个线程访问,锁定同步监视器
    2. 第二个线程访问,发现已经被锁定,无法访问
    3. 第一个线程访问完毕,解锁同步监视器
    4. 第二个线程访问,发现同步监视器没有锁,然后锁定并且访问
使用同步方法解决问题:
  1.     public static void main(String[] args) throws InterruptedException {
  2.         Thread thread = new Thread(() -> {
  3.             for (int i = 0; i < 5; i++) {
  4.                 try {
  5.                     Thread.sleep(1000);
  6.                 } catch (InterruptedException e) {
  7.                     e.printStackTrace();
  8.                 }
  9.             }
  10.             System.out.println("////////");
  11.         });
  12.         //观测状态
  13.         Thread.State state = thread.getState();
  14.         System.out.println(state);
  15. //        启动线程
  16.         thread.start();
  17.         state = thread.getState();
  18.         System.out.println(state);
  19. //        只要线程不终止一直输出状态
  20.         while (state != Thread.State.TERMINATED){
  21.             Thread.sleep(1000);
  22.             state = thread.getState();
  23.             System.out.println(state);
  24.         }
  25.     }
  26. /*
  27. NEW
  28. RUNNABLE
  29. RUNNABLE
  30. TIMED_WAITING
  31. TIMED_WAITING
  32. RUNNABLE
  33. TIMED_WAITING
  34. ////////
  35. TERMINATED
复制代码
锁块:
  1. public class MyThread implements  Runnable {
  2.     @Override
  3.     public void run() {
  4.         try {
  5.             Thread.sleep(1000);
  6.         } catch (InterruptedException e) {
  7.             e.printStackTrace();
  8.         }
  9.         System.out.println(Thread.currentThread().getName());
  10.     }
  11.     public static void main(String[] args) throws InterruptedException {
  12.         //主线程优先级:主线程优先级无法修改
  13.         System.out.println(Thread.currentThread().getName()+"-----"+Thread.currentThread().getPriority());
  14.         MyThread myThread = new MyThread();
  15.         Thread t1 = new Thread(myThread,"t1");
  16.         Thread t2 = new Thread(myThread,"t2");
  17.         Thread t3 = new Thread(myThread,"t3");
  18.         Thread t4 = new Thread(myThread,"t4");
  19.         Thread t5 = new Thread(myThread,"t5");
  20. //        一定要注意:先设置优先级再启动否则没用
  21.         t1.start();
  22.         t2.setPriority(1);
  23.         t2.start();
  24.         //设置为最大优先级
  25.         t3.setPriority(Thread.MAX_PRIORITY);
  26.         t3.start();
  27.     }
  28. }
  29. /*
  30. main-----5
  31. t3
  32. t2
  33. t1
复制代码
CopyOnWriteArrayList
  1. Thread thread = new Thread();
  2. //默认是false代表用户线程,设置为true表示守护线程
  3. thread.setDaemon(true);
复制代码
死锁

多个线程各自占有一些共享资源,同时互相等待对方占有资源,从而导致两个或多个线程都在等待对方释放资源停止执行的情况。
某一个同步块同时拥有两个以上对象的锁,就可能出现死锁
  1. public class MyThread implements  Runnable {
  2.     private  int number=10;
  3.     private  boolean flag = true;
  4.     @Override
  5.     public  void run() {
  6.         while (flag) {
  7.             try {
  8.                 Thread.sleep(1000);
  9.             } catch (InterruptedException e) {
  10.                 e.printStackTrace();
  11.             }
  12.             buy();
  13.         }
  14.     }
  15.     //上锁,run方法也可以锁
  16.     public synchronized void buy() {
  17.         if (number < 1) {
  18.             flag=false;
  19.             return;
  20.         }
  21.             System.out.println(Thread.currentThread().getName() + "--" + number--);
  22.         }
  23.     public static void main(String[] args) {
  24.         MyThread myThread = new MyThread();
  25.         new Thread(myThread,"李").start();
  26.         new Thread(myThread,"马").start();
  27.         new Thread(myThread,"王").start();
  28.     }
  29. }
  30. /*
  31. 李--10
  32. 王--9
  33. 马--8
  34. 马--7
  35. 李--6
  36. 王--5
  37. 马--4
  38. 李--3
  39. 王--2
  40. 马--1
复制代码
产生死锁的必要条件

Lock(锁)


  • JDK5.0开始,java提供更加强大的线程同步机制--通过显式定义同步锁对象来实现同步。同步锁使用Lock充当
  • 每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应当先获得Lock锁
  • ReentranLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁
  1. public class MyThread implements  Runnable {
  2.     private  int number=10;
  3.     private  boolean flag = true;
  4.     @Override
  5.     public  void run() {
  6.         while (flag) {
  7.             try {
  8.                 Thread.sleep(1000);
  9.             } catch (InterruptedException e) {
  10.                 e.printStackTrace();
  11.             }
  12.             //同步块,()填变化的量必须是引用类型,锁定的就是传入参数
  13.             synchronized (Integer.valueOf(number)){
  14.                 if (number < 1) {
  15.                     flag=false;
  16.                 }
  17.                 else {
  18.                     System.out.println(Thread.currentThread().getName() + "--" + number--);
  19.                 }
  20.             }
  21.             }
  22.         }
  23.     public static void main(String[] args) {
  24.         MyThread myThread = new MyThread();
  25.         new Thread(myThread,"李").start();
  26.         new Thread(myThread,"马").start();
  27.         new Thread(myThread,"王").start();
  28.     }
  29. }
  30. /*
  31. 马--10
  32. 王--9
  33. 李--8
  34. 李--7
  35. 王--6
  36. 马--5
  37. 马--4
  38. 李--3
  39. 王--2
  40. 马--1
复制代码
线程协作--生产者消费者问题

生产者消费者问题:生产者消费者共享一个资源,并且生产者和消费者之间相互依赖,互为条件,操作系统学过的不想多说具体如下:

下面两个解决办法后面有具体演示
解决方法1:

解决方法二:信号灯法
java提供了几个方法解决线程之间的通信问题:
注意:以下均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常,另外sleep不会释放锁,wait会释放锁

解决方法一:管程法
  1. //线程安全的arraylist
  2. CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
  3. for (int i = 0; i < 1000; i++) {
  4.     new Thread(()->{
  5.         list.add(Thread.currentThread().getName());
  6.     }).start();
  7. }
  8. Thread.sleep(3000);
  9. System.out.println(list.size());
  10. //1000
复制代码
上面实现的存在一定问题,但是要传达的思想就这样
解决方法二:信号灯法
  1. //当mirror和lipstick都只有一份时
  2.             //获得mirror
  3.             synchronized (mirror){
  4. //                获得了mirror,此时lipstick被另外一个线程占据等待
  5. //                其释放,与此同时占据lipstick的线程也在等待它释放mirror
  6. //                  于是死锁发生了
  7.                 synchronized (lipstick){
  8.                     
  9.                 }
  10.             }
复制代码
线程池


  • 经常创建和销毁、使用量大的资源,对性能影响很大
  • 思路:提前创建好多个线程,放入线程池,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
  • 好处:

    • 提高响应速度
    • 降低资源消耗
    • 便于线程管理

      • corePoolSize:核心池大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时最多保持多少时间后终结


  • 相关api:

    • ExecutorService :真正的线程池接口。常见子类ThreadPoolExecutor

      • 相关方法:


  • Executors:工具类,用于创建并返回不同类型的线程池
  1. public class MyThread implements  Runnable {
  2.     private static Integer number=10;
  3.     private  boolean flag = true;
  4.     //定义lock锁
  5.     private final  ReentrantLock lock = new ReentrantLock();
  6.     @Override
  7.     public  void run() {
  8.         while (flag) {
  9.             try {
  10.                 Thread.sleep(100);
  11.                 lock.lock();//加锁,上锁区域就是lock到unlock区域
  12.                 if (number < 1) {
  13.                     flag=false;
  14.                 }
  15.                 else {
  16.                     System.out.println(Thread.currentThread().getName() + "--" +number--);
  17.                 }
  18.             } catch (InterruptedException e) {
  19.                 e.printStackTrace();
  20.             } finally {
  21.                 //解锁
  22.                 lock.unlock();
  23.             }
  24.             }
  25.         }
  26.     public static void main(String[] args) {
  27.         MyThread myThread = new MyThread();
  28.         new Thread(myThread,"李").start();
  29.         new Thread(myThread,"马").start();
  30.         new Thread(myThread,"王").start();
  31.     }
  32. }
  33. /*
  34. 马--10
  35. 王--9
  36. 李--8
  37. 李--7
  38. 王--6
  39. 马--5
  40. 王--4
  41. 李--3
  42. 马--2
  43. 马--1
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

刘俊凯

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表