java多线程 笔记<创建><状态><同步><守护><单例>

本文介绍了Java多线程的相关知识,包括线程的创建方式(继承Thread类和实现Runnable接口)、线程的五种状态、线程安全与不安全的概念、线程同步的七种方法(同步方法、同步代码块、volatile、ReentrantLock、ThreadLocal、阻塞队列和原子变量),以及守护线程的特性和单例模式的五种实现方式。
摘要由CSDN通过智能技术生成

目录

一、线程与多线程

二、java创建线程的方式

 三、线程状态

四、线程安全与线程不安全

 五、线程同步七种方式

六、守护线程

七、五种单例模式


 

一、线程与多线程

1、什么是线程?

        线程是操作系统中能够进行运算调度的最小单位,它被包含在进程之中,是进程中实际的运作单位,一个java程序最少就有两个线程在同时执行:main和GC

 2.什么是多线程?

        在程序中不同的线程完不同的任务,称为多线程

3.程序、进程与线程的关系

程序与进程:

        ①一个程序运行产生一个或多个进程

        ② 程序是静态指令集合,进程是动态的,进程的生命周期是程序代码完整的执行过程

进程与线程:

        ①一个进程可以有多个线程,进程分配CPU,内存等等系统资源

        ② 线程是进程的子集,使用资源执行任务

        ③不同的进程使用不同的内存空间,同一个进程中多个线程共享一片内存空间

二、java创建线程的方式

        1、继承Thread类 

                Thread th = new Thread();

                th.start();

        2.实现Runnable接口

                Thread th = new Thread(new Runnable());

                th.start();

        注意:

                1、Thread 类继承了Runnable接口,Thread 有start() 方法和 run()方法

                2、Runnable 接口只有run() 方法,实现接口需要借用 Thread类构造

                3、当需要多继承 其他类的时候选用Runnable 接口

        问题:调用start() 方法会执行run方法,为什么不直接执行run方法?

                线程调用start() 方法后进入就绪状态,执行线程准备工作,分配到时间片后才能运行

                而直接执行run方法会把run方法当成main线程下的普通方法执行,并不是多线程工作

                

 三、线程状态

        1、New(初始化状态):线程被创建

        2、Runnable(可运行状态)

                1)Runnable_READY(就绪状态),线程创建后调用start()方法,线程处于就绪状态,线程获得可运行的资格,等待被调配

               2) Runnable_RUNNING(运行状态):线程运行中

        3、BLOCKED(阻塞状态):只有一种情况会导致线程阻塞,synchronized关键字修饰的方法或代码块

        4、WAITING(无时间限制等待状态):

                1)调用无参的Object.wait()方法,直到调用notify()或notifyAll()显示唤醒,回到可运行状态

                2)调用Thread.join()方法,例如创建线程A,调用A.join(), 那么此时主线程就是等待状态

        5、TIMED_WAITING(有时间限制等待状态)
                处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。 

                1)Object.wait(long timeout)

                2)  Thread.join(long millis)

                3)  Thread.sleep(long millis)

        6. TERMINATED(终止状态)
                当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。
在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

四、线程安全与线程不安全

        导致线程不安全的原因

  • 原子性:一个或者多个操作在 CPU 执行的过程中被中断
  • 可见性:一个线程对共享变量的修改,另外一个线程不能立刻看到
  • 有序性:程序执行的顺序没有按照代码的先后顺序执行

 五、线程同步七种方式

        使用线程同步原因
            java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突

        因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性

1、同步方法
    即有synchronized关键字修饰的方法。

        public synchronized void save(){}

注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

2、同步代码块
    即有synchronized关键字修饰的语句块。
    被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

         synchronized(object){
    }

    注:同步是一种高开销的操作,因此应该尽量减少同步的内容。
    通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可

3、使用特殊域变量(volatile)实现线程同步

    a.volatile关键字为域变量的访问提供了一种免锁机制,
    b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,
    c.因此每次使用该域就要重新计算,而不是使用寄存器中的值
    d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量

   private volatile int account = 100;

  注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。
    用final域,有锁保护的域和volatile域可以避免非同步的问题。

4、使用重入锁 ReentrantLock类来支持同步。
    ReentrantLock类是可重入、互斥、实现了Lock接口的锁,
    它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力
    ReenreantLock类的常用方法有:

        ReentrantLock() : 创建一个ReentrantLock实例
        lock() : 获得锁
        unlock() : 释放锁

class Bank {
            
            private int account = 100;
            //需要声明这个锁
            private Lock lock = new ReentrantLock();
            public int getAccount() {
                return account;
            }
            //这里不再需要synchronized 
            public void save(int money) {
                lock.lock();
                try{
                    account += money;
                }finally{
                    lock.unlock();
                }
                
            }
        }


    注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用

5、使用局部变量ThreadLocal实现线程同步

        使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响

    ThreadLocal 类的常用方法

    ThreadLocal() : 创建一个线程本地变量
    get() : 返回此线程局部变量的当前线程副本中的值
    initialValue() : 返回此线程局部变量的当前线程的"初始值"
    set(T value) : 将此线程局部变量的当前线程副本中的值设置为value

public class Bank{
            //使用ThreadLocal类管理共享变量account
            private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                @Override
                protected Integer initialValue(){
                    return 100;
                }
            };
            public void save(int money){
                account.set(account.get()+money);
            }
            public int getAccount(){
                return account.get();
            }
        }

6、使用阻塞队列LinkedBlockingQueue<E>

         实际开发当中,应当尽量远离底层结构。 
    使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。 
    本小节主要是使用LinkedBlockingQueue<E>来实现线程的同步 
    LinkedBlockingQueue<E>是一个基于已连接节点的,范围任意的blocking queue。 
    队列是先进先出的顺序(FIFO),关于队列以后会详细讲解~ 
   LinkedBlockingQueue 类常用方法 
    LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue 
    put(E e) : 在队尾添加一个元素,如果队列满则阻塞 
    size() : 返回队列中的元素个数 
    take() : 移除并返回队头元素,如果队列空则阻塞 

7、使用原子变量实现线程同步

        需要使用线程同步的根本原因在于对普通变量的操作不是原子的。
问题:什么是原子操作呢?
原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作
即-这几种行为要么同时完成,要么都不完成。
        在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类
使用该类可以简化线程同步。
其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。

public class BlockingSynchronizedThread {
13     /**
14      * 定义一个阻塞队列用来存储生产出来的商品
15      */
16     private LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
17     /**
18      * 定义生产商品个数
19      */
20     private static final int size = 10;
21     /**
22      * 定义启动线程的标志,为0时,启动生产商品的线程;为1时,启动消费商品的线程
23      */
24     private int flag = 0;
25 
26     private class LinkBlockThread implements Runnable {
27         @Override
28         public void run() {
29             int new_flag = flag++;
30             System.out.println("启动线程 " + new_flag);
31             if (new_flag == 0) {
32                 for (int i = 0; i < size; i++) {
33                     int b = new Random().nextInt(255);
34                     System.out.println("生产商品:" + b + "号");
35                     try {
36                         queue.put(b);
37                     } catch (InterruptedException e) {
38                         // TODO Auto-generated catch block
39                         e.printStackTrace();
40                     }
41                     System.out.println("仓库中还有商品:" + queue.size() + "个");
42                     try {
43                         Thread.sleep(100);
44                     } catch (InterruptedException e) {
45                         // TODO Auto-generated catch block
46                         e.printStackTrace();
47                     }
48                 }
49             } else {
50                 for (int i = 0; i < size / 2; i++) {
51                     try {
52                         int n = queue.take();
53                         System.out.println("消费者买去了" + n + "号商品");
54                     } catch (InterruptedException e) {
55                         // TODO Auto-generated catch block
56                         e.printStackTrace();
57                     }
58                     System.out.println("仓库中还有商品:" + queue.size() + "个");
59                     try {
60                         Thread.sleep(100);
61                     } catch (Exception e) {
62                         // TODO: handle exception
63                     }
64                 }
65             }
66         }
67     }
68 
69     public static void main(String[] args) {
70         BlockingSynchronizedThread bst = new BlockingSynchronizedThread();
71         LinkBlockThread lbt = bst.new LinkBlockThread();
72         Thread thread1 = new Thread(lbt);
73         Thread thread2 = new Thread(lbt);
74         thread1.start();
75         thread2.start();
76 
77     }
78 
79 }


AtomicInteger类常用方法:
AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicInteger
addAddGet(int dalta) : 以原子方式将给定值与当前值相加
get() : 获取当前值

class Bank {
 2         private AtomicInteger account = new AtomicInteger(100);
 3 
 4         public AtomicInteger getAccount() {
 5             return account;
 6         }
 7 
 8         public void save(int money) {
 9             account.addAndGet(money);
10         }
11     } 

六、守护线程

        1、什么是守护线程?

        在java中线程分两种

                1)守护线程,如:垃圾回收线程

                2)用户线程,应用程序中的自定义线程

        区别:

                1)main 方法中启动用户线程:

                主线程执行完毕,用户线程没有执行完毕JVM无法退出,直到用户线程执行完毕

               2) main 方法中启动守护线程

                主线程执行完毕,用户线程没有执行完毕JVM正常退出

                3)守护线程具有自动结束生命周期的特性,用户线程没有

        在start() 方法前 线程调用setDaemon(true)方法,表示一个守护线程

七、五种单例模式

                一个类始终只有一个对象,这种模式称为单例模式

        优点:不需要反复创建对象,回收对象,节约内存空间,调用执行效率

        缺点:并发操作导致线程不安全

1、懒汉模式

public class SingleOne{

    private static SingleOne single;

    private SingleOne(){};

    public static syschronized SingleOne getInstance(){
        
        if(single == null){
            single = new SingleOne ();
        }
        return single;
    }
}

2、饿汉模式

public class SingleTwo{
    private static SingleTwo single= new SingleTwo();

    private SingleTwo(){};

    public static SingleTwo getInstance(){
        return single;
    }
}

3、枚举模式

public enum SingleThree{
    INSTANCE;
}

4、静态内部类模式

  

public class SingleFour{

    private SingleFour(){};

    private static class getSingle(){
        private static final SingleFour SINGLE = new SingleFour();
    }

    public static SingleFour getInstance(){
        return getSingle.SINGLE;
    }
}

5、双重检查模式

public class SingleFive{

    private static volatile SingleFive single;

    private SingleFive(){};

    public static SingleFive getInstance(){
    //多线程进入 th1 th2 th3   如果 th2 发现th1 被实例化,th3 不再等待
        if(single == null){
        //如果对象为空锁住代码块 th1 进入,th2、th3 等待
            syschronized (SingleFive.class){
            //双重检查 防止多次实例化
                if(single == null){
                //th1实例化
                    single = new SingleFive();
                }
            }
        }
        return single;
    }
}

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值