high-level——线程状态、并发包(9)

一、jdk中的线程安全与不安全的类型

  • StringBuffer 与 StringBuilder ,前者的大多数方法都带有同步关键字,后面的方法不带有同步关键字。前者是线程安全,执行效率低于后者的线程不安全。
  • ArrayList 是线程不安全的,Vector 是线程安全的。
  • HashMap 是非线程安全的;HashTable是线程安全的;ConcurrentHashMap<K,V>:支持检索的完全并发性和更新的高预期并发性(不保证绝对安全)的哈希表。
  • volatile 关键字,用来修饰成员变量,静态变量,可以避免变量的不可见性。用它来保证变量的可见性主要通过两种手段,第一,工作内存中的变量被修改会立刻写回主内存,如果语句中操作了 volatile 的变量,在指令重排的过程中,会插入内存屏障的指令,比如 loadload,loadstore,storestore,内存屏障可以保证有些指令不被重排。volatile 是解决线程安全的轻量级的手段,但不保证线程的安全性,只保证变量的可见性。

二、了解 jdk 中的并发包

  • 此包中的多数api都用来解决线程的并发问题
  1. 工具包下的 locks 子包

    • Interface Lock 接口表示锁,它的实现类都具有锁的功能。与同步关键字比起来,它提供了更大的灵活性。
    • Lock的实现类有 ReentrantLock ,ReentrantReadWriteLock ,它们的实现层次在 jdk,同步关键字的实现层次比较低,属于操作系统的层次的实现,执行的效果比较好。
    • ReentrantLock:一个可重入互斥Lock具有与使用synchronized方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。
      • 可重入:如果一个线程获取到某个方法的锁,那么再去获得此方法的锁或再去获得此方法所在对象的其他方法的锁的,具有优先权。在递归算法中体现的比较突出。同步关键字也具有可重入性和互斥性。
      • 扩展性:可指定锁是否公平;利用它可以显示的加锁( lock() )和释放锁( unlock() )。公平的情况下线程的执行机会相等。
    • ReentrantReadWriteLock :它具有可重入锁的特点,可提供读锁和写锁。读锁允许多个线程同时获取,因此读的情况下,可以提高线程的并发性,写锁还是只能允许一个线程获得锁。
    package com.zhong.test_4;
    
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class SynchronizedDemo{
        public static void main(String[] args) {
            Test test = new Test();
                for(int i = 0;i < 10;i++){
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            test.run();
                        }
                    }).start();
                }
                Runtime.getRuntime().addShutdownHook(new Thread(){
                    @Override
                    public void run() {
                        System.out.println("count = " + test.count);
                    }
                });
           }
    
       static class Test implements Runnable{
           ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
           ReentrantReadWriteLock.ReadLock rl = readWriteLock.readLock();
           ReentrantReadWriteLock.WriteLock wl = readWriteLock.writeLock();
           int count = 0;
           @Override
            public void run() {
                wl.lock();
                for(int i = 0;i < 100000;i++) {
                    count++;
                }
                wl.unlock();
    
            }
        }
    }
    
  2. AtomicInteger 原子类型的整数,像 long,Array,boolean 等都提供了这种类型,如果多个线程对象共享的数字变量进行单纯的加减运算,可以使用它实现同步操作。

三、线程的六种状态

  • 新建状态:创建了线程对象,未执行 start();
  • 可运行状态:执行了 start(),此时线程可以是正在运行(消耗CPU),也可以是处于阻塞或等待的情形(不消耗CPU)。
  • 阻塞状态:线程被同步,它在等待锁;线程正在休眠中,sleep();线程做出让步,yield();线程执行了加入,join()。
  • 线程等待状态:在线程间的共享的对象锁上执行 wait() 方法,线程就处于等待状态,它的执行时机决定于两种情况,一种是等待的时间结束,如果收到另一个线程的通知,也会结束;另外一种是无限等待,如果另一个线程对它发出通知,则停止等待。(两种状态:无限等待和计时等待)
  • 消亡状态:在线程上调用 stop() 方法(已弃用,本质上不安全),线程立即中止;线程已经执行完毕,run() 方法结束;在线程执行中遇到了未捕获的异常。
  • 注意:如果一个线程已被创建并执行完毕,该线程不允许再次进入可执行状态。否则会抛出 IllegalThreadStateException 异常,线程状态混乱异常。如果一个线程已进入可执行状态,不允许再次调用 start() 方法。

四、线程之间的通信

  • 设计线程有两个主要任务,一个是线程间的同步,另一个就是线程间的通讯。
  1. 实现线程间的通讯,就需要两个线程间能够互相发送消息,它与同步的机制是不一样的。同步不需要发消息,通过对象锁的变化来控制线程的执行。

  2. 实现线程通讯的方式

    • 通过对多个线程都可见的变量值来实现通讯。这种方式同时需要解决变量上的同步问题。
    • 通过创建线程间的管道(流对象)来实现通讯,但是管道是单向的,不能实现双向通讯。
    • 通过 Object 的 wait() 和 notify() 方法来实现线程间的通讯。这是最合适的方式。
    package com.zhong.test_4;
    
    public class MessageDemo {
        private static Object obj = new Object();
    
        public static void main(String[] args) {
            new Thread(() -> {
                synchronized (obj) {
                    for (int i = 1; i <= 50; i++) {
                        System.out.print("子线程" + i + ":");
                        for (int j = 1; j <= 10; j++) {
                            System.out.print(j + " ");
                        }
                        System.out.println();
                        obj.notify();
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
    
            synchronized (obj) {
                for (int i = 1; i <= 50; i++) {
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.print("主线程" + i + ":");
                    for (int j = 1; j <= 20; j++) {
                        System.out.print(j + " ");
                    }
                    System.out.println();
                    obj.notify();
                }
            }
        }
    }
    

五、ThreadLocal 类

  1. 如果多个线程需要使用同一个变量,那么该变量应该是一个共享变量。
  2. 如果是非 ThreadLocal 类的共享变量,任何一个线程对它的修改都会影响到其它线程中某些变量的值。
  3. 如果所有的线程需要得到的共享变量的值是同一个值,而且在线程内部还要对此变量进行修改,又不能影响到其他线程中该变量的值,此时就需要使用 ThreadLocal 类。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Æ_华韵流风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值