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

    一、线程的基本概念

    1.程包括以下这几个状态:创建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)

    2.java中线程分为两种类型:用户线程和守护线程。

    如果JVM中所有的线程都是守护线程,那么JVM就会退出,守护线程也会退出。
    如果JVM中还存在用户线程,那么JVM就会一直存活,不会退出。

    由此可以得到:
    守护线程是依赖于用户线程,用户线程退出了,守护线程也就会退出,典型的守护线程如垃圾回收线程。
    用户线程是独立存在的,不会因为其他用户线程退出而退出。
    默认情况下启动的线程是用户线程,通过setDaemon(true)将线程设置成守护线程,这个函数务必在线程启动前进行调用,否则会报java.lang.IllegalThreadStateException异常,启动的线程无法变成守护线程,而是用户线程。 

    二、线程创建的方法

    1)继承Thread类;
    继承Thread类的话,必须重写run方法,在run方法中定义需要执行的任务。
    创建好了自己的线程类之后,就可以创建线程对象了,然后通过start()方法去启动线程。注意,不是调用run()方法启动线程,run方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务。

    class MyThread extends Thread{
        private static int num = 0;
        public MyThread(){
            num++;
        }
        @Override
        public void run() {
            System.out.println("主动创建的第"+num+"个线程");
        }
    }
    public class Test {
        public static void main(String[] args)  {
            MyThread thread = new MyThread();
            thread.start();
        }
    }


    创建好了自己的线程类之后,就可以创建线程对象了,然后通过start()方法去启动线程。注意,不是调用run()方法启动线程,run方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务。
    2)实现Runnable接口。
    在Java中创建线程除了继承Thread类之外,还可以通过实现Runnable接口来实现类似的功能。实现Runnable接口必须重写其run方法。

    public class Test {
        public static void main(String[] args)  {
            System.out.println("主线程ID:"+Thread.currentThread().getId());
            MyRunnable runnable = new MyRunnable();
            Thread thread = new Thread(runnable);
            thread.start();
        }
    }
    class MyRunnable implements Runnable{
        public MyRunnable() {
        }
        @Override
        public void run() {
            System.out.println("子线程ID:"+Thread.currentThread().getId());
        }
    }

    三、Thread类方法

        1)start方法
      start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。

     2)run方法
      run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。

     3)sleep方法
           sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。
    sleep(long millis)     //参数为毫秒
    sleep(long millis,int nanoseconds)    //第一参数为毫秒,第二个参数为纳秒
      但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。
        
        4)yield方法
            调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。
            但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
            注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。

        5)jion方法
            join方法有三个重载版本:

    join()
    join(long millis)     //参数为毫秒
    join(long millis,int nanoseconds)    //第一参数为毫秒,第二个参数为纳秒


           假如在main线程中,调用thread.join方法,则main方法会等待thread线程执行完毕或者等待一定的时间。如果调用的是无参join方法,则等待thread执行完毕,如果调用的是指定了时间参数的join方法,则等待一定的时间, 实际上调用join方法是调用了Object的wait方法。wait方法会让线程进入阻塞状态,并且会释放线程占有的锁,并交出CPU执行权限。由于wait方法会让线程释放对象锁,所以join方法同样会让线程释放对一个对象持有的锁。

     6)interrupt方法
            interrupt,顾名思义,即中断的意思。单独调用interrupt方法可以使得处于阻塞状态的线程抛出一个异常,也就说,它可以用来中断一个正处于阻塞状态的线程; 直接调用interrupt方法不能中断正在运行中的线程
            调用interrupt方法配合isInterrupted()能够中断正在运行的线程,因为调用interrupt方法相当于将中断标志位置为true,那么可以通过调用isInterrupted()判断中断标志是否被置位来中断线程的执行。
            一般情况下不建议通过这种方式来中断线程,一般会在MyThread类中增加一个属性 isStop来标志是否结束while循环,然后再在while循环中判断isStop的值。

    class MyThread extends Thread{
            private volatile boolean isStop = false;
            @Override
            public void run() {
                int i = 0;
                while(!isStop){
                    i++;
                }
            }
     
            public void setStop(boolean stop){
                this.isStop = stop;
            }
        }

    四、线程安全

    1.synchronized
            用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。

    class InsertData {
        private ArrayList<Integer> arrayList = new ArrayList<Integer>();
          
        public synchronized void insert(Thread thread){
            for(int i=0;i<5;i++){
                System.out.println(thread.getName()+"在插入数据"+i);
                arrayList.add(i);
            }
        }
    }
    class InsertData {
        private ArrayList<Integer> arrayList = new ArrayList<Integer>();
        public void insert(Thread thread){
            synchronized (this) {
                for(int i=0;i<100;i++){
                    System.out.println(thread.getName()+"在插入数据"+i);
                    arrayList.add(i);
                }
            }
        }
    }

      注意:
            1)当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法。这个原因很简单,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。
      2)当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。这个原因很简单,访问非synchronized方法不需要获得该对象的锁,假如一个方法没用synchronized关键字修饰,说明它不会使用到临界资源,那么其他线程是可以访问这个方法的,
      3)如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型),也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。
            4)如果一个线程执行一个对象的非static synchronized方法,另外一个线程需要执行这个对象所属类的static synchronized方法,此时不会发生互斥现象,因为访问static synchronized方法占用的是类锁,而访问非static synchronized方法占用的是对象锁,所以不存在互斥现象。
    2.Lock
           1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
      2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

    3. volatile
            a.一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
          1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
          2)禁止进行指令重排序。
            b. volatile不能保证原子性,比如i++操作不是原子性操作,所以用volatile修饰i,但不能保证结果正确。(解决方法,采用synchronized或者Lock)
            c. volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有序性。
               volatile关键字禁止指令重排序有两层意思:
              1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
              2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

    //x、y为非volatile变量
    //flag为volatile变量
    x = 2;        //语句1
    y = 0;        //语句2
    flag = true;  //语句3
    x = 4;         //语句4
    y = -1;       //语句5

    由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会将语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。
                    
    4.ThreadLocal,线程本地变量或者线程本地存储。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。
    5. 同步容器是线程安全的容器指的是 Vector、Stack、HashTable 以及Collections类中提供的静态工厂方法创建的类,但都不推荐使用。

  • 相关阅读:
    HDU 2236 无题Ⅱ
    Golden Tiger Claw(二分图)
    HDU 5969 最大的位或 (思维,贪心)
    HDU 3686 Traffic Real Time Query System (图论)
    SCOI 2016 萌萌哒
    Spring Boot支持控制台Banner定制
    构建第一个Spring Boot程序
    Spring Boot重要模块
    Java fastjson JSON和String互相转换
    BCompare 4 Windows激活方法【试用期30天重置】
  • 原文地址:https://www.cnblogs.com/aiguona/p/10634040.html
Copyright © 2011-2022 走看看