• Java 多线程


    一. 基本名词概念

    1. 进程:一个运行的程序

    2. 线程:是进程中的单个顺序控制流,是一条执行路径

    (1)单线程:一个进程只有一条执行路径(记事本中的点开设置)

    (2)多线程:一个进程有多条执行路径(扫雷中的计时器和游戏)

    二. Java实现多线程

    1. 继承Thread类的方式实现多线程

    (1)定义类MyThread继承Thread类

    (2)在MyThread中重写run()方法

    (3)创建MyThread对象,启动线程

    2. 设置和获取线程名称

    (1)如果不设置线程名称默认名为"Thread-0, Thread-1..."

    (2)setName("xx")设置名称,getName()获取名称

    (3)设置名称也可以重写构造函数,调用父类Thread的带参构造函数,传入name

    (4)获取当前正在执行的线程对象的引用:静态方法Thread.currentThread()

    public class MyThread extends Thread{
        public MyThread() {
            super();
        }
    
        public MyThread(String name) {
            super(name);
        }
    
        @Override
        public void run() {//快捷键Ctrl+O
            for(int i=0;i<1000;i++){
                System.out.println(getName()+":"+i);
            }
        }
    }
    public class MyThreadDemo {
        public static void main(String[] args) {
            MyThread my1=new MyThread("hh1");
            MyThread my2=new MyThread("hh2");
            //run()封装线程执行的代码
            //my1.setName("Hello1");
            //my2.setName("Hello2");
            my1.start();//启动线程,由JVM调用run()方法
            my2.start();
            System.out.println(Thread.currentThread().getName());
        }
    }

    3. 线程调度

    线程调度有两种模型:

    • 分时调度模型:所有线程轮流使用CPU,平均分配每个线程占用CPU的时间片

    • 抢占式调度模型:优先让优先级高的线程使用CPU,如果优先级相同则随机选择一个

    Java使用的是抢占式调度模型

    Thread类中提供的方法:

    public final void setPriority(int newPriority)//更改此线程的优先级
    public final int getPriority()//返回此线程的优先级。
    MIN_PRIORITY=1
    MAX_PRIORITY=10
    NORM_PRIORITY=5//默认

    4. 线程控制

    public static void sleep(long millis)//当前正在执行的线程休眠(暂停执行)为指定的毫秒数
    public final void join()//等待这个线程死亡,等价于join(0)
    public final void setDaemon(boolean on)//标志着该线程是守护线程,如果运行的线程都只剩下守护线程是,Java虚拟机退出
    //Java中除了用户进程就是守护进程,守护进程是后台服务进程,比如垃圾回收进程

    5. 线程生命周期

    6. 实现Runnable接口的方式实现多线程

    (1)定义一个类MyRunnable实现Runnable接口

    (2)在Runnable类中重写run()方法

    (3)创建MyRunnable对象

    (4)创建Thread类的对象,把MyRunnable对象作为构造方法的参数

    (5)启动线程

    好处:

    • 避免了Java单继承的局限性,MyRunnable可以再继承其他父类

    • 适合多个相同的程序的代码去处理同一个资源的情况,较好的体现面向对象思想

    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            for(int i=0;i<1000;i++){
                System.out.println(Thread.currentThread().getName()+":"+i);//不能直接getName()
            }
        }
    }
    public class MyRunnableDemo {
        public static void main(String[] args) {
            MyRunnable my=new MyRunnable();
            Thread t1=new Thread(my);
            Thread t2=new Thread(my,"hh");//带设置名的构造函数
            t1.start();
            t2.start();
        }
    }

    三. 线程同步

    1. 卖票案例

    电影院出售1000张票,三个窗口同时卖票,每次出票时间10ms

    非同步出现的问题:

    • 相同的票出现了多次(三个线程都打印"第100张票正在出售")

    • 出现了负数的票

    问题原因:

    • 线程执行的随机性导致的

    2. 安全问题的解决

    解决方式:把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可

    (1)同步代码块:

    synchronized (任意对象){
      多条语句操作共享数据的代码
    }

    synchronized相当于给代码加锁,任意对象就可以看成一把锁

    同步的好处和弊端:

    • 好处:解决了多线程的数据安全问题

    • 弊端:线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中降低了程序运行效率

    public class SellTicket implements Runnable{
        private int ticket=1000;
        private Object obj=new Object();//三个线程要用同一把锁,所以放一个obj变量在这
        @Override
        public void run() {
            while(true){
                synchronized (obj){//同步代码块
                    //t1进来后,就会把这段代码锁起来
                    if(ticket>0){
                        try {
                            Thread.sleep(10);//快捷键Ctrl+Alt+T
                            //即使t1在休息,t2抢到CPU执行权,也无法执行这段代码
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
                        ticket--;
                    }
                }
            }
        }
    }
    public class SellTicketDemo {
        public static void main(String[] args) {
            SellTicket st=new SellTicket();
            Thread t1=new Thread(st,"窗口1");
            Thread t2=new Thread(st,"窗口2");
            Thread t3=new Thread(st,"窗口3");
            t1.start();
            t2.start();
            t3.start();
        }
    }

    (2)同步方法:

    同步方法:就是把synchronized关键字加到方法上

    修饰符 synchronized 返回值类型 方法名(参数){}

    同步方法的锁对象:this

    同步静态方法:就是把synchronized关键字加到静态方法上

    修饰符 static synchronized 返回值类型 方法名(参数){}

    同步静态方法的锁对象:类名.class

    public class SellTicket implements Runnable{
    //    private int ticket=1000;
        private static int ticket=1000;
        private Object obj=new Object();//三个线程要用同一把锁,所以放一个obj变量在这
        private int x=0;
        @Override
        public void run() {
            while(true){
                if(x%2==0){
    //                synchronized (obj){
    //                synchronized (this){//同步方法的锁对象:this
                    synchronized (SellTicket.class){//同步静态方法的锁对象:该类的字节码文件对象
                        if(ticket>0){
                            try {
                                Thread.sleep(10);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
                            ticket--;
                        }
                    }
                }else{
                    sellTicket();
                }
                x++;
            }
        }
    
        private static synchronized void sellTicket() {
            if(ticket>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
                ticket--;
            }
        }
    }

    3. 几个线程安全的类

    (1)StringBuffer

    • 线程安全,可变的字符序列,源码中的方法都加了synchronized

    • 如果不需要同步,应使用StringBuilder替代

    (2)Vector

    • 改进了List接口,使其成为Java Collections Framework的成员

    • 如果不需要线程安全,应使用ArrayList替代

    (3)Hashtable

    • 实现了一个哈希表,任何非null值可作为键或值

    • 如果不需要同步,建议使用Hashmap替代(Hashmap允许键或值均可为null,而Hashtable均不可)

    (4)工具类转为线程安全

    举例:

    List<String> synList = Collections.synchronizedList(new ArrayList<>());

    4. Lock锁

    为了体现更为清晰而广泛的加锁释放锁过程,JDK5之后提供了一个新的锁对象Lock

    void lock() //获得锁
    void unlock() //释放锁

    Lock是接口不能直接实例化,可以采用它的实现类ReentrantLock来实例化

    public class SellTicket implements Runnable{
        private int ticket=1000;
        private Lock lock =new ReentrantLock();
        @Override
        public void run() {
            while (true){
                try {//常用try-finally块,即使代码出问题也会释放锁
                    lock.lock();
                    if(ticket>0){
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
                        ticket--;
                    }
                } finally {
                    lock.unlock();
                }
            }
        }
    }

     5. volatile关键字

    (1)volatile是一种轻量级的同步机制,相比于synchronized和Lock(synchronized通常称为重量级锁),volatile更轻量级;

    (2)volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性,所以不能用于非原子操作(赋值是原子操作);

    (3)volatile用于修饰类变量或成员变量,被修饰的变量在缓存中的修改会被立即写入主存(一般变量只是先写入线程私有的本地内存);

    (4)volatile在操作系统底层是内存屏障的原语,用于解决可见性和有序性问题;

    (5)应用场景:单例模式

    public class TestInstance{
        private volatile static TestInstance instance;
        
        public static TestInstance getInstance(){        //1
            if(instance == null){                        //2
                synchronized(TestInstance.class){        //3
                    if(instance == null){                //4
                        instance = new TestInstance();   //5
                    }
                }
            }
            return instance;                             //6
        }
    }

    如果不用volatile,第五行代码在多线程情况下会出现问题,因为instance = new TestInstance(); 事实上有三个执行步骤

    a. memory = allocate() //分配内存
    
    b. ctorInstanc(memory) //初始化对象
     
    c. instance = memory //设置instance指向刚分配的地址

    由于可能存在指令重排,如果一个进程1先执行了ac,另一个线程b判断instance == null就会认为是false,产生错误。

    三. 生产者消费者模型

    Java在Object类提供了一些方法,体现生产和消费过程中的等待和唤醒:

    void wait() //使当前线程等待,直到另一个线程调用此对象的notify()或notifyAll()方法
    void notify() //唤醒一个在这个对象的监视器上等待的单个线程
    void notifyAll() //唤醒正在等待此对象监视器上的所有线程

    • 这些方法需要在同步的条件下,一般配合synchronized使用

    • 如果等待的线程有多个,notify方法只会唤醒其中一个,唤醒哪一个取决于操作系统对于多线程的管理

    • notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况

    生产者消费者代码示例:

    public class Box {
        //成员变量,表示第x件商品
        private int good;
        //表示箱内是否有商品
        private  boolean state=false;
    
        public synchronized void put(int good) {//注意要加synchronized关键字,否则wait()抛异常
            if(state){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.good = good;
            System.out.println("生产者将第"+this.good+"件商品放入箱中");
            state=true;
            notifyAll();
        }
    
        public synchronized void get() {
            if(!state){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("消费者拿到第"+good+"件商品");
            state=false;
            notifyAll();
        }
    }
    public class Producer implements Runnable{
        private Box b;
    
        public Producer(Box b){
            this.b=b;
        }
        @Override
        public void run() {
            for(int i=1;i<=5;i++){
                b.put(i);
            }
        }
    }
    public class Customer implements Runnable{
        private Box b;
        public Customer(Box b){
            this.b=b;
        }
        @Override
        public void run() {
            while(true){
                b.get();
            }
        }
    }
    public class BoxDemo {
        public static void main(String[] args) {
            Box b=new Box();//存储箱类
            Producer p=new Producer(b);//生产者对象
            Customer c=new Customer(b);//消费者对象
            Thread t1=new Thread(p);//生产者线程
            Thread t2=new Thread(c);//消费者线程
            t1.start();//启动线程
            t2.start();
        }
    }

    输出:

  • 相关阅读:
    动态规划
    nginx访问静态资源403
    转)服务器CPU占用100%的问题排查
    linux下脚本设置开机自启服务
    在蓝鲸标准运维上进行原子开发
    Django model数据排序
    Mac tar压缩命令,去除隐藏文件
    vue 简单的分页功能实现二
    vue 简单的分页功能实现一
    安装 libmagic in Mac OS (for Python-magic)
  • 原文地址:https://www.cnblogs.com/Kinghao0319/p/13394596.html
Copyright © 2020-2023  润新知