zgq105 / blog

2 stars 0 forks source link

java多线程总结篇 #71

Open zgq105 opened 5 years ago

zgq105 commented 5 years ago

image

1. 什么是多线程?作用?

线程是操作系统能够进行运算调度的最小单位。通常一个进程包含一个或者多个线程;多线程指的是多个线程同时进行工作;多线程可以提高运行效率,比如,多线程下载,多线程处理耗时任务。

2. 创建线程的方式

2.1 Thread

//创建线程

public class Thread1 extends Thread{
    @Override
    public void run() {
        super.run();

        while (true){
            System.out.println(this.getName());
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//启动线程

Thread1 thread1=new Thread1();
thread1.start();

2.2 Runnable

//创建

public class Thread2 implements Runnable{

    public void run() {
        System.out.println("runing");
    }
}

//启动

 Thread2 thread2=new Thread2();
  new Thread(thread2).start();

2.3 juc

juc(java.util.concurrent)是jdk提供的多线程开发包。包括线程池、原子类、线程同步、锁等。 //创建线程方式一

ExecutorService executorService= Executors.newFixedThreadPool(1);
executorService.execute(new Runnable() {
            public void run() {
                System.out.println("hhhh");
            }
        });

//创建线程方式一

ExecutorService executorService= Executors.newFixedThreadPool(1);
executorService.submit(new Callable<String>() {

            public String call() throws Exception {
                System.out.println("gggg");
                return null;
            }
        });

注意Runnable和Callable的区别:

注:ReentrantLock和ReadWriteLock默认都是非公平锁,非公平锁的效率比较高。

//公平锁实现

public class LockService {

    private ReentrantLock reentrantLock;

    public LockService(boolean fair) {
        reentrantLock = new ReentrantLock(fair);
    }

    public void test() {
        reentrantLock.lock();
        System.out.println("获取锁的线程"+Thread.currentThread().getName());
        reentrantLock.unlock();
    }
}
 public static void main(String[]args){
        final LockService lockService=new LockService(true);

        ExecutorService executorService=   Executors.newFixedThreadPool(5);
        for (int i=0;i<5;i++){
            executorService.execute(new Runnable() {
                public void run() {
                    System.out.println("当前线程"+Thread.currentThread().getName());
                    lockService.test();
                }
            });
        }

    }

//非公平锁

public static void main(String[]args){
        final LockService lockService=new LockService(false);

        ExecutorService executorService=   Executors.newFixedThreadPool(5);
        for (int i=0;i<5;i++){
            executorService.execute(new Runnable() {
                public void run() {
                    System.out.println("当前线程"+Thread.currentThread().getName());
                    lockService.test();
                }
            });
        }

    }

注:公平锁执行结果按线程的先后顺序;而非公平锁执行结果顺序不确定。

4.2 可重入锁

可重入锁指可重复、可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁;java中ReentrantLock、synchronized是可重入锁。

4.3 独享锁/共享锁

避免死锁的方式: 1.指定加锁的顺序;

  1. 开放调用;只锁定需要同步操作的区域。
  2. 使用定时锁;使用Lock#tryLock获取锁,超时之后返回错误,不会一直等待下去。
  3. 死锁检测;使用工具JConsole和Jstack来检查死锁问题。

    5. 线程同步

    5.1 synchronized关键字

    synchronized是线程同步的关键字可以修饰同步方法、同步代码块。synchronized可以锁定类或者锁定对象,在锁定对象时,一定要注意锁定的是同一个对象。 //锁定方法

    private static int sum = 0;
    
    public synchronized void add() {
        sum++;
        System.out.println(sum);
    }

    //锁定代码块

    private static int sum = 0;
    
    public  void add() {
        synchronized (this){
            sum++;
            System.out.println(sum);
        }
    
    }

    5.2 volatile

    volatile是一个线程同步中一个关键字,有以下几个特点:

    • 保证线程的可见性。
    • 不能保证原子性。
    • 可以防止指令重排。(保证部分有序性) //使用举例:
      
      class Singleton{
      private volatile static Singleton instance = null;

    private Singleton() {

    }

    public static Singleton getInstance() { if(instance==null) { synchronized (Singleton.class) { if(instance==null) instance = new Singleton(); } } return instance; } }

    
    知识点:
  4. 原子性操作:只有简单的读取、赋值操作成为原子性操作。
  5. 指令重排:指JVM在编译时,CPU和编译器为了提升程序执行的效率,会按照一定的规则允许进行指令优化。

    5.3 Lock

    Lock是juc包中锁机制主要包含ReentrantLock和ReentrantReadWriteLock锁,以ReentrantLock为例,实现如下:

    
    public class SynchronizedTest {
    
    private static int sum = 0;
    
    private ReentrantLock mReentrantLock;
    
    public SynchronizedTest() {
        this.mReentrantLock = new ReentrantLock();
    }
    
    public void add() {
        try {
            mReentrantLock.lock();
            sum++;
            System.out.println(sum);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            mReentrantLock.unlock();
        }
    
    }
    
    public static void main(String[] args) {
        final SynchronizedTest synchronizedTest = new SynchronizedTest();
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            executorService.execute(new Runnable() {
                public void run() {
                    synchronizedTest.add();
                }
            });
        }
    
    }
    }
## 5.4 ThreadLocal
ThreadLocal从字面上理解是就是本地线程的意思,其主要作用就是给每个线程存储线程自己的变量,其他线程是不能访问的;ThreadLocal对象本身是所有线程共享的,其内部存储的数据是线程自己的,不同的线程通过ThreadLocal存储自己的变量。具体举例如下:

public static void main(String[] args) {

    final ThreadLocal<String> threadLocal = new ThreadLocal<String>();
    threadLocal.set("zgq");
    Executors.newFixedThreadPool(1).execute(new Runnable() {
        public void run() {
            threadLocal.set("zgq2");
            System.out.println(threadLocal.get());
        }
    });
    Executors.newFixedThreadPool(1).execute(new Runnable() {
        public void run() {
            threadLocal.set("zgq3");
            System.out.println(threadLocal.get());
        }
    });
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(threadLocal.get());

}
//输出结果

zgq2 zgq3 zgq

注:从输出结果来看,ThreadLocal对象本身是多线程共享的;但是内部存储的值是线程独有的,互不影响。
## 5.5 阻塞队列(BlockingQueue)
阻塞队列是一种为多线程环境而设计的一种队列,和一般的队列的区别主要有以下几点:
1. 阻塞队列支持多线程环境,多个线程可以安全访问。
2. 支持生产和消费等待,多个线程之间互相配合,当队列为空的时候,消费线程会阻塞等待队列不为空;当队列满了的时候,生产线程就会阻塞直到队列不满。

阻塞队列主要包括ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, LinkedTransferQueue, PriorityBlockingQueue, SynchronousQueue。以属猪类型的阻塞队列ArrayBlockingQueue为例,使用过程如下:

public static void main(String[] args) {

    final ArrayBlockingQueue<String> arrayBlockingQueue=new ArrayBlockingQueue<String>(3);

    ExecutorService executorService=Executors.newFixedThreadPool(4);
    for (int i=0;i<4;i++){
        executorService.execute(new Runnable() {
            public void run() {
                try {
                    arrayBlockingQueue.put("output:"+new Date());
                    System.out.println("add:"+new Date());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    try {
        Thread.sleep(2000);
        System.out.println("队列大小:"+arrayBlockingQueue.size());
        arrayBlockingQueue.remove();
        System.out.println("队列大小:"+arrayBlockingQueue.size());
        Thread.sleep(2000);
        System.out.println("队列大小:"+arrayBlockingQueue.size());

    } catch (InterruptedException e) {
        e.printStackTrace();
    }

}
//输出结果

add:Sun Jun 23 19:39:19 CST 2019 add:Sun Jun 23 19:39:19 CST 2019 add:Sun Jun 23 19:39:19 CST 2019 队列大小:3 队列大小:2 add:Sun Jun 23 19:39:21 CST 2019 队列大小:3

注:从输出的结果来看,ArrayBlockingQueue是一个使用多线程环境的阻塞队列;调用ArrayBlockingQueue#put添加元素时,当队列满时,线程就会进入阻塞状态;反之,直接加入到队列中。当阻塞队列中元素变动时,如果容量有剩余时,被阻塞的队列就会随后添加到队列中。(阻塞队列内部是通过ReentrantLock来实现多线程安全的)

## 5.6 atomic
atomic是指java.util.concurrent.atomic中包中提供的一系列原子类的API;像AtomicInteger、AtomicIntegerArray、AtomicLong、AtomicReference等,这里以AtomicInteger实现一个线程安全的例子,代码如下:

public static void main(String[] args) { final AtomicInteger atomicInteger=new AtomicInteger(); ExecutorService executorService= Executors.newFixedThreadPool(4); for (int i=0;i<4;i++){ executorService.execute(new Runnable() { public void run() { atomicInteger.getAndIncrement(); } }); } try { Thread.sleep(1000); System.out.println(atomicInteger.get()); } catch (InterruptedException e) { e.printStackTrace(); }

}

注:原子类都是通过CAS+volatile实现线程安全,是属于乐观锁。
# 6. sleep和wait的区别
sleep是线程Thread类中的方法,只是暂停当前线程,并不会释放对象锁;而wait是Object类提供的方法,可以将当前线程挂起,释放对象锁,让其他线程能获取到锁并操作同步数据。

# 7. 线程池
线程池是为了提升性能,避免每次创建线程带来的系统开销;预先创建好线程放入池子中,使用线程的时候直接从池子中获取线程对象即可。
使用线程的优点:
1. 降低系统的开销。
2. 提升响应速度。
3. 提高线程的可管理性。

在java中,juc中提供了丰富的API来创建和管理线程池。主要有以下几种方式来创建线程池:
1.  Executors#newCachedThreadPool(),表示可以无限扩大的线程池,适用于处理**执行时间比较短**的任务。
2. Executors#newFixedThreadPool,表示固定大小的线程池,适用于已知并发数的情形。
3. Executors#newScheduledThreadPool,表示调度线程池,适用于需要多个后台线程执行**周期任务**的场景。
4. Executors#newSingleThreadExecutor,表示创建单个线程的线程池,用于需要保证**顺序执行**的场景,并且只有一个线程在执行。
5. Executors#newWorkStealingPool,表示一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来**并行**执行。
6. ThreadPoolExecutor,表示自定义线程池,可以根据自身需求制定线程池配置。

# 8. 如何检查对象是否加锁?
使用 Thread.holdsLock()可以判断当前线程是否加锁于某个对象。
# 9. ReadWriteLock和ReentrantLock的区别?
ReadWriteLock表示读写锁,读锁是共享的,而写锁是互斥的;而ReentrantLock不管读写都是互斥锁,属于悲观锁。
# 10. 什么是CAS
CAS(Compare and Swap),即比较并替换,实现并发算法时常用到的一种技术,属于乐观锁。
CAS的思想:三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。(原子类中就是采用这种算法实现线程同步的)
# 11. 什么是AQS
AbstractQueuedSynchronized(AQS),翻译过来就是抽象队列同步器,AQS定义了一套多线程访问共享资源的同步器框架。
参考:https://www.jianshu.com/p/279baac48960
# 12. 信号量(Semaphore)

# 13. 栅栏(CyclicBarrier)

# 14. 闭锁(CountDownLatch)