• Java


    概念

    关于什么是线程,进程等概念,请看下面文章:

    并发/并行/阻塞/非阻塞

    多进程概念

    多线程概念

    上述文章代码使用的是python,但是所有的概念和原理都是相同的;

    需要特别强调的是Java中的线程在多核处理器上是可以真正并行执行的,没有cpython中的全局锁这么一说

    线程的状态切换以及生命周期:

    image-20191127093852138

    注意:stop方法以及被弃用建议不要使用了,线程正确的结束,应该是线程任务全部完成,或者是被作为守护线程,被守护线程运行结束,再或者调用中断方法interrupt

    Java中启动线程的方式:

    package com.yh.lesson.collection.thread;
    
    import sun.nio.ch.ThreadPool;
    
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    public class Test {
        public static void main(String[] args) {
            //1.匿名类
            Thread td1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
            td1.start();
    
            //2.lambda表达式 DJK1.8
            Thread td2 = new Thread(()->{
                System.out.println(Thread.currentThread().getName());
            });
            td2.start();
    
            //3.runnable实现类
            Thread td3 = new Thread(new Runner());
            td3.start();
        }
    }
    class Runner implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
    
    			//4.继承extends 覆盖run方法
    class TTT extends Thread{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
    
    

    其他方法属性参照API,你可以把线程看做一个子程序,无法是启动,中断,获取状态,设置守护等等操作

    ThreadAPI

    常用方法 sleep,join

    sleep是线程睡眠一段时间然后继续运行

    注意:sleep是Thread类的静态方法,需要捕获异常

    //下面的代码输出大写A-Z 每个500毫秒循环一次
    new Thread(()->{
      for (int i = 65; i < 91; i++) {
        char c = (char) i;
        System.out.printf("%s",c);
        try {
          Thread.sleep(500);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }).start();
    

    join使当前线程等到调用join的线程执行结束才能执行

    注意:join是对象方法,也需要捕获异常

    System.out.println("start!");
    Thread th1 = new Thread(()->{
        for (int i = 0; i < 10; i++) {
            System.out.print(i);
        }
    });
    try {
        th1.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("over!");
    /*
    输出:
    start!
    0123456789
    over!
    */
    

    使得主线程等待th1执行完毕后再输出over!

    另外join函数重载了一个带有超时的方法,可以使主线程等待一段时间后开始运行

    优先级:

    image-20191127112518233

    image-20191127112614109

    image-20191127112625647

    线程锁:

    image-20191127114305274

    为什么使用synchronized

    当多个线程并发的操作同一个资源时就看你引发数据安全问题,解决方案就是使并发变成串行(异步变同步)

    使用synchronized关键字,可以使得代码串行,其可应用于方法声明或是代码块

    案例:

    在方法声明中使用,表示该方法不允许并发执行:

    public class LockDemo {
        public static void main(String[] args) {
            new Thread(()->{
               func("pic");
            }).start();
            new Thread(()->{
                func("text");
            }).start();
        }
        static synchronized void func(String s){
            for (int i=0;i < 10;i++){
                try {
                    Thread.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(s+"  "+i);
            }
        }
    }
    

    在代码块中使用,表示该代码块不允许并发执行:

    public class LockDemo2 {
        public static void main(String[] args) {
            new Thread(()->{
               func("pic");
            }).start();
            new Thread(()->{
                func("text");
            }).start();
    
        }
        static void func(String s){
    //        synchronized (new String("")) {   //注意用于同步的对象必须保持一致,否则无法同步
            synchronized ("") {//字符时常量所以对象地址没有变化 可以实现同步
                for (int i = 0; i < 10; i++) {
                    try {
                        Thread.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(s + "  " + i);
                }
            }
        }
    }
    

    注意:synchronized ()括号中可以填写任意对象,但是要保证多个线程使用的是同一个对象,否则无法实现同步

    避免随意加锁

    很多人在碰到安全问题时会随意的加锁,有加在方法上的,有随便加载代码上的.,很随意...

    锁的粒度:粒度表示锁住代码范围,
    • 被锁住的代码越少粒度越小,性能越好
    • 被锁住的代码越多粒度越大,性能越差
    锁对象的选择:

    锁对象可以是任意对象,有的人用this有的用Person.class,各种各样的....

    哪种都不算错,但是要注意,不要让不想关的线程在对象上访问锁,

    例如:有一个余额变量balance,线程A和线程B需要并发的操作这个变量,但是线程C并不会对balance做任何修改,此时就不应该让C也使用A/B线程所使用的锁;

    简单的说:尽量减少同一个锁对象管理的线程数量

    死锁:

    当多个线程对同一个锁对象执行wait时将进入死锁(为什么要这么做?)

    synchronized代码块的设计极大的避免了死锁出现的情况,就这点来看java更方便,python需要明确的加锁和解锁,代码量挺多....

    线程间通讯:

    首先明确多个线程共享进程中的资源,也就是是说本来就可以相互通讯,但是我们直接通过访问某个变量来通讯的话,是效率非常低的, 因为你不知道什么时候对方线程准备好了,所有你只能无限的循环去检查变量的值;

    来看这样一个例子:

    小明派小刚去安装炸弹,安装完成后小明只需要按下手中的遥控器就可以引爆,问题是小明不知道什么时候安装完成,并且小刚已经撤离,一旦过早的引爆小刚会发生危险,过晚的引爆又无法及时完成任务;这时候就需要让他们之间可以传递讯号,来获知彼此的状态;

    这个例子说明了为什么需要在线程间通讯,并且我们可以发现小明不需要不断的询问小刚是否完成,而是等待小刚给自己发信号,这就是线程通讯要实现的效果

    image-20191127133217555

    强调:上面的唤醒,始终针对的是在当前同步对象(锁对象)上等待的线程

    案例:

    package com.yh.lesson.collection.thread;
    
    public class ThreadCommunication {
        static boolean finished = false;//表示炸弹是否安装完成的变量
        static String lock = new String();//锁
    
        public static void main(String[] args) throws InterruptedException {
            //小明线程
            Thread t1 = new Thread(() -> {
                System.out.println("等待....");
                synchronized (lock){
                    try {
                        lock.wait();
                        System.out.println("收到信号!");
                        if(finished){
                            System.out.println("安全引爆,任务完成!");
                        }else{
                            System.out.println("安装未完成,小刚牺牲了!");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            //小刚线程
            Thread t2 = new Thread(() -> {
                try {
                    System.out.println("开始安装!");
                    Thread.sleep(3000);
                    System.out.println("安装完成!");
                    finished = true;
                    synchronized (lock) {
                        lock.notifyAll();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            
            t1.start();
            t2.start();
        }
    }
    

    注意:wait()和notify(),notifyall(),必须在同步代码中使用

  • 相关阅读:
    关于排列组合与分配问题
    Stirling数
    UVA 294 求约数的个数
    Linux中profile与bashrc的作用
    一致性哈希(consistent hashing)算法
    TCP三次握手与四次挥手
    MySQL查询昨天、今天、7天、近30天、本月、上一月数据
    java基础-注解Annotation原理和用法
    java基础-浅复制与深复制的理解
    Linux命令行提示符设置
  • 原文地址:https://www.cnblogs.com/yangyuanhu/p/11944834.html
Copyright © 2020-2023  润新知