前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JUC 多线程基础

JUC 多线程基础

作者头像
万能青年
发布2019-08-30 14:41:46
4890
发布2019-08-30 14:41:46
举报

一、内存可见性

如果一个线程对共享变量的修改,能够被其它线程看到,那么就能说明共享变量在线程之间是可见的。如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。

线程对共享变量的所有操作都必须在自己的工作内存中进行,不能从主内存中读写;而且不同线程之间无法直接访问其它线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成,所以不加任何修饰的,线程之间内存是不可见的。

二、volatile 关键字

详细介绍看此处 万能青年,公众号:JavaArtisanJUC 多线程之 volatile 关键字

三、原子变量

因为volatile关键字不能保证原子性操作,所以需要java.util.concurrent.atomic包下的原子变量来进行修改,底层使用CAS算法保证数据的原子性。

四、CAS算法

  1. CAS算法是硬件对于并发操作共享数据的支持,用于管理对共享数据的并发访问。
  2. 包含三个操作数:V 内存值、 A 预估值、 B 更新值
  3. 当且仅当V == A时,才执行 V = B,否则不作任何操作
  4. CAS算法比锁的效率要高,是一种无锁,非阻塞算法实现

五、同步容器类ConcurrentHashMap

在map集合中,HashMap效率高,但是不能保证线程安全,HashTable可以保证线程安全,但是效率非常低,在需要保证线程安全的情况下,也不使用HashTable,因为效率实在是太低,所以使用ConcurrentHashMap。

六、闭锁CountDownLatch

在完成某些运算时,只有当所有线程的运算全都完成后,才继续运行的这种情况下,需要用到闭锁。

七、创建线程的四种方式

1、继承Thread类

代码语言:javascript
复制
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("继承Thread类");
    }
}

public class Test {
    public static void main(String args[]) {
        //启动继承Thread类创建的线程对象
        MyThread t1 = new MyThread();
        t1.start(); 
    }

2、实现Runnable接口

代码语言:javascript
复制
class RunnableDemo implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++){
            System.out.println(i);
        }
    }
}
public class TestRunable {
    public static void main(String[] args) {
        RunnableDemo runnableDemo = new RunnableDemo();
        new Thread(runnableDemo).start();
    }
}

3、实现Callable接口

  1. 需要实现Callable接口
  2. 相较于Runnable,方法有返回值,并且可以抛出异常。
  3. 需要FutureTask实现类接收运算结果
代码语言:javascript
复制
public class TestCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadDemo threadDemo = new ThreadDemo();
        //执行callable方式,需要FuturnTask实现类的支持,用于接收运算结果
        FutureTask<Integer> futureTask = new FutureTask<>(threadDemo);
        //启动线程
        new Thread(futureTask).start();
        //接收运算结果
        Integer sum = futureTask.get();
        System.out.println(sum);
    }

}
class ThreadDemo implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++){
            sum += i;
        }
        return sum;
    }
}

4、线程池创建线程

八、同步锁的使用

同步锁是一个显示锁,需要lock()方法上锁,unlock()释放锁

代码语言:javascript
复制
public class TestLock {
    public static void main(String[] args) {
        LockDemo lockDemo = new LockDemo();

        new Thread(lockDemo, "1号").start();
        new Thread(lockDemo, "2号").start();
        new Thread(lockDemo, "3号").start();
    }
}

class LockDemo implements Runnable{
    private  int ticket = 100;
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            //上锁
            lock.lock();
            try {
                if (ticket > 0){
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "完成售票,余票为" + --ticket);
                }
            }finally {
                //释放锁
                lock.unlock();
            }

        }
    }
}

九、生产者消费者问题解决

代码语言:javascript
复制
package com.artisan.juc;

/**
 * @author wannengqingnian
 */
public class ProAndCus {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Productor productor = new Productor(clerk);
        Consumer consumer = new Consumer(clerk);

        new Thread(productor, "生产者A1").start();
        new Thread(consumer, "消费者B1").start();
        new Thread(productor, "生产者A2").start();
        new Thread(consumer, "消费者B2").start();
    }

}

/**
 * 店员
 */
class Clerk{

    private int product = 0;
    

    public synchronized void get() {
        while (product >= 144){
            System.out.println("产品已满!");

            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        //进货
        System.out.println(Thread.currentThread().getName() + " : " + ++product);
        this.notifyAll();

    }

    public synchronized void sales(){
        while (product <= 0){
            System.out.println("产品缺货!");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //卖货
        System.out.println(Thread.currentThread().getName() + " : " + --product);
        this.notifyAll();
    }
}


/**
 * 生产者
 */
class Productor implements Runnable{

    private Clerk clerk;

    public Productor(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++){
            clerk.get();
        }
    }
}

/**
 * 消费者
 */
class Consumer implements Runnable{
    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++){
            clerk.sales();
        }
    }
}

十、使用Lock完成生产者消费者问题

代码语言:javascript
复制
package com.artisan.juc;
import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author wannengqingnian
 */
public class ProAndCus {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Productor productor = new Productor(clerk);
        Consumer consumer = new Consumer(clerk);

        new Thread(productor, "生产者A1").start();
        new Thread(consumer, "消费者B1").start();
        new Thread(productor, "生产者A2").start();
        new Thread(consumer, "消费者B2").start();
    }

}

/**
 * 店员
 */
class Clerk{

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private int product = 0;

    public void get() {
        lock.lock();
        try {
            while (product >= 1){
                System.out.println("产品已满!");

                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //进货
            System.out.println(Thread.currentThread().getName() + " : " + ++product);
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }

    public void sales(){
        lock.lock();
        try {
            while (product <= 0){
                System.out.println("产品缺货!");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //卖货
            System.out.println(Thread.currentThread().getName() + " : " + --product);
            condition.signalAll();
        }finally {
            lock.unlock();
        }

    }
}


/**
 * 生产者
 */
class Productor implements Runnable{

    private Clerk clerk;

    public Productor(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++){
            clerk.get();
        }
    }
}

/**
 * 消费者
 */
class Consumer implements Runnable{
    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++){
            clerk.sales();
        }
    }
}

十一、读写锁

为什么需要读写锁?

读读不互斥,读写互斥,写写互斥,而一般的独占锁是:读读互斥,读写互斥,写写互斥,而场景中往往读远远大于写,读写锁就是为了这种优化而创建出来的一种机制。

Synchronized存在明显的一个性能问题就是读与读之间互斥,读写锁可以解决这个问题。

代码演示:

代码语言:javascript
复制
package com.artisan.juc;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁
 *
 * 创建一个写线程
 * 20个读线程
 * @author wannengqingnian
 */
public class TestReadWriteLock {
    public static void main(String[] args) {
        ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();



        new Thread(new Runnable() {

            @Override
            public void run() {
                readWriteLockDemo.set((int) (Math.random() * 101));
            }
        }, "写线程").start();

        for (int i = 1; i <= 20; i++){
            new Thread(new Runnable() {

                @Override
                public void run() {
                    readWriteLockDemo.get();
                }
            }, "读线程 " + i).start();
        }

    }
}

class ReadWriteLockDemo {

    private int number = 0;

    /**
     * 创建读写锁
     */
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    public void get(){
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " : " + number);
        }finally {
            lock.readLock().unlock();
        }
    }

    public void set(int number){
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName());
            this.number = number;
        }finally {
            lock.writeLock().unlock();
        }
    }
}

十二、线程八锁

代码演示:

代码语言:javascript
复制
/*
 * 题目:判断打印的 "one" or "two" ?
 * 
 * 1. 两个普通同步方法,两个线程,标准打印, 打印? //one  two
 * 2. 新增 Thread.sleep() 给 getOne() ,打印? //one  two
 * 3. 新增普通方法 getThree() , 打印? //three  one   two
 * 4. 两个普通同步方法,两个 Number 对象,打印?  //two  one
 * 5. 修改 getOne() 为静态同步方法,打印?  //two   one
 * 6. 修改两个方法均为静态同步方法,一个 Number 对象?  //one   two
 * 7. 一个静态同步方法,一个非静态同步方法,两个 Number 对象?  //two  one
 * 8. 两个静态同步方法,两个 Number 对象?   //one  two
 * 
 * 线程八锁的关键:
 * ①非静态方法的锁默认为  this,  静态方法的锁为 对应的 Class 实例
 * ②某一个时刻内,只能有一个线程持有锁,无论几个方法。
 */
public class TestThread8Monitor {

    public static void main(String[] args) {
        Number number = new Number();
        Number number2 = new Number();

        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            } 
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
//              number.getTwo();
                number2.getTwo();
            }
        }).start();

        /*new Thread(new Runnable() {
            @Override
            public void run() {
                number.getThree();
            }
        }).start();*/

    }

}

class Number{

    public static synchronized void getOne(){//Number.class
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }

        System.out.println("one");
    }

    public synchronized void getTwo(){//this
        System.out.println("two");
    }

    public void getThree(){
        System.out.println("three");
    }
}

总结:

1、一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其他的线程都只能等待,换句话说,某一时刻内,只能有唯一一个线程去访问这些synchronized方法。

2、所有的非静态同步方法用的都是同一把锁 -- 实例对象本身,也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已经取锁的非静态同步方法释放锁就可以获取他们自己的锁。

3、所有的静态同步方法用的也是同一把锁 -- 类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间不会有竞争条件。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们是同一个实例对象。

原文链接:https://blog.csdn.net/kfengqingyangk/article/details/69485858

十三、线程池

1、为什么使用线程池?

需要并发线程数量很多,可以避免新创建与销毁线程产生额外的开销,提高响应速度,使线程可以复用。线程池提供了一个线程队列,队列中保存着所有等待状态的线程。

2、线程池的体系结构

Java.util.concurrent.Executor : 负责线程的使用与调度的根接口

|-- ExecutorService 子接口:线程池的主要接口

|--ThreadPoolExecutor :线程池的实现类

|--ScheduledExecutorService 子接口 :负责线程的调度

|--ScheduledThreadPoolExecutor : 继承ThreadPoolExecutor,实现ScheduledExecutorService,具备两者的功能

3、工具类 : Executor

ExecutorService new FixedThreadPool() : 创建固定大小的线程池。

ExecutorService newCachedThreadPool() : 缓存线程池,线程池的线程数量不固定,可以根据需求自动更改数量。

ExecutorService newSingleThreadPool() : 创建单个线程的线程池。

ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程池,可以延时或定时执行任务。

4、使用线程池

runnable方法:

代码语言:javascript
复制
package com.artisan.juc;
import java.util.concurrent.Executors;

import java.util.concurrent.ExecutorService;

/**
 * @author wannengqingnian
 */
public class TestThreadPool {

    public static void main(String[] args) {
        TestRunnableDemo demo = new TestRunnableDemo();

        //1.创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(5);

        //2.为线程池中的线程分配任务
        for (int i = 0; i < 10; i++){
            pool.submit(demo);
        }

        //3.关闭线程
        pool.shutdown();
    }
}

class TestRunnableDemo implements Runnable{
    private int i = 0;
    @Override
    public void run() {
        while (i <= 100){
            System.out.println(Thread.currentThread().getName() + " : " + i++);
        }
    }
}

Callable方式:

代码语言:javascript
复制
package com.artisan.juc;
import java.util.ArrayList;
import  java.util.List;
import java.util.concurrent.*;
/**
 * @author wannengqingnian
 */
public class TestThreadPool {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallableDemo cdemo = new TestCallableDemo();
        List < Future < Integer > > futures = new ArrayList<>();

        //1.创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(5);

        //2.为线程池中的线程分配任务 Callable方式
        for (int i = 0 ; i < 10; i++){
            Future<Integer> future = pool.submit(cdemo);
            futures.add(future);
        }

        for (Future future : futures){
            System.out.println(future.get());
        }
        //3.关闭线程
        pool.shutdown();
    }
}
class TestCallableDemo implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++){
            sum += i;
        }
        return sum;
    }
}

十四、面试题

三个线程,三个线程ID分别是ABC,打印20遍,显示结果按顺序显示?

代码语言:javascript
复制
package com.artisan.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 三个线程,三个线程ID分别是ABC,打印20遍,显示结果按顺序显示
 * 如 ABCABC...
 * @author wannengqingnian
 */
public class PrintingInSequence {

    public static void main(String[] args) {
        PrintDemo printDemo = new PrintDemo();

        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 1; i <= 20; i++){
                    printDemo.loopA(i);
                }
            }
        }, "A").start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 1; i <= 20; i++){
                    printDemo.loopB(i);
                }
            }
        }, "B").start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 1; i <= 20; i++){
                    printDemo.loopC(i);
                }
            }
        }, "C").start();
    }

}

class PrintDemo{
    private int number = 1;
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    public void loopA(int total){
        lock.lock();
        try {

            //1.判断是不是到该自己执行,不到则等待
            if (number != 1){
                condition1.await();
            }

            //2.打印自己编号
            for (int i = 1 ; i <= 1; i++){
                System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + total);
            }

            //3.自己结束后,唤醒下一个线程
            number = 2;
            condition2.signal();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }

    public void loopB(int total){
        lock.lock();
        try {

            //1.判断是不是到该自己执行,不到则等待
            if (number != 2){
                condition2.await();
            }

            //2.打印自己编号
            for (int i = 1 ; i <= 1; i++){
                System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + total);
            }

            //3.自己结束后,唤醒下一个线程
            number = 3;
            condition3.signal();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
    public void loopC(int total){
        lock.lock();
        try {

            //1.判断是不是到该自己执行,不到则等待
            if (number != 3){
                condition3.await();
            }

            //2.打印自己编号
            for (int i = 1 ; i <= 1; i++){
                System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + total);
                System.out.println("==========================");
            }

            //3.自己结束后,唤醒下一个线程
            number = 1;
            condition1.signal();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 JavaArtisan 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 六、闭锁CountDownLatch
    • 为什么需要读写锁?
      • 1、为什么使用线程池?
        • 2、线程池的体系结构
          • 3、工具类 : Executor
          • 三个线程,三个线程ID分别是ABC,打印20遍,显示结果按顺序显示?
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档