IT教程 ·

并发与多线程

深入了解MySQL,一篇简短的总结

并发与多线程

基本概念

并发与并行

  1. 并发:指两个或多个事宜在统一时候距离内发作 。当有多个线程在操纵时,假如体系只要一个CPU,则它基础不大概真正同时举行一个以上的线程,它只能把CPU运转时候划分红若干个时候段,再将时候 段分配给各个线程实行,在一个时候段的线程代码运转时,别的线程处于挂起状。这类体式格局称之为并发(Concurrent)
  2. 并行:指两个或许多个事宜在统一时候发作 。当体系有一个以上CPU时,则线程的操纵有大概非并发。当一个CPU实行一个线程时,另一个CPU可以实行另一个线程,两个线程互不抢占CPU资本,可以同时举行,这类体式格局称之为并行(Parallel)

历程与线程

  1. 一个程序大概有多个历程,一个历程由多个线程和同享资本构成
  2. 历程:具有资本的基本单位
  3. 线程:自力调理分配的基本单位

线程

建立线程

Thread

  1. 继承 Thread 类(Thread 完成了 Runnable 接口)
  2. 重写 run 要领
  3. start() 要领启动线程

Runnable

  1. 完成 Runnable 接口
  2. 重写 run 要领
  3. new Thread(Runnable target),new Thread(Runnable target,String name)

多个 Thread 实例共用一个 Runnable,这些线程的 run 要领雷同,可以同享雷同的数据

然则存在线程同步问题

public class RunnableTest implements Runnable
{
    private int ticket = 10;
    public void run()
    {
        while (true)
        {
            if (ticket > 0)
            {
                System.out.println(Thread.currentThread().getName() + "售出" + ticket + "号票");
                ticket--;
            }
            else System.exit(0);
        }
    }
    public static void main(String[] args)
    {
        RunnableTest rt = new RunnableTest();
        Thread t1 = new Thread(rt, "1号窗口");
        Thread t2 = new Thread(rt, "2号窗口");
        t1.start();
        t2.start();
    }
}

print

1号窗口售出10号票
1号窗口售出9号票
1号窗口售出8号票
1号窗口售出7号票
2号窗口售出7号票
2号窗口售出5号票
1号窗口售出6号票
2号窗口售出4号票
1号窗口售出3号票
2号窗口售出2号票
1号窗口售出1号票

匿名类

匿名类可以轻易的接见要领的局部变量,然则必需声明为 final,由于匿名类和一般局部变量生命周期不一致

jdk7 中已不再须要显现声明为 final,实际上被虚拟机自动隐式声清楚明了

public static void main(String[] args)
{
    new Thread( )
    {
        public void run( )
        {
            //内容
        }
    }.start( );
    new Thread(new Runnable( )
    {
        public void run( )
        {
            //内容
        }
	}).start( );
}

Callable

  1. 建立 Callable 的完成类,并冲写 call() 要领,该要领为线程实行体,而且该要领有返回值
  2. 建立 Callable 完成类的实例,并用 FutuerTask 类来包装 Callable 对象,该 FutuerTask 封装了 Callable 对象 call() 要领的返回值
  3. 实例化 FutuerTask 类,参数为 FutuerTask 接口完成类的对象来启动线程
  4. 经由历程 FutuerTask 类的对象的 get() 要领来猎取线程终了后的返回值
    public class CallableTest implements Callable<Integer>
    {
        //重写实行体 call( )
        public Integer call( ) throws Exception
        {
            int i = 0;
            for (; i < 10; i++)
            {
               //
            }
            return i;
        }
        public static void main(String[] args)
        {
            Callable call = new CallableTest( );
            FutureTask<Integer> f = new FutureTask<Integer>(call);
            Thread t = new Thread(f);
            t.start( );
            //取得返回值
            try
            {
                System.out.println("返回值:" + f.get( ));
            }
            catch (Exception e)
            {
                e.printStackTrace( );
            }
        }
    }
    

    print

    返回值:10
    

线程要领

  1. 线程实行体:run()
  2. 启动线程:start()
  3. Thread 类要领
    要领 形貌
    public final void setName(String name) 转变线程称号
    public final void setPriority(int priority) 设置优先级
    public final void setDaemon(boolean on) 设为保卫线程,当只剩下保卫线程时自动终了
    public final boolean isAlive() 测试线程是不是处于运动状况
    public static void yield() 停息当前线程(回到停当状况)
    public static void sleep(long millisec) 进入休眠状况
    public final void join() 停息当前线程,守候挪用该要领线程实行终了
    public final void join(long millisec) 停息当前线程指定时候
    public static Thread currentThread() 返回对当前正在实行的线程对象的援用

线程状况

  1. 停当状况:
    • start() 要领进入停当状况,守候虚拟机调理
    • 运转状况挪用 yield 要领会进入停当状况
    • lock 池中的线程取得锁后进入停当状况
  2. 运转状况:停当状况经由线程调理进去运转状况
  3. 壅塞状况:
    • 休眠:挪用 sleep 要领
    • 对象 wait 池:挪用 wait 或 join 要领,被 notify 后进入 lock 池
    • 对象 lock 池:未取得锁
  4. 殒命状况:run 要领实行终了

    graph TB T(新线程)--start要领-->A(停当状况) A--线程调理-->B(运转状况) B--yield要领-->A B--sleep要领-->D(壅塞:休眠) B--wait或join要领-->E(壅塞:wait池) B--未取得锁-->F(壅塞:lock池) B--run要领实行完-->C(殒命状况) D--时候到-->A E--notify要领-->F F--取得锁-->A

线程同步

保证程序原子性、可见性、有序性的历程

壅塞同步

基于加锁争用的消极并发战略

synchronized

  1. synchronized 寄义
    • 运用 synchronized 可以锁住某一对象, 当其他线程也想锁住该对象以实行某段代码时,必需守候已持有锁的线程开释锁
    • 开释锁的体式格局有互斥代码实行终了、抛出非常、锁对象挪用 wait 要领
  2. 差别的运用体式格局代表差别的锁粒度
    • 润饰一般要领 = synchronized(this)
    • 润饰静态要领 = synchronized(X.class)
    • 润饰代码块(对象 extends Object)

ReentrantLock

  1. 建立 Lock 锁

    ReentrantLock 完成了 Lock 接口, Lock lock = new ReentrantLock()

  2. Lock 寄义
    • 运用 lock() 要领示意当前线程占领 lock 对象
    • 开释该对象要显现掉用 unlock() 要领 ,多在 finally 块中举行开释
  3. trylock 要领
    • synchronized 会一向守候锁,而 Lock 供应了 trylock 要领,在指定时候内试图占用
    • 运用 trylock, 开释锁时要推断,若占用失利,unlock 会抛出非常
  4. Lock 的线程交互
    • 经由历程 lock 对象取得一个 Condition 对象,Condition condition = lock.newCondition()
    • 挪用这个Condition对象的:await,signal,signalAll 要领
  5. 示例
    public class LockTest
    {
        public static void log(String msg)//日记要领
        {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date date = new Date( );
            String dateStr = sdf.format(date);
            System.out.println(dateStr + " " + Thread.currentThread( ).getName( ) + " " + msg);
        }
        public static void main(String[] args)
        {
            Lock lock = new ReentrantLock( );
            new Thread("t1")
            {
                public void run( )
                {
                    boolean flag = false;
                    try
                    {
                        log("线程已启动");
                        log("尝试占领lock");
                        flag = lock.tryLock(1, TimeUnit.SECONDS);
                        if (flag)
                        {
                            log("胜利占领lock");
                            log("实行3秒营业操纵");
                            Thread.sleep(3000);
                        }
                        else
                        {
                            log("经由1秒钟尝试,占领lock失利,摒弃占领");
                        }
                    }
                    catch (InterruptedException e)
                    {
                        e.printStackTrace( );
                    }
                    finally
                    {
                        if (flag)
                        {
                            log("开释lock");
                            lock.unlock( );
                        }
                    }
                    log("线程终了");
                }
            }.start( );
            try
            {
                //先让 t1 先实行两秒
                Thread.sleep(2000);
            }
            catch (InterruptedException e1)
            {
                e1.printStackTrace( );
            }
            new Thread("t2")
            {
                public void run( )
                {
                    boolean flag = false;
                    try
                    {
                        log("线程启动");
                        log("尝试占领lock");
    
                        flag = lock.tryLock(1, TimeUnit.SECONDS);
                        if (flag)
                        {
                            log("胜利占领lock");
                            log("实行3秒的营业操纵");
                            Thread.sleep(3000);
                        }
                        else
                        {
                            log("经由1秒钟的尝试,占领lock失利,摒弃占领");
                        }
                    }
                    catch (InterruptedException e)
                    {
                        e.printStackTrace( );
                    }
                    finally
                    {
                        if (flag)
                        {
                            log("开释lock");
                            lock.unlock( );
                        }
                    }
                    log("线程终了");
                }
            }.start( );
        }
    }
    

    print

    2019-11-07 15:50:01 t1 线程已启动
    2019-11-07 15:50:01 t1 尝试占领lock
    2019-11-07 15:50:01 t1 胜利占领lock
    2019-11-07 15:50:01 t1 实行3秒营业操纵
    2019-11-07 15:50:03 t2 线程启动
    2019-11-07 15:50:03 t2 尝试占领lock
    2019-11-07 15:50:04 t2 经由1秒钟的尝试,占领lock失利,摒弃占领
    2019-11-07 15:50:04 t2 线程终了
    2019-11-07 15:50:04 t1 开释lock
    2019-11-07 15:50:04 t1 线程终了
    
  6. synchronized 和 Lock 区分
    • synchronized 是关键字,Lock 是接口, synchronized是内置的言语完成,Lock是代码层面的完成
    • synchronized 实行终了自动开释锁,Lock 须要显现 unlock()
    • synchronized 会一向守候,尝试占用锁,Lock 可以运用 trylock,在一段时候内尝试占用,时候到占用失利则摒弃

非壅塞同步

非壅塞同步是一种基于争执检测和数据更新的乐观并发战略

actomic 类

  1. 原子操纵
    • 原子操纵是不可中断的操纵,必需一次性实行完成
    • 赋值操纵是原子操纵,但 a++ 不是原子操纵, 而是取值、加一、赋值三个步骤
    • 一个线程取 i 的值后,还没来得及加一,第二个线程也来取值,就产生了线程平安问题
  2. actomic 类的运用
    • jdk6 今后,新增包 java.util.concurrent.atomic,内里有种种原子类,比方 AtomicInteger
    • AtomicInteger 供应了种种自增,自减等要领,这些要领都是原子性的。换句话说,自增要领 incrementAndGet 是线程平安的
    • 10000 个线程做 value 加一的操纵,用 a++ 体式格局得出不准确的效果,用原子类 AtomicInteger 的 addAndGet() 要领得出准确效果
    public class ThreadTest
    {
        static int value1 = 0;
        static AtomicInteger value2 = new AtomicInteger(0);//原子整型类
        public static void main(String[] args)
        {
            for (int i = 0; i < 100000; i++)
            {
                new Thread( )
                {
                    public void run( )
                    {
                        value1++;
                    }
                }.start( );
                new Thread( )
                {
                    public void run( )
                    {
                        value2.addAndGet(1);//value++的原子操纵
                    }
                }.start( );
            }
            while (Thread.activeCount( ) > 2)
            {
                Thread.yield( );
            }
            System.out.println(value1);
            System.out.println(value2);
        }
    }
    

    print

    99996
    100000
    

无同步计划

假如一个要领不触及同享数据,那末他天生就是线程平安的

可重入代码

可以在代码实行的任何时候中断它,转而去实行别的一段代码,在掌握权返回以后,本来的程序不会涌现任何的毛病

  1. 一个要领返回效果是可以展望的,输入了雷同的数据,就可以返回雷同的效果,那这个要领就具有可重入性,也就是线程平安的
  2. 栈关闭是一种可重用代码

    多个线程接见统一个要领的局部变量时,不会涌现线程平安问题,由于局部变量保存在虚拟机栈中,属于线程的私有地区,所以不会涌现线程平安性

    public class ThreadTest
    {
        static void add( )
        {
            int value = 0;
            for (int i = 0; i < 1000; i++)
            {
                value++;
            }
            System.out.println(value);
        }
    
        public static void main(String[] args)
        {
            ExecutorService threadPool = Executors.newCachedThreadPool( );
            threadPool.execute(( ) -> add( ));
            threadPool.execute(( ) -> add( ));
            threadPool.shutdown( );
        }
    }
    

    print

    1000
    1000
    

线程当地存储

  1. 把同享数据的可见局限限定在统一个线程以内,即使无同步也能做到防止数据争用
  2. 运用 java.lang.ThreadLocal 类来完成线程当地存储功用
    • ThreadLocal 变量是一个差别线程可以具有差别值的变量,一切的线程可以同享一个ThreadLocal对象
    • 恣意一个线程的 ThreadLocal 值发作变化,不会影响其他的线程
    • 用set()和get()要领对ThreadLocal变量举行赋值和检察其值
    public class ThreadLocalDemo
    {
        public static void main(String[] args)
        {
            ThreadLocal threadLocal1 = new ThreadLocal( );
            Thread t1 = new Thread(( ) ->
            {
                threadLocal1.set(1);
                try
                {
                    Thread.sleep(3000);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace( );
                }
                System.out.println(threadLocal1.get( ));
            });
            Thread t2 = new Thread(( ) -> threadLocal1.set(2));
            t1.start( );
            t2.start( );
        }
    }
    

    print

    1
    
  3. ThreadLocal 道理
    • 每一个线程都有一个 ThreadLocal.ThreadLocalMap 对象,挪用 threadLocal1.set(T value) 要领时,将 threadLoacl1 和 value 键值对存入 map
    • ThreadLocalMap 底层数据结构大概致使内存泄漏,尽大概在运用 ThreadLocal 后挪用 remove()要领

死锁

死锁前提

  1. 互斥前提
  2. 要求与坚持前提
  3. 不可褫夺前提
  4. 轮回守候前提(环路前提)

Java死锁示例

public static void main(String[] args)
{
    Object o1 = new Object( );
    Object o2 = new Object( );

    Thread t1 = new Thread( )
    {
        public void run( )
        {
            synchronized (o1)//占领 o1
            {
                System.out.println("t1 已占领 O1");
                try
                {
                    Thread.sleep(1000);//停留1000毫秒,另一个线程有充足的时候占领 o1
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace( );
                }
                System.out.println("t1 试图占领 o2");
                System.out.println("t1 守候中");
                synchronized (o2)
                {
                    System.out.println("t1 已占领 O2");
                }
            }
        }
    };
    Thread t2 = new Thread( )
    {
        public void run( )
        {
            synchronized (o2)  //占领 o2
            {
                System.out.println("t2 已占领 o2");
                try
                {
                    Thread.sleep(1000);//停留1000毫秒,另一个线程有充足的时候占领 o2
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace( );
                }
                System.out.println("t2 试图占领 o1");
                System.out.println("t2 守候中");
                synchronized (o1)
                {
                    System.out.println("t2 已占领 O1");
                }
            }
        }
    };
    t1.start( );
    t2.start( );
}

print

t1 已占领 O1
t2 已占领 o2
t1 试图占领 o2
t1 守候中
t2 试图占领 o1
t2 守候中

线程通讯

  1. Object 类要领
    要领 形貌
    wait() 线程进入守候池
    notify() 叫醒守候当前线程锁的线程
    notifyAll() 叫醒一切线程,优先级高的优先叫醒

    为何这些要领设置在 Object 对象上?

    表面上看,由于任何对象都可以加锁

    底层上说,java 多线程同步的 Object Monitor 机制,每一个对象上都设置有类似于鸠合的数据结构,贮存当前取得锁的线程、守候取得锁的线程(lock set)、守候被叫醒的线程(wait set)

  2. 生产者花费者模子
    • sleep 要领,让出 cpu,但不放下锁
    • wait 要领,进入锁对象的守候池,放下锁
public class ProducerAndConsumer
{
    public static void main(String[] args)
    {
        Goods goods = new Goods();
        Thread producer = new Thread()//生产者线程
        {
            public void run()
            {
                while (true) goods.put();
            }
        };
        Thread consumer = new Thread()//花费者线程
        {
            public void run()
            {
                while (true) goods.take();
            }
        };
        consumer.start();
        producer.start();
    }
}
class Goods//商品类
{
    int num = 0;//商品数量
    int space = 10;//空位总数
    public synchronized void put()
    {
        if (num < space)//有空位可放,可以生产
        {
            num++;
            System.out.println("放入一个商品,现有" + num + "个商品," + (space - num) + "个空位");
            notify();//叫醒守候该锁的线程
        }
        else//无空位可放,守候空位
        {
            try
            {
                System.out.println("没有空位可放,守候拿出");
                wait();//进入该锁对象的守候池
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
    public synchronized void take()
    {
        if (num > 0)//有商品可拿
        {
            num--;
            System.out.println("拿出一个商品,现有" + num + "个商品," + (space - num) + "个空位");
            notify();//叫醒守候该锁的线程
        }
        else///守候生产产物
        {
            try
            {
                System.out.println("没有商品可拿,守候放入");
                wait();//进入该锁对象的守候池
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
}

print

没有商品可拿,守候放入
放入一个商品,现有1个商品,9个空位
放入一个商品,现有2个商品,8个空位
拿出一个商品,现有1个商品,9个空位
放入一个商品,现有2个商品,8个空位
放入一个商品,现有3个商品,7个空位
放入一个商品,现有4个商品,6个空位
拿出一个商品,现有3个商品,7个空位
放入一个商品,现有4个商品,6个空位
···

线程池

线程的启动和终了都是比较斲丧时候和占用资本的,假如在体系顶用到了许多的线程,大批的启动和终了行动会严重影响机能

线程池很像生产者花费者形式,花费的对象是一个一个的可以运转的使命

  1. 设想思绪
    • 预备使命容器,可用 List,寄存使命
    • 线程池类组织要领中建立多个实行者线程
    • 使命容器为空时,一切线程 wait
    • 当外部线程向使命容器到场使命,就会有实行者线程被 notify
    • 实行使命终了后,没有接到新使命,就回归守候状况
  2. 完成一个线程池
    public class ThreadPool
    {
        int poolSize;// 线程池大小
        LinkedList<Runnable> tasks = new LinkedList<Runnable>();// 使命容器
        public ThreadPool(int poolSize)
        {
            this.poolSize = poolSize;
            synchronized (tasks)//启动 poolSize 个使命实行者线程
            {
                for (int i = 0; i < poolSize; i++)
                {
                    new ExecuteThread("实行者线程 " + i).start();
                }
            }
        }
        public void add(Runnable r)//增加使命
        {
            synchronized (tasks)
            {
                tasks.add(r);
                System.out.println("到场新使命");
                tasks.notifyAll();// 叫醒守候的使命实行者线程
            }
        }
        class ExecuteThread extends Thread//守候实行使命的线程
        {
            Runnable task;
            public ExecuteThread(String name)
            {
                super(name);
            }
            public void run()
            {
                System.out.println("启动:" + this.getName());
                while (true)
                {
                    synchronized (tasks)
                    {
                        while (tasks.isEmpty())
                        {
                            try
                            {
                                tasks.wait();
                            }
                            catch (InterruptedException e)
                            {
                                e.printStackTrace();
                            }
                        }
                        task = tasks.removeLast();
                        tasks.notifyAll(); // 许可增加使命的线程可以继承增加使命
                    }
                    System.out.println(this.getName() + " 接到使命");
                    task.run();//实行使命
                }
            }
        }
        public static void main(String[] args)
        {
            ThreadPool pool = new ThreadPool(3);
            for (int i = 0; i < 5; i++)
            {
                Runnable task = new Runnable()//建立使命
                {
                    public void run()//使命内容
                    {
                        System.out.println(Thread.currentThread().getName()+" 实行使命");
                    }
                };
                pool.add(task);//到场使命
                try
                {
                    Thread.sleep(1000);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
        }
    }
    

    print

    main 到场新使命
    启动:实行者线程 0
    实行者线程 0 接到使命
    实行者线程 0 实行使命
    启动:实行者线程 1
    启动:实行者线程 2
    main 到场新使命
    实行者线程 2 接到使命
    实行者线程 2 实行使命
    main 到场新使命
    实行者线程 2 接到使命
    实行者线程 2 实行使命
    
  3. java 线程池类
    • 默许线程池类 ThreadPoolExecutor 在 java.util.concurrent 包下
      ThreadPoolExecutor threadPool= new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
      /*
      第一个参数 int 范例, 10 示意这个线程池初始化了 10 个线程在内里事情
      第二个参数 int 范例, 15 示意假如 10 个线程不够用了,就会自动增加到最多 15个 线程
      第三个参数 60 连系第四个参数 TimeUnit.SECONDS,示意经由 60 秒,多出来的线程还没有接到使命,就会接纳,末了坚持池子里就 10 个
      第五个参数 BlockingQueue 范例,new LinkedBlockingQueue() 用来放使命的鸠合
      */
      
    • execute() 要领增加新使命
      public class TestThread 
      {   
          public static void main(String[] args) throws InterruptedException 
          {
              ThreadPoolExecutor threadPool= new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
              threadPool.execute(new Runnable()
              {//增加使命
                  public void run() 
                  {
                      System.out.println("实行使命");
                  }    
              });
          }
      }
      
  4. java 中几种线程池

    java 线程池的顶级接口是 Executor ,子接口是 ExecutorService ,子接口运用更普遍

    Executors 类供应了一系列工场要领用于建立线程池,返回的线程池完成了 ExecutorService 接口

    • newCachedThreadPool有缓冲的线程池,线程数 JVM 掌握,有线程可运用时不会建立新线程
    • newFixedThreadPool,牢固大小的线程池,使命量凌驾线程数时,使命存入守候行列
    • newScheduledThreadPool,建立一个线程池,可部署在给定耽误后运转敕令或许按期地实行
    • newSingleThreadExecutor,只要一个线程,次序实行多个使命,若不测停止,则会新建立一个
    ExecutorService threadPool = null;
    threadPool = Executors.newCachedThreadPool();//缓冲线程池
    threadPool = Executors.newFixedThreadPool(3);//牢固大小的线程池
    threadPool = Executors.newScheduledThreadPool(2);//定时使命线程池
    threadPool = Executors.newSingleThreadExecutor();//单线程的线程池
    threadPool = new ThreadPoolExecutor(···);//默许线程池,多个可控参数
    

线程平安类

  1. StringBuffer:内部要领用 synchronized 润饰
  2. Vetort:继承于 AbstractList
  3. Stack:继承于 Vector
  4. HashTable:继承于 Dictionary,完成了 Map 接口
  5. Property:继承于 HashTable,完成了 Map 接口
  6. concurrentHashMap:分段加锁机制

JVM解毒——JVM与Java体系结构

参与评论