zoukankan      html  css  js  c++  java
  • Java多线程并发入门(基础知识)

    1.每个进程都拥有自己的一整套变量,线程共享数据。

    2.共享变量使得线程之间通信更有效、容易。

    3.关于Runnable函数式接口的问题参考我的博客:https://www.cnblogs.com/cckong/p/14264821.html

    4.直接调用run方法只会在一个线程执行,是同步的,start方法是异步的。

    我们知道Thread是实现Runnable接口的,但是调用start方法,是让线程进入了可执行态。如果调用run会当成普通方法调用。

    5.线程状态(6个:新建、可运行、阻塞、等待、计时等待、终止)

    这里是根据java Thread.state 官方表示的状态

    操作系统我们也有线程3种或者5种状态的说法

    三种状态:就绪、运行、等待
    五种状态:新建、就绪、运行、等待、退出

     6.想要获取当前状态 使用getState方法

    7.当你new Thread时进入新建状态了

    8.可运行状态取决于CPU处理器是否运行你,有可能在运行也有可能不在。

    9.阻塞或等待状态原因:需要的资源的锁没有释放

    等待另一个线程通知出现了一个条件

    方法有超时参数,线程会进入计时等待阶段。

    10.终止状态:run方法自然退出,线程自然终止

    没有捕获的异常使线程意外终止

    11.中断线程:interrupt方法请求终止一个线程,(阻塞的线程无法请求终止),最好捕获InterruptedException

    12.守护线程:setDaemon(true)转换为守护线程。

    13.异常

    检查型异常:其他所有

    非检查型异常:派生于Error类 、RuntimeException类的异常。

    14.run方法不能抛出任何检查型异常,但非检查型可能导致线程终止,或死亡。

    15.针对上条,可以用处理未捕获异常的处理器,实现UncaughtExceptionHandler接口的类。

    16.让线程调用setUncaughtExceptionHandler静态方法安装处理器。

    17.线程优先级 可以用setPriority方法1-10(不推荐声明优先级)

    18.竞态条件:两个或以上线程对于共享数据的修改会让对象破坏,需要要同步存取。

    例子:修改数字三步:读出数据,修改数据,写回数据。

    线程A读出、修改了,这时候线程B杀了进来,完成了修改三部曲,A继续第三步,这时候数字就已经不对了

    19.Reentrant类:重入锁

    从JDK5引入。下面来看一个实例

     private ReentrantLock mylock=new ReentrantLock();
    
        public void transfer(int a)
        {
            
            mylock.lock();
            try {
                a++;
            }
            finally {
                mylock.unlock();
            }
        }

    unlock操作一定要放在finally里面执行,不然其他线程永久阻塞。

    20.用条件对象(if/while)来管理获得一个锁但不能做有用工作的线程。

    例子:线程A进入修改数字程序,发现数字低于多少值不能修改了。

    21.await方法 线程暂停 并放弃锁

    signalAll方法重新激活所有等待此条件的线程(通知现在有可能满足条件,线程需自己去检查满足条件与否)

    22。只要一个对象的状态发生变化,就signalAll所有等待线程来检查条件。

    23.signal()唤醒指定线程。

    24.总结锁和条件对象:

    锁保护代码片段,一次只有一个线程执行代码

    一个锁可以有一个或多个条件对象

    锁管理进入保护代码的线程

    条件对象管理进入保护代码但还不能运行的线程

    24.synchronized关键字:

    可以作为synchronized方法 或者 synchronized代码块

    从Java1.0开始,每个对象都有一个内部锁。这个锁有一个内部条件

    如果一个方法声明时有synchronized关键字 对象的锁将保护整个方法代码

    public synchronized void method()
    {
        //body
    }

    等价于(intrinsic固有的)

    public void method()
    {
        this.intrinsicLock.lock();
        try{
            //body
        }
        finally{
            this.intrinsicLock.unlock();
        }
    }

    wait和notify的应用:

     wait将线程添加到等待集里面

    notify/notifyAll解除等待线程的阻塞

    25.并发关键字使用推荐顺序:
    (1)concurrent包里的机制,如阻塞队列

    (2)synchronized关键字,减少代码量

    (3)Lock/Condition

    26.监视器monitor特型:

    只包含私有字段的类

    类中每个对象都有一个关联的锁

    所有方法由这个锁锁定。

    锁可以有任意多的相关条件。

    27.监视器本质上就是os上的管程,https://www.cnblogs.com/noteless/p/10394054.html,这篇博客讲的非常清楚。

    28.volatile关键字(英文翻译:不稳定的)提供免锁机制,编译器和jvm知道该字段有可能被另一个线程更新

    29.

    private boolean done;
    public synchronized boolean isDone(){ return done;}
    public synchronized void setDone(){done=false;}

    同时调用两个方法,肯定会有一个加锁,另一个阻塞。

    使用volatile关键字修改对其他所有线程可见。

    private volatile boolean done;
    public  boolean isDone(){ return done;}
    public  void setDone(){done=false;}

    30.假设对共享变量只有赋值的功能,可以使用volatile关键字。

    31.stop(终止)不安全,suspend(阻塞)易导致死锁,resume(恢复suspend的线程):都试图控制一个线程的操作,而不是线程互操作。

    stop:结束所有未结束的方法,包括run方法,立即释放对象的锁,导致对象处于不一致状态。(转账,钱取出来了,还没放进别人账户,就是释放了,不安全)

    因为调用stop的线程不清楚 调用线程的状态!

    suspend如果线程在拥有一个锁,然后被阻塞了,锁是不能释放的,会导致别的线程无法访问,导致死锁。

    安全挂起线程:suspendRequested变量

    32.并发的修改一个数据结构,很容易破环这个数据结构(链表的指针无效、混乱等)

    33.可以并发的时候加锁来保证数据结构的安全,但不如选择线程安全的实现。

    34.阻塞队列:

    35.concurrentHashMap计数方法*如果映射太大 会超出int)

     36..concurrentHashMap默认支持16个书写器,通过构造器可以更多。

    37.

     38.对于并发散列映射

    search搜索:为每个键或者值应用一个函数,直到生成一个非null的结果

    reduce规约:组合所有键或值

    foreach:所有键或值应用一个函数。

    39.线程池::包含很多准备运行的线程。

    40.Callble和Runnable类似,封装一个call方法(函数式接口),但是有返回值,返回调用的类型。Callable<Integer> 就会返回Integer

    public interface Callable<V>
    {
        V call() throws Exception;
    }

    41.Future保存异步计算的结果,接口下的方法:
    V get()调用后阻塞,直到计算完成

    V get(long tiomeout,TimeUnit unit)调用后阻塞,在时间限制内计算完成返回,否则返回异常

    void cancel(boolean mayInterrupt)取消计算,已经开始计算让它中断,没开始则取消

    boolean isCancelled()是否取消

    boolean isDone()是否完成

    42.执行器(Executors)类中有很多静态工厂类方法来构造线程池

    43.实现线程的三个方法

    继承Thread类 实现Runnable接口 实现Callable接口

    44.使用Thread类写一个线程

    我们先观察一下 线程调用run方法和start方法的区别

    package com.Thread;
    
    /**
     * @Description: 使用继承thread类 实现线程。重写run方法,在主函数用start调用
     * @Author: cckong
     * @Date: 2021/2/2
     */
    public class Test01 extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                System.out.println("run"+i);
            }
        }
    
        public static void main(String[] args) {
            Test01 test01=new Test01();
            test01.run();
            for (int i = 0; i < 20; i++) {
                System.out.println("main"+i);
            }
        }
    }

     我们可以看到run方法是执行完在执行其他的线程。

    我们再看一下start方法

    package com.Thread;
    
    /**
     * @Description: 使用继承thread类 实现线程。重写run方法,在主函数用start调用
     * @Author: cckong
     * @Date: 2021/2/2
     */
    public class Test01 extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 17; i++) {
                System.out.print("   start"+i);
            }
        }
    
        public static void main(String[] args) {
            Test01 test01=new Test01();
            test01.start();
            System.out.println("
    ");
            for (int i = 0; i < 17; i++) {
                System.out.print("   main"+i);
            }
        }
    }

     可以看出是交替执行的。(由CPU调度安排)

     45.实现Runnable接口生成一个线程

    package com.Thread;
    
    /**
     * @Description: 实现Runnable接口 实现线程.先重写run方法,然后使用一个thread代理调用start方法
     * @Author: cckong
     * @Date: 2021/2/2
     */
    public class Test02 implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 17; i++) {
                System.out.print("   start"+i);
            }
        }
    
        public static void main(String[] args) {
            Test02 test02=new Test02();
            new Thread(test02).start();
            
            
            for (int i = 0; i < 17; i++) {
                System.out.print("   main"+i);
            }
        }
    }

     我们可以看见也需要重写run方法。唯一的不同是 需要new一个Thread来代理调用

    46.二者区别

    47.经典抢火车票问题

    /**
     * @Description: 经典抢火车票例子
     * @Author: cckong
     * @Date: 2021/2/2
     */
    public class Test03 implements Runnable{
        private int ticnum=10;
    
        @Override
        public void run() {
            while (true) {
                if(ticnum<=0) break;
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "抢到了" + ticnum);
                ticnum--;
            }
        }
    
        public static void main(String[] args) {
            Test03 test03=new Test03();
    
            new Thread(test03,"黄牛").start();
            new Thread(test03,"小明").start();
            new Thread(test03,"小红").start();
            new Thread(test03,"老师").start();
    
    
        }
    }

     出现了很多并发的问题需要解决。

    我们现在ticnum上加了 volatile关键字 其作用是 变量的更改对外可见

    但还是无法保证安全性

    48.经典龟兔赛跑问题

    /**
     * @Description: 龟兔赛跑案例
     * @Author: cckong
     * @Date: 2021/2/2
     */
    public class Test04 implements Runnable{
        private static String winner;//赢家的名字
        @Override
        public void run() {
            String user=Thread.currentThread().getName();
            int step=0;//记录当前步数
            for (int i = 0; i < 101; i++) {
    
                //这里是对于兔子树下睡觉的形象化 当线程用户是兔子时 十步歇一会
                if (user.equals("兔子") && step % 10 == 0) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                step++;//步数+1
                System.out.println(user+"已经跑了"+step+"步");
    
                if(win(step)) break;//当已经有了赢家了或者自己赢了 就停止循环
            }
    
        }
        //判断是否结束游戏了(已经存在赢家 自己赢了)
        public boolean win(int step)
        {
            if(winner!=null) return true;
            if(step>=100){
                winner=Thread.currentThread().getName();
    
                System.out.println(winner+"赢了");
                return true;
    
            }
            return false;
        }
    
        public static void main(String[] args) {
            Test04 test04=new Test04();
    
            new Thread(test04,"兔子").start();
            new Thread(test04,"乌龟").start();
    //
    //        System.out.println(winner+"赢了");
        }
    }

    49.实现callbale接口 建立线程

    /**
     * @Description: callable接口
     * @Author: cckong
     * @Date: 2021/2/3
     */
    public class Test05 implements Callable<Boolean> {
        @Override
        public Boolean call() throws Exception {
            for (int i = 0; i < 17; i++) {
                System.out.print("   start"+i);
            }
            return true;//可以有返回值
        }
    
        public static void main(String[] args) {
            Test05 test05=new Test05();//创建一个线程
            ExecutorService executorService= Executors.newFixedThreadPool(1);//创建执行环境 并指定线程数
            Future<Boolean> r1=executorService.submit(test05);//提交线程

    System.out.println(" "); for (int i = 0; i < 17; i++) { System.out.print(" main"+i); }
    boolean rs1=false;//默认false try { rs1 = r1.get();//获得返回值 } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(rs1); executorService.shutdownNow();//关闭服务 } }

    50.模拟倒计时(Thread.sleep())

    51.线程礼让yield

    礼让是讲资源让出来 然后由cpu重新调度。不一定礼让就会换线程

    正在运行的线程由运行状态变为就绪状态。

    52.线程强制执行join(插队)

    主线程和thread刚开始是并发执行的。

    在i=200时 thread进行插队 只执行thread。

    thread执行之后在执行主线程。

    53.Thread.getState()获取当前状态

    54.setPriority()设置优先级

    当你设置最高优先级的话 也不一定调度 要看cpu的调度情况

    要求在1-10之间

    55.守护线程 daemon

    需要用到线程函数 setDaemon设置为true 默认是false是用户线程。

    main是用户线程 gc垃圾回收算法是守护线程

     

    在这个程序里 上帝作为守护线程先输出 当用户线程 你启动了之后 开始运行你

    当你运行结束。此时jvm里面只有一个守护线程上帝。

    上帝还要运行一段时间 因为jvm关闭需要时间。

    56.list是线程不安全的集合

     可以使用CopyOnWriteArrayList是JUC包下的安全集合

    57.死锁的条件

    58.线程池

  • 相关阅读:
    mysql 存储过程实例
    国际会议查询方式和相关会议
    用 WEKA 进行数据挖掘,第 1 部分: 简介和回归(转)
    java实现甘特图的2种方法:SwiftGantt和Jfree (转)
    通过jxl 读取excel 文件中的日期,并计算时间间隔
    R 操作矩阵和计算SVD的基本操作记录
    SVD java 算法实现
    聚类方法简介
    Kolmogorov-Smirnov检验
    Java Thread 多线程 介绍
  • 原文地址:https://www.cnblogs.com/cckong/p/14260544.html
Copyright © 2011-2022 走看看