zoukankan      html  css  js  c++  java
  • 十、多线程基础-需要强化的知识点

    1、sleep()和wait()方法异同
      sleep方法和wait方法都可以用来放弃CPU一定的时间,不同点在于如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器
    1)Thread.sleep():方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。
    2)Object.wait():线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态
    2、start()和run()方法区别
    start()方法:
    1)用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。
    2)通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到CPU时间片,就开始执行run()方法。
    run()方法:
    1)run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条。
    总结:
    1)调用start方法方可启动线程,
    2)而run方法只是thread的一个普通方法调用,还是在主线程里执行。
    3)把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用run()方法,这是由jvm的内存机制规定的。
    4)    并且run()方法必须是public访问权限,返回值类型为void.
    3、wait()和notify()/notifyAll()方法为什么要在同步块中被调用
      这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁
    4、wait()和notify()/notifyAll()方法在放弃对象监视器时区别是什么
      wait()方法立即释放对象监视器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器 。
    5、线程的join、yield、priority用法
    1)thread1.join();可以将thread1理解为插队者,当thread1执行完毕之后当前线程才会继续执行

    public class JoinThreadDemo02 {
        /*
         * T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行  
         * */
        public static void main(String[] args) {
             final Thread t1 = new Thread(new Runnable() {
                public void run() {
                    for (int i = 0; i < 20; i++) {
                        System.out.println("t1,i:" + i);
                    }
                }
            });
            
             final Thread t2 = new Thread(new Runnable() {
                public void run() {
                    try {
                        t1.join();
                    } catch (Exception e) {
                        // TODO: handle exception
                    }
                    for (int i = 0; i < 20; i++) {
                        System.out.println("t2,i:" + i);
                    }
                }
            });
            
            Thread t3 = new Thread(new Runnable() {
                public void run() {
                    try {
                        t2.join();
                    } catch (Exception e) {
                        // TODO: handle exception
                    }
                    for (int i = 0; i < 20; i++) {
                        System.out.println("t3,i:" + i);
                    }
                }
            });
            t1.start();
            t2.start();
            t3.start();
        }
    }
    package threadLearning.join_yield_priority;
    class JoinThread implements Runnable {
    
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "---i:" + i);
            }
        }
    }
    View Code

    2)现代操作系统基本采用时分的形式调度运行的线程,线程分配得到的时间片的多少决定了线程使用处理器资源的多少,也对应了线程优先级这个概念。在JAVA线程中,通过一个int priority来控制优先级,范围为1-10,其中10最高,默认值为5。下面是源码(基于1.8)中关于priority的一些量和方法。
    t1.setPriority(10) // 注意设置了优先级, 不代表每次都一定会被执行。 只是CPU调度会优先分配
    3)Thread.yield()方法的作用:暂停当前正在执行的线程,并执行其他线程。(可能没有效果)yield()让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的机会。因此,使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。结论:大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
    6、synchronized和ReentrantLock的区别
    synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上:
    1)ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁
    2)ReentrantLock可以获取各种锁的信息
    3)ReentrantLock可以灵活地实现多路通知
    另外,二者的锁机制其实也是不一样的。ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word,这点我不能确定。
    7、volatile关键字 (示列2个)
    理解volatile关键字的作用的前提是要理解Java内存模型, volatile关键字的作用主要有两个:
    1)多线程主要围绕可见性和原子性两个特性而展开,使用volatile关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到volatile变量,一定是最新的数据
    2)代码底层执行不像我们看到的高级语言—-Java程序这么简单,它的执行是 Java代码–>字节码–>根据字节码执行对应的C/C++代码–>C/C++代码被编译成汇编语言–>和硬件电路交互 ,现实中,为了获取更好的性能JVM可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。使用volatile则会对禁止语义重排序,当然这也一定程度上降低了代码执行效率从实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic包下的类,比如AtomicInteger。
    普通变量在多线程中的不可见性示列:

    /**
     *
     * @classDesc: 功能描述:Volatile 关键字的作用是变量在多个线程之间可见。
     * 如果变量没有使用volatile关键字,那么变量在多线程之间是不可见的,线程读取的是该变量的副本,而不会从主内存中读取;
     * @author: zjb
     * @createTime: 创建时间:2018-6-24 下午4:57:55
     * @version: v1.0
     * @copyright:
     */
    
    public class ThreadVolatileDemo extends Thread{
        //public  volatile boolean flag=true;
        public  boolean flag=true;
        @Override
        public void run(){
            System.out.println("开始执行子线程...."+Thread.currentThread().getName());
            while (flag) {
            }
            System.out.println("线程停止"+Thread.currentThread().getName());
    
        }
        public void setRuning(boolean flag) {
            this.flag = flag;
        }
    }
    
    package threadLearning.volatileKeyWord;
    public class ThreadVolatileDemoTest {
        public static void main(String[] args) throws InterruptedException {
            ThreadVolatileDemo threadVolatileDemo = new ThreadVolatileDemo();
            threadVolatileDemo.start();
            Thread.sleep(1000);
            threadVolatileDemo.setRuning(false);
            System.out.println("flag 已经设置成false");
            Thread.sleep(1000);
            System.out.println("threadVolatileDemo.flag---》"+threadVolatileDemo.flag);
    
        }
        /*
            已经将结果设置为fasle为什么?还一直在运行呢。
        原因:线程之间是不可见的,读取的是副本,没有及时读取到主内存结果。
        解决办法使用Volatile关键字将解决线程之间可见性, 强制线程每次读取该值的时候都去“主内存”中取值
        */
    }
    View Code

    AtomicInteger的原子性示例:

    AtomicInteger :可以以原子方式更新的int值。
    AtomicInteger用于诸如原子递增计数器之类的应用程序,不能用作java.lang.Integer的替换。但是,这个类确实扩展了Number,允许处理基于数字的类的工具和实用程序进行统一访问。

    public class AtomicIntegerTest extends Thread {
        private static AtomicInteger atomicInteger = new AtomicInteger();
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                //等同于i++
                atomicInteger.incrementAndGet();
            }
            System.out.println("atomicInteger----->"+atomicInteger);
        }
    
        public static void main(String[] args) {
            // 初始化10个线程
            AtomicIntegerTest[] volatileNoAtomicThread = new AtomicIntegerTest[10];
            for (int i = 0; i < 10; i++) {
                // 创建
                volatileNoAtomicThread[i] = new AtomicIntegerTest();
            }
            
            for (int i = 0; i < volatileNoAtomicThread.length; i++) {
                volatileNoAtomicThread[i].start();
            }
        }
    
    }
    View Code

    8、volatile与synchronized区别
    1)volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法
    2)volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。使用Volatile不能保证线程的安全性(原子性)。synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。


    细水长流,打磨濡染,渐趋极致,才是一个人最好的状态。
  • 相关阅读:
    个人知识管理系统Version1.0开发记录(09)
    个人知识管理系统Version1.0开发记录(08)
    个人知识管理系统Version1.0开发记录(07)
    个人知识管理系统Version1.0开发记录(06)
    个人知识管理系统Version1.0开发记录(05)
    个人知识管理系统Version1.0开发记录(04)
    登录流程
    小程序开发工具黑屏
    免费BOOTSTARP后台模板
    JQUERY重写
  • 原文地址:https://www.cnblogs.com/jiarui-zjb/p/9622773.html
Copyright © 2011-2022 走看看