多线程

一多线程

 一.概念的相关问题

 1.理解概念  线程和进程的区别?
  所有的操作系统都支持进程的概念,所有的程序都会对应一个进程每个运行的程序相当于一个进程,每一个进程一定是独立存在的。

    1.进程具有以下特点:
   a.独立性:进程是操作系统独立存在的实体,它拥有自己独立的资源  每一个进程都拥有自己的私立地址空间,如果没有本进程的允许求他的进程是不能访问其他私立的进程。

   b.动态性:进程与程序的区别  在于动态性  程序是一个静态指令的集合,进程是一个正在程序汇总运行是指令的集合  进程有自己的生命周期

   c.并发性: 多个进程可以在单个处理器上并发执行  多个进程不会相互影响。

   注意:并发性和并行性  两个概念
    并行性:指在同一个时刻内  有多个指令在多个处理器上同时执行
    并发性:指在同一个时刻内只能有一个指令执行  但是多个进程指令快速切换执行

    2.线程
   一个进程中包含了多个线程
   多线程的优势:
    a.进程之间不能共享资源,但是线程之间可以共享内存
    b.系统创建进程需要为每个进程分配系统资源,但是创建线程代价很小
    c.java语言内置多线程的支持   不是单纯的操作系统的调度  而是简化了java多线程的编程


 二.线程的创建
   线程有两种创建方式
   第一种:  继承Thread类 重写run方法
   第二种:  实现Runnable接口 重写run方法

  面试题: 线程有几种创建方式?  这两种创建方式分别有什么优势你比较倾向于哪个创建方式

   采用实现Runnable接口的方式
    ***>线程类只实现了Runnable接口 还可以继承其他实现类
    ***>*******如果实现Runnable接口 多个线程可以共享一个Target对象  非常适合多个相同的线程共同处理同一个资源,
      可以将cpu,代码,数据分开,从而形成一个清晰的模型 比较好的体现了面向对象的思想
     **>劣势;编程稍微复杂一些, 如果需要访问当前线程 必须使用Thread.currentThread方法 

 
      采用是继承Thread类
       优势:编程比较简单  如果需要访问当前线程 无需使用Thread.currentThread方法 直接使用this来获得当前线程
        劣势: 因为继承了Thread类无法再继承其他的类 不能进行相应的扩展  
    推荐使用实现Runnable接口
 
 三.线程的生命周期
  面试题: 线程的生命周期分为几种分别介绍一下

  当线程创建完毕后   它经历了五种状态
  当线程运行的时候  不能一直占有cpu的时间片  cpu会在多个线程之间相互调度  线程进入阻塞和运行状态进行切换

   新建 
    当线程 对象被创建出来 就进入到了新建状态  和其他java对象一样的 仅仅是由java虚拟机为其分配了内存地址
   
   就绪
    当调用start方法 仅仅是进入就绪状态不是运行状态   至于什么时候进入运行状态是由jvm虚拟机进行调度决定的
   
   运行 
    如果线程就绪状态 同时获得了cpu的执行权 这个线程就进入到运行状态
    
    线程由阻塞状态进入到运行状态
     a.调用阻塞方法已经返回
     b.调用sleep方法已经到期
     c.线程获取同步监视器成功
     d.调用的suspend方法已经恢复
   
   
   阻塞
    线程什么时候进入阻塞状态:
     a.线程调用一个阻塞的方法,在该方法返回之前一直阻塞
     b.线程调用sleep方法进入阻塞状态
     c.线程尝试获取同步监视器 ,但该同步监视器被其他线程所持有
     d.线程调用suspend方法   
   
   死亡 
    a.run方法执行完毕
    b.线程抛出异常
    c.直接调用线程的stop方法

 四.线程的控制
  1.join线程
   先start后join

public class JoinThreadDemo {

    public static void main(String[] args) {
        JoinThreadDemo jtd =new JoinThreadDemo();
        jtd.init();
    }
    
    public void init(){
        MainThread mt =new MainThread();
        //对当前线程设置名称
        mt.setName("主线程");
        mt.start();
        
        
    }
    
    /*
     * 要求;
     *  主线程打印到30的时候  停止 先去打印join加塞线程  打印 1-30  然后继续执行主线程 
     */
    class MainThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                if(i==30){
                    //执行加塞线程
                    JoinThread jt =new JoinThread();
                    jt.setName("加塞进程");
                    //是先join然后star还是先start然后join
                    jt.start();
                    try {
                        jt.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(this.getName()+","+i);
            }

        }

    }

    class JoinThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 30; i++) {
                    //获得当前线程名字 每个线程都有自己的默认的名称
                System.out.println(this.getName()+","+i);
            }
        }
    }

}
join控制示例

 
  2.线程的让步
   线程的让步(yield())和线程睡眠(sleep(long millis))差不多  但是有一个重要的区别
      线程的让步是让当前线程暂停执行 会放弃cpu的执行权  然后操作系统重新调用重新分配资源  所有相同优先级的线程都有可能抢到CPU的执行权 由于当前线程还在运行,所以不允许低优先级的线程获得运行机会
     线程的睡眠是让线程暂停执行,但是不放弃cpu的执行权,允许较低优先级的线程获得运行机会,时间一到就开始继续执行
  3.线程的优先级
   每个线程都有一个优先级   优先级越高 得到cpu执行的权利越高 
    
    优先级在windows系统有时无用的 在linux系统是完全可以用的
  4.线程的睡眠
     让线程休息一下  可以使用Thread类的静态方法实现


 五.************线程的安全
   线程的安全  概念定义?
   《Java并发编程实践》,作者做出了 如下定义: 多个线程访问一个类的对象  如果不考虑这些线程在运行环境下的调度和交替执行
   并且不需要额外的同步以及在掉队方法代码不需要做其他的协调  那么这个类的行为如果仍然正常  那么这个类就是线程安全的
   synchronized 这个加到方法上意味着整个方法都加了一把大锁
   
   同步代码块  就是定义一组原子行为所谓的原子行为  只允许一个线程既进入直到这个线程执行完毕 下一个线程才可以进入 
   synchronized(obj){
   
   } 
   synchronized(this){  代表这个类的方法
   
   }
   锁住类的字节码
   synchronized(PrintString.class){
   }
   

public class ThreadSafe {
    public static void main(String[] args) {
        ThreadSafe ts = new ThreadSafe();
        ts.init();
    }

    // 全局的
    PrintString ps = new PrintString();

    public void init() {

        // 开启两个线程
        new Thread(new Runnable() {

            @Override
            public void run() {
                while (true) {
                    ps.showString("AAAAAAAAAAAAAAAAAAAAAAAA");
                }
            }
        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                while (true) {
                    ps.showString("BBBBBBBBBBBBBBBBBBBBBBBB");
                }
            }
        }).start();

    }

    // 这个是一个普通的内部类
    class PrintString {

        // 打印指定的字符串
        public void showString(String s) {
            synchronized (PrintString.class) {
                for (int i = 0; i < s.length(); i++) {
                    System.out.print(s.charAt(i));//charAt返回指定索引处的char值
                }
                System.out.println();
            }
        }

    }

}
Threadsafe

 


   同步监视器
  比如A线程想执行showString方法 需要尝试获取同步监视器  但是这个同步监视器被B线程所持有 所以A线程需要等待 -->阻塞状态
  执行B线程执行完毕  释放同步监视器 A获得同步监视器然后执行showString方法 也就是运行状态
  
   完成两个程序同时运行 每个程序自加到10000  两个程序总和必须是20000

public class TwoAdd {
    /**
     * 思路: 1.两个线程同时访问一个方法 一定会有并发问题 必须考虑加锁
     *  2.如果做到 保证在main方法执行完毕之前 其他两个线程都执行完毕
     */
    public int i = 0;

    public static void main(String[] args) {
        TwoAdd ta =new TwoAdd();
        ta.init();
    }

    // 保证操作的是同一个类的对象
    AddDemo ad = new AddDemo();

    public void init() {
        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    ad.add();
                }
            }
        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    ad.add();
                }
            }
        }).start();
        //根据线程的活跃数 来确定线程是否执行完毕
        while(Thread.activeCount()!=1){
            //继续卡在这里继续执行其他程序
        }
        
        //在打印i之前必须保证两个线程都运行完毕
        System.out.println(i);

    }

    class AddDemo {
        public synchronized void add() {
            i++;
        }

    }
}
Twoadd


  
 六.wait  notify
  A线程在运行的过程中不满足条件 则需要等待  等待B线程执行完毕才能执行  ----死锁

   面试题:  wait和sleep有什么区别?
    a. sleep必须指定时间  而wait不需要  可以不指定
    b.sleep和wait都可以让线程处于冻结状态(阻塞状态)----释放了执行权(相同点)
    c.持有锁的线程执行sleep 不释放锁  不释放cpu的执行权  wait 释放锁
    d.sleep到时间后会自动唤醒  wait没有指定时间必须通过notfiy或者notifyAll唤醒这个线程

    练习: 假设有三个线程同时执行  分别每个线程执行一次
        A B C  A  B C

public class ThreadWaitDemo {
    /*
     * 两个线程A和B 让两个线程交替执行 A B A B A B 思路: 1.A和B线程是同时争抢cpu的执行权 A A设置一个条件
     * 让a必须等待b的执行完毕才能执行 wait B 线程执行完毕才唤醒A线程
     */
    public static void main(String[] args) {
        ThreadWaitDemo tw =new ThreadWaitDemo();
        tw.init();

    }

    // 同一个对象
    PrintString ps = new PrintString();

    public void init() {

        new Thread(new Runnable() {

            @Override
            public void run() {
                while (true) {
                    ps.f1();
                }
            }
        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                while (true) {
                    ps.f2();
                }
            }
        }).start();
    }

    class PrintString {
        // 通过一个标志来判断谁执行
        boolean flag = true;

        public synchronized void f1() {
            while (flag) {
                try {
                    wait();// 等待其他线程唤醒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            flag = true;
            notifyAll();// 唤醒所有线程
            System.out.println("我是线程AAAAAAAAAAAAAAAAAAAAAA");
        }

        public synchronized void f2() {
            while (!flag) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            flag = false;
            notifyAll();// 唤醒所有的线程
            System.out.println("我是线程BBBBBBBBBBBBBBBBBBBBBB");
        }

    }

}
2个各打印一次
public class ThreadWaitDemo2 {

    public static void main(String[] args) {
        ThreadWaitDemo2 tw2 = new ThreadWaitDemo2();
        tw2.init();
    }

    PrintString ps = new PrintString();

    public void init() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    ps.f1();
                }

            }
        }).start();
        new Thread(new Runnable() {

            @Override
            public void run() {
                while (true) {
                    ps.f2();
                }

            }
        }).start();
        new Thread(new Runnable() {

            @Override
            public void run() {
                while (true) {
                    ps.f3();
                }

            }
        }).start();
    }

    class PrintString {
        public int i = 1;

        public synchronized void f1() {
            while (i != 1) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            i = 2;
            notifyAll();
            System.out.println("AAAAAAAAAAAAAAAAAAAAAAAAAA");
        }

        public synchronized void f2() {
            while (i != 2) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            i = 3;
            notifyAll();
            System.out.println("BBBBBBBBBBBBBBBBBBBBBBBBB");
        }

        public synchronized void f3() {
            while (i != 3) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            i = 1;
            notifyAll();
            System.out.println("CCCCCCCCCCCCCCCCCCCCCC");
        }
    }

}
3个各打印一次


   死锁:是指两个或两个以上线程在执行的过程中,因争抢资源而进入相互等待的现象  如果没有外力作用 它们将无法推动进行此时系统处于某种死锁状态
    这就是所指的线程进入死锁状态    

   为什么会产生死锁现象呢?
    a.因为系统资源不足
    b.系统执行的顺序不正确
    c.资源分配不当
    
   操作系统 : 产生死锁需要四个 :
     1.互斥条件 :所谓的互斥条件就是进程在某一个时间内独占资源 
     
     2.请求与保持条件: 一个进程因为请求资源而阻塞时,对已获得的资源部释放
     
     3.不剥夺条件 :进程对已获得的资源在未使用完毕之前 不能强行剥夺
     
     4.循环等待条件: 若干个进程之间 形成一种头尾相互等待资源的关系
   
   如果避免产生死锁:
     只要想法破解其中一个产生死锁的条件即可
     
     避免事务中用户的交互(减少持有资源的时间,较少的锁去竞争)
     
     使用隔离级别(数据库....)

 

多线程高级部分
  JUC详解
  一、java.util.concurrent.atomic 
   支持在单个变量上解除锁的线程安全编程。
   AtomicInteger 
   addAndGet(  以原子方式将给定值与当前值相加。
   getAndIncrement() 以原子方式将当前值加 1。
   incrementAndGet()    以原子方式将当前值加 1。

public class AtomicIntergerDemo {
    
    public int i=0;
    
    public static void main(String[] args) {
        new AtomicIntergerDemo().init();
    }
    
    AtomicInteger aa=new AtomicInteger();
    
    public void init(){
        new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i <10000; i++) {
                    add();
                }
            }
        }).start();
        
        new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i <10000; i++) {
                    add();
                }
            }
        }).start();
        
        while(Thread.activeCount()!=1){
            
        }
        System.out.println(aa.intValue());
    }
    
    
    public synchronized void add(){
        aa.getAndIncrement();
    }

}
用原子实现2个线程想加


  底层依赖于 compareAndSet (CAS) CAS需要三个操作数  分别是内存位置V   旧的预期的值 A  新的值N
      使用CAS进行更新时  当v符合A 使用N更新v的值  否则就不执行更新

二:java.util.concurrent.locks
 
  1.Lock锁Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
   Lock 类还可以提供与隐式监视器锁完全不同的行为和语义,
   Lock接口 有一个实现类  ReentrantLock  一个可重入的互斥锁  
   使用Lock锁 有以下优势:
     怎样加锁 在什么位置释放锁一目了然
  Lock锁相对于synchronized增加了以下功能:
   
   等待可中断 当前持有锁线程 如果长期不释放 正在等待的线程可以放弃等待 处理其他事情
   
   公平锁    
   
   绑定条件

public class LockDemo {
    public static void main(String[] args) {
        new LockDemo().init();
    }

    Printstring ps = new Printstring();

    public void init() {
        new Thread(new Runnable() {

            @Override
            public void run() {
                while (true) {
                    ps.showString("AAAAAAAAAAAAAAAA");
                }

            }
        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                while (true) {
                    ps.showString("BBBBBBBBBBBBBBBB");
                }

            }
        }).start();
    }

}

class Printstring {
    // 得到Lock锁
    Lock lock = new ReentrantLock();

    public void showString(String s) {
        try {
            // 在这个位置进行加锁
            lock.lock();
            for (int i = 0; i < s.length(); i++) {
                System.out.print(s.charAt(i));
            }
            System.out.println();
        } finally {
            lock.unlock();
        }
    }
}
Lock锁实现示例

   2.Condition  条件锁

public class ConditionDemo {
    
    PrintString ps=new PrintString();
    
    public static void main(String[] args) {
        new ConditionDemo().init();
    }
    public void init(){
        new Thread(new Runnable() {
            public void run() {
                while(true){
                    ps.f1();
                }
            }
        }).start();
        
        new Thread(new Runnable() {
            public void run() {
                while(true){
                    ps.f2();
                }
            }
        }).start();
        
        new Thread(new Runnable() {
            public void run() {
                while(true){
                    ps.f3();
                }
            }
        }).start();
        
    }
    
    class PrintString{
        //得到条件锁
        Lock lock=new ReentrantLock();
        //得到三把条件锁
        Condition c1=lock.newCondition();
        Condition c2=lock.newCondition();
        Condition c3=lock.newCondition();
        int token=1;
        
        public void f1(){
            try{
                lock.lock();
                while(token!=1){
                    //是当前线程进入等待状态
                    c1.await();
                }
                System.out.println(Thread.currentThread().getName());
                token=2;
                //单独唤醒c2
                c2.signal();
            }catch(Exception e){
                
            }finally{
                lock.unlock();
            }
            
        }
        
        public void f2(){
            try{
                lock.lock();
                while(token!=2){
                    //是当前线程进入等待状态
                    c2.await();
                }
                System.out.println(Thread.currentThread().getName());
                token=3;
                //单独唤醒c2
                c3.signal();
            }catch(Exception e){
                
            }finally{
                lock.unlock();
            }
            
        }
        
        public void f3(){
            try{
                lock.lock();
                while(token!=3){
                    //是当前线程进入等待状态
                    c3.await();
                }
                System.out.println(Thread.currentThread().getName());
                token=1;
                //单独唤醒c2
                c1.signal();
            }catch(Exception e){
                
            }finally{
                lock.unlock();
            }
            
        }
        
    }

}
条件锁实现示例

三、java.util.concurrent
   1.线程池
    线程池概念:  线程池也是一种多线程处理的方式,处理过程中 将任务添加到队列中  然后创建线程后自动启动这些任务
     线程池都是一些后台线程  一定时间内创建固定数量的后台线程
    
     使用线程池的步骤:
     1.使用Executors  类的静态方法创建ExcutorService对象  这个对象就代表一个线程池
     2.使用这个线程池对象的 excute或者submit方法进行提交  这个任务也是一个线程
     3.执行完毕要关闭线程池

public class ThreadPool {
    
    public static void main(String[] args) {
        //创建一个固定大小的线程池
        ExecutorService pool1=Executors.newFixedThreadPool(5);//相当于创建了5个线程
        //创建了一个线程的线程池
        ExecutorService pool2=Executors.newSingleThreadExecutor();
        //相当于创建了一个固定大小为10的线程池
        ExecutorService pool3=Executors.newCachedThreadPool();
        
        //开启十个任务  每个任务的事情都是循环十次
        final Random r=new Random();
        for (int i = 0; i <10; i++) {
            final int task=i;
            pool3.execute(new Runnable() {
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        try {
                            Thread.sleep(r.nextInt(100));
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        System.out.println("当前线程:"+Thread.currentThread().getName()+
                                "在运行第"+task+"个任务");
                    }
                }
            });
        }
        pool3.shutdown();
        
    }

}
线程池示例

   2.容器
    同步容器:使用同步容器要加锁 不让会报错
     java.util.ConcurrentModificationException  迭代的时候进行修改
    
    并发容器 :用并发容器代替同步容器(集合)
     ConcurrentHashMap  是代替Map同步的并发容器
     ConcurrentLinkedQueue  
     CopyOnWriteArrayList  List的并发容器的替代
     CopyOnWriteArraySet   set并发容器的实现

  四、同步工具
    java.util.concurrent
   1.Semaphore
    这个类代表一个信号量,信号量维护了一个许可集,可以控制某个资源被访问的个数
    如果要访问资源  必须获得一个acquire(),如果没有则等待其他的release()
    

public class SemaphoreDemo {
    Dosth ds=new Dosth();
    
    public static void main(String[] args) {
        SemaphoreDemo sd=new SemaphoreDemo();
        sd.go();
    }
    
    //同时访问worked方法的并发不能超过2个
    public void go(){
        //开启十个线程
        final Semaphore sp=new Semaphore(2);//设置最大的许可数
        for (int i = 0; i <10; i++) {
            new Thread(new Runnable() {
                
                @Override
                public void run() {
                    try {
                        //获得许可
                        sp.acquire();
                        ds.work();
                        sp.release();//最后要释放许可
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    
                    
                }
            },"线程:"+i).start();
        }
    }
    
    class Dosth{
        public void work(){
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
        }
    }

}
Semaphore示例


   2.CountDownLatch
    四个人约定打球
    

public class CountDownLatchDemo {

    public static void main(String[] args) {
        CountDownLatchDemo cdl=new CountDownLatchDemo();
        cdl.init();

    }

    Random r = new Random();
    CountDownLatch cla = new CountDownLatch(3);

    public void init() {
        new Thread(new Runnable() {

            @Override
            public void run() {
                System.out.println("张三正在赶来的路上...");
                try {
                    Thread.sleep(r.nextInt(5000));
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("张三到达集合地点");
                cla.countDown();
            }
        },"张三线程").start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                System.out.println("李四正在赶来的路上...");
                try {
                    Thread.sleep(r.nextInt(5000));
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("李四到达集合地点");
                cla.countDown();
            }
        },"李四线程").start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                System.out.println("王五正在赶来的路上...");
                try {
                    Thread.sleep(r.nextInt(5000));
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("王五到达集合地点");
                cla.countDown();
            }
        },"王五线程").start();
        
        new Thread(new Runnable() {
            
            @Override
            public void run() {
                System.out.println("司机正在集合地点等待");
                try {
                    cla.await();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("所有人到达集合地点可以进行下一步任务");
            }
        },"司机线程").start();
    }

}
CountDownLatch示例

可以应用在处理大数据时  比如:1千万个数据可以分为1000个线程处理  每个线程处理10000个数据  然后让一个总线程处理得到的1000个线程的结果
   3.CyclicBarrier
    

public class CyclicBarrierDemo {

    public static void main(String[] args) {
        final CyclicBarrier cyclicBarrier = new CyclicBarrier(3);

        ExecutorService service = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 3; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 5; j++) {
                        try {
                            Thread.sleep(new Random().nextInt(5000));

                            System.out.println(Thread.currentThread().getName()
                                    + "已经到达集合点" + ",现在共有"
                                    + (cyclicBarrier.getNumberWaiting() + 1)
                                    + "个线程达到");
                            if (cyclicBarrier.getNumberWaiting() + 1 == 3) {
                                System.out.println("所有线程都已经达到集合点,可以进行下一步任务..");
                            } else {
                                System.out.println("正在等待其他线程的到达...");
                            }
                            cyclicBarrier.await();

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

                    }

                }
            });
        }
        service.shutdown();

    }

}
CyclicBarrier示例

 

posted on 2016-09-05 15:23  cch_java  阅读(185)  评论(0编辑  收藏  举报

导航