zoukankan      html  css  js  c++  java
  • Java 之多线程

    1. 进程
    2. 线程
    3. 多线程存在的意义
    4. 线程的创建方式
    5. 多线程的特性

    进程: 正在运行中的程序.打开一个软件, 相当于打开了一个进程.

    线程

    1. 线程, 就是进程中一个负责程序执行的控制单元(执行路径).
      • 一个进程中至少要有一个线程, 也可以有多个执行路径(即多线程).
      • 开启多线程是为了同时运行多部分代码
      • 每一个线程都有自己运行的内容, 这个内容可以称为线程要执行的任务.
    2. 多线程优缺点
      • 好处: 解决了多部分同时运行的问题
      • 弊端: 线程太多会造成效率的降低
        其实应用程序的执行都是 cpu 在做着快速的切换完成的, 这个切换是随机的.

    JVM 线程分析

    JVM 启动时, 就启动了多个线程, 至少有两个线程可以分析的出来:

    1. 执行 main 函数的线程. 该线程的任务代码都定义在 main 函数中.
    2. 负责垃圾回收的线程

    创建线程

    1. 创建线程的目的是为了开启一条执行路径, 可以让指定的代码和其他代码实现同时运行.
      其中, JVM 创建的主线程的任务都定义在主函数中.
    2. 自定义的线程的任务是通过 Thread 类中的 run 方法来体现的. 也就是说, run 方法就是封装
      自定义线程运行任务的函数. Thread 类用于描述线程, 线程是需要任务的, 而 run() 方法中定义
      的就是线程要运行的任务代码.
    3. 开启线程是为了运行指定代码, 所以只有继承 Thread 类, 并复写 run() 方法, 将运行的代码定义
      在 run() 方法中即可.

    创建线程的方式一: 继承 Thread 类

    步骤:

    1. 定义一个类继承 Thread 类
    2. 覆盖 Thread 类中的 run() 方法
    3. 直接创建 Thread 类的子类对象,即创建线程
    4. 调用 start() 方法开启线程并调用线程的任务 run 方法执行.
    class Demo extends Thread
    {
        private String name;
        Demo(String name)
        {
            this.name = name;
        }
    
        public void run()
        {
            show();
        }
    
        public void show()
        {
            for(int x=0; x<10; x++)
            {
                for(int y=-999999; y<999999; y++){}
                System.out.println(name + "...x="+x);
            }
        }
    }
    
    class ThreadDemo
    {
        public static void main(String[] args)
        {
            Demo d1 = new Demo("旺财"); // 一创建线程, 该线程就有编号
            Demo d2 = new Demo("xiaoqiang");
    
            d1.start(); //开启线程, Java 虚拟机调用该线程的 run 方法
            d2.start();
        }
    }
    

    Thread 类中的方法

    • getName(), 获取线程的名称, Thread-编号(从 0 开始)
    • currentThread(), 静态方法, 获取当前执行线程对象.
      通过 Thread.currentThread().getName() 获取当前运行线程

    线程的四种状态

    创建线程的方式二: 实现 Runnable 接口

    步骤:

    1. 定义类实现 Runnable 接口
    2. 覆盖接口中的 run() 方法, 将线程的任务代码封装到 run() 方法中
    3. 通过 Thread 类创建线程对象, 并将 Runnable 接口的子类对象作为 Thread 类的构造函数的
      参数进行传递. 因为线程的任务都封装在 Runnable 接口子类对象的 run 方法中, 所以要在线程对象
      创建时就必须明确要运行的任务.
    4. 调用线程对象的 start 方法开启线程

    实现 Runnable 接口的好处:

    1. 将线程的任务从线程的子类中分离出来, 进行了单独的封装.
      按照面向对象的思想将任务封装成对象.
    2. 避免了 java 单继承的局限性.
    // 示例: 卖票
    class Ticket implements Runnable
    {
        private int num = 100;
    
        public void run()
        {
            sale();
        }
    
        public void sale()
        {
            while(true)
            {
                if(num>0)
                {
                   System.out.println(Thread.currentThread().getName()+"......"+num--);
                }
            }
        }
    }
    
    class TicketDemo
    {
        public static void main(String[] args)
        {
            Ticket t = new Ticket(); // 创建一个线程任务对象
    
            Thread t1 = new Thread(t); // 创建线程
            Thread t2 = new Thread(t); // 创建线程
    
            t1.start();
            t2.start();
        }
    }
    

    线程安全问题

    线程安全问题产生的原因:

    1. 多个线程在操作共享的数据
    2. 操作共享数据的代码有多条
    3. 当一个线程在执行操作共享数据的多条代码过程中, 其他线程参与了运算, 就会导致线程安全问题.

    解决思路: 就是将多条操作共享数据的线程代码封装起来, 当有线程在执行这些代码的时候,
    其他线程是不可以参与运算的, 只有当前线程把这些代码都执行完毕后, 其他线程才可以参与运算.

    同步代码块

    在 java 中, 用同步代码块就可以解决这个问题.
    格式:

    synchronized(对象) //此处对象相当于锁
    {
        需要被同步的代码;
    }
    
    // 同步代码块
    class Ticket implements Runnable
    {
        private int num = 100;
        Object obj = new Object();
    
        public void run()
        {
            sale();
        }
    
        public void sale()
        {
            while(true)
            {
                synchronized(obj) // 同步代码块, 对象 obj 相当于锁
                {
                    if(num>0)
                    {
                        System.out.println(Thread.currentThread().getName()+"......"+num--);
                    }
                }
            }
        }
    }
    
    1. 同步的好处: 解决了线程的安全问题
    2. 同步的弊端: 相对于以前降低了效率, 因为同步外的线程都会判断同步锁.
    3. 同步的前提: 同步中必须有多个线程并使用同一个锁.

    同步函数

    // 需求: 两个储户, 往一个帐号存钱, 每次存 100, 各存 3 次
    
    class Bank
    {
        private int sum;
    /*  private Object obj = new Object();
        public void add(int num)
        {
            synchronized(obj) //此处存在安全隐患, 使用同步代码块
            {
                sum = sum + num;
                System.out.println("sum="+sum);
            }
        }
    */
    
       // 同步函数
       public synchronized void add(int num)
          {
                  sum = sum + num;
                  System.out.println("sum="+sum);
          }
    }
    
    class Cus implements Runnable
    {
        private Bank b = new Bank();
        public void run()
        {
            for(int x=0; x<3; x++)
            {
                b.add(100);
            }
        }
    }
    
    class BankDemo
    {
        public static void main(String[] args)
        {
            Cus c = new Cus();
            Thread t1 = new Thread(c);
            Thread t2 = new Thread(c);
            t1.start();
            t2.start();
        }
    }
    
    • 同步函数使用的锁是 this
    • 同步函数和同步代码块的区别
      • 同步函数的锁是固定的, this
      • 同步代码块的锁是任意的对象
    • 建议使用同步代码块
    • 静态同步函数时的锁是该函数所属字节码文件对象(Class), 可以用 getClass 方法获取,
      也可以用 当前类名.class 表示. 因为静态函数中没有 this

    多线程下的单例

    // 饿汉式
    class Single
    {
        private static Single s = new Single();
    
        private Single(){}
    
        public static Single getInstance()
        {
            return s;
        }
    }
    
    // 懒汉式
    class Single
    {
        private static Single s = null;
    
        private Single(){}
    
        public static Single getInstance()
        {
            if(s == null) // 此处存在安全隐患
                s = new Single();
            return s;
        }
    }
    
    // 懒汉式升级, 线程安全
    class Single
    {
        private static Single s = null;
    
        private Single(){}
    
        public static Single getInstance()
        {
            if(s==null) //此处为提高效率, 线程判断锁次数减少
            {
                synchronized(Single.class) // 注意 静态方法,
                {
                    if(s==null)
                        s = new Single();
                }
            }
            return s;
        }
    }
    

    死锁

    class Test implements Runnable
    {
        private boolean flag;
        Test(boolean flag)
        {
            this.flag = flag;
        }
    
        public void run()
        {
            if(flag)
            {
                synchronized(MyLock.locka)
                {
                    System.out.println("if  locka....");
                    synchronized(MyLock.lockb)
                    {
                        System.out.println("if  lockb....");
                    }
                }
            }
            else
            {
                synchronized(MyLock.lockb)
                {
                    System.out.println("else   lockb....");
                    synchronized(MyLock.locka)
                    {
                        System.out.println("else  locka....");
                    }
                }
            }
        }
    }
    
    class MyLock
    {
        public static final Object locka = new Object();
        public static final Object lockb = new Object();
    }
    
    class DeadLockTest
    {
        public static void main(String[] args)
        {
            Test a = new Test(true);
            Test b = new Test(false);
    
            Thread t1 = new Thread(a);
            Thread t2 = new Thread(b);
            t1.start();
            t2.start();
        }
    }
    


    参考资料

  • 相关阅读:
    蓝桥杯入门训练
    <泛> STL
    传递 hdu 5961 拓扑排序有无环~
    Alice and Bob hdu 4268
    Bipartite Graph hdu 5313 bitset 并查集 二分图
    Bomb HDU 3555 dp状态转移
    不要62 hdu 2089 dfs记忆化搜索
    Leaving Auction CF 749D
    Moo University
    算法(三)
  • 原文地址:https://www.cnblogs.com/linkworld/p/7454783.html
Copyright © 2011-2022 走看看