java多线程的一点整理

     多线程是提升效率性能的一个重要技术,企业级开发中运用的很普遍,我在学习了一段时间以后,仍是有不少地方是很懵比的,后续的会继续学习更新。html

    1.  多线程简单的概述java

    进程:正在进行的程序,好比任务管理器上的进程,QQ等等编程

    线程:就是进程中负责执行程序的控制单元(或者执行路径),一个进程中有多个执行路径称之为多线程安全

 开启多线程是为了同时运行多部分代码,每一个线程都有本身的运行内容,这个内容称之为线程的任务。多线程

 多线程的好处:解决了多程序同时运行的问题。多线程的弊端:线程太多回到效率下降。并发

其实应用程序的执行是CPU在作着快速切换完成的,这个切换是随机的。dom

 

下面是多线程的几种状态ide

jdk 1.5以前的 多线程两种建立方式就很少说了。函数

1.extend Thread ,覆写run方法(run 方法就是描述的线程任务),将该类new一个对象就是建立了一个线程对象,用start()方法开启线程性能

2.实现Runnable接口,覆写run()方法,方法里面也是描述线程任务,new一个Thread线程对象,将任务构造的时候传递进去,用start()方法开启线程

注意:实现Runnable接口的好处

1.将线程的任务从线程的子类中分离出来,进行的单独的封装,按照面向对象的思想将任务进行了封装

2.避免了java单继承的局限性。

2.0 多线程安全问题

public class ticket implements Runnable{
    private  int num=500;

    public void sale(){
        while (true){
            synchronized(this) {
                if (num>0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"...sale=="+num--);
                }else {
                    break;
                }
            }


        }
    }


    @Override
    public void run() {
        sale();
    }
}
public static void main(String[] args){
  
         //屡次启动一个线程是非法的。
         ticket t1= new ticket();   
         Thread thread1= new Thread(t1);
         Thread thread2= new Thread(t1);
         Thread thread3= new Thread(t1);
         Thread thread4= new Thread(t1);
         thread1.start();
         thread2.start();
         thread3.start();
         thread4.start();

     }

这段代码很简单,4 个线程操做同一个ticket对象,num是一个全局变量,每一个线程进来休眠10ms后都减1,直到为0,能够观察到里面加了synchronized关键字(同步),若是不加synchronized,将会出现线程不安全的因素。

附:屡次启动一个线程对象是非法的!

打印的结果是

分析:出现这种缘由就是0线程进来后,此时num为1,判断(num>0)以后,线程休眠了,cpu释放执行权(由于cpu执行那个线程是随机切换的),1线程进来以后也出现这种状况num=1的时候,cpu释放执行权,同理其余的2个线程也发生这种状况,致使出现数据不一样步的状况,要解决这种就要加锁

线程安全问题产生的缘由:

1.多个线程操做共享数据

2.操做共享数据的代码有多条

用锁去解决线程安全的问题,synchronized(锁对象){须要被同步的代码},this表示当前线程任务对象

同步的好处: 解决了线程的安全问题

同步的弊端:相对下降了效率,由于同步外的线程都会判断同步锁。

同步的前提:同步必须有多个线程并使用同一个锁。

同步函数的锁对象是当前固定的this,同步代码块的锁是任意的对象。

静态同步函数锁的对象是该函数所属的字节码文件对象,能够用this.getClass()或者类名.class表示

死锁的状况:同步锁的的嵌套,死锁可能会出现和谐的状况。

2.1 单例模式下多线程的安全问题

/**
    * 懒汉式
    * */
    public static class  Single{
        private static  Single s= null;

        private Single(){

        }

        public static Single getInstance(){
          

            /*
            * 加锁是为了解决线程的安全的问题,锁以前加个判断 是由于线程进来判断锁,比较消耗效率(解决多线程下单例的效率问题)
            * */
            if (s==null){
                synchronized (Single.class){
             //多线程访问须要同步,例如a,b两个线程,a线程执行到s==null时失去了cpu的执行权,
            // 而后b线程同理执行到s==null失去cpu的执行权,这个就不是单例模式了。
            //懒汉式在多线程状况下,操做s的共享数据有多条代码,所以存在线程不安全的状况,须要同步操做s共享数据的多条代码
                    if (s==null){
                        s= new Single();
                    }
                }
            }


            return s;
        }

    }

上述懒汉式在多线程状况下就不是单例模拟了。

2.3 多线程之间的通讯 wait和notify机制(生成者-消费者模式)

先看一下简单的例子,2个线程,一个输入线程负责读,一个输出线程负责写

/**
 * Created by ZC-16-012 on 2018/2/2.
 *
 * 定义多线程的资源共享数据对象
 */
public class Resource {
    private String  name;

    private String sex;

    private boolean flag=false;

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    //输入线程set方法
    public synchronized  void set(String name,String sex){
        if (flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            this.name= name;
            this.sex= sex;
            flag=true;
            this.notify();
        }
    }

    //输出线程
    public synchronized void out(){
        if (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            System.out.println(this.getName()+"===="+this.getSex());
            this.setFlag(false);
            this.notify();
        }
    }
}
public class InputTask implements Runnable{
    Resource r;

    public InputTask(Resource r){
        this.r = r;
    }

    @Override
    public void run() {
        int x=0;

        while (true){
            /**
            * 分析:因为资源Resource是共享数据,当输入线程获取的cpu执行权时,第一次赋值name是weiAnKang,sex是男,
             *     第二次输入线程再次赋值name 是'丽丽'时,因为线程之间是cpu执行权的切换,此时输出线程获取cpu的执行权,
             *     输出线程的结果就是'丽丽====男',这样致使多线程操做同一个资源时出现的安全问题
            * */
            //todo:此时在输入线程加上同步锁依然没有解决,Resource共享数据同步的问题,出现这种问题应该考虑多个线程之间是否是判断同一个锁
             
            
            if (x==0){
                r.set("weiAnKang","nan");

            }else {
                r.set("丽丽","女女女");

            }

            x=(x+1)%2;
        }

    }
}
/**
 * Created by ZC-16-012 on 2018/2/2.
 *
 * 定义输出线程的任务
 */
public class OutputTask implements Runnable{

    Resource r;

    public OutputTask(Resource r){
        this.r=r;
    }

    @Override
    public void run() {
        while (true){        
            r.out();

        }
    }
}
/**
 * Created by ZC-16-012 on 2018/2/2.
 *
 * 线程之间的通讯
 *
 * 等待/唤醒机制
 * wait(): 让线程处于冻结状态,被wait的线程会被存储到线程池中
 * notify():唤醒线程池中的一个线程(任意的)
 * notifyAll():唤醒线程池中的全部线程
 * 这些方法必须定义在同步中,由于这些方法是用于操做线程的状态的方法,
 * 必需要明确到底操做的是那个锁上的线程
 *
 */
public class ThreadCommunicationDemo {

    public static void main(String[] args){

        Resource r= new Resource();

        InputTask input= new InputTask(r);
        OutputTask output= new OutputTask(r);

        //建立线程,并将线程的任务传递给线程对象
        Thread t1= new Thread(input);
        Thread t2= new Thread(output);

        //开启线程
        t1.start();
        t2.start();

    }
}

等待/唤醒机制
 * wait(): 让线程处于冻结状态,被wait的线程会被存储到线程池中
 * notify():唤醒线程池中的一个线程(任意的)
 * notifyAll():唤醒线程池中的全部线程
 * 这些方法必须定义在同步中,由于这些方法是用于操做线程的状态的方法,
 * 必需要明确到底操做的是那个锁上的线程

2.4 jdk1.5以后的java.util.concurrent.locks.Lock的应用

假若有多个线程进行读的操做,多个线程进行写的操做呢

/**
 * Created by ZC-16-012 on 2018/2/5.
 *
 * 生产者和消费者
 * while判断标记,解决了线程获取cpu执行权后,是否要执行
 * notifyAll 解决了本方线程必定会唤醒对方线程
 */
public class ResourceClass {

    private String name;

    private int  count=1;

    private boolean flag=false;

    private Lock lock= new ReentrantLock();//使用jdk1.5版本以后的锁对象

    //经过已有的锁对象获取该锁上的监视器对象,建立两组监视器,一组监视生产者,一组监视消费者
    Condition producerCon= lock.newCondition();

    Condition consumerCon= lock.newCondition();

    /**
    * 分析:0,1 是生产者线程,2 3是消费者线程,假设0先获取cpu的执行权,2线程消费,有一种状况当0,1线程
     *  t0(活),notify以后正好是 唤醒t1 线程,t1判断flag=true,t0,t1一直在while循环中等待,出现了死锁状况
     *  出现这种状况就是生成者线程唤醒的是另外一个生成者线程,应该生成者要保证唤醒的至少有消费者线程
    * */

    /**
    * 对于synchronized 修饰的同步代码块,锁是隐式的
     * 可使用interrupt()方法将线程从冻结状态强制恢复到运行状态中,让线程具备cpu的执行资格
     * 该强制动做会发生 InterruptedException 异常
     *
    * */
    public  void set(String name) {
        lock.lock();
        try {
            while (flag) {
                try {
//                    this.wait();// t0(活),notify 正好唤醒t1
                    //经过condition的await 让线程冻结
                    producerCon.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.name = name + count;
            count++;
            System.out.println(Thread.currentThread().getName() + "..生产者.." + this.name);
            flag = true;
//            notifyAll();//唤醒了全部线程池中等待的线程
            //经过condition的signalAll 唤醒消费者的全部线程
            consumerCon.signal();
        } finally {
            lock.unlock();//释放锁
        }

    }

    public  void out (){
        lock.lock();
        try {
            if (!flag) {
                try {
//                    this.wait();
                    consumerCon.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                System.out.println(Thread.currentThread().getName() + ".......消费者.." + this.name);
                flag = false;
//                notifyAll();
                //消费者唤醒全部生成者的线程
                producerCon.signal();
            }
        } finally {
            lock.unlock();
        }
    }

}
public class Producer implements Runnable{
    ResourceClass r;

    public Producer(ResourceClass r){
        this.r=r;
    }

    @Override
    public void run() {
        while (true){
            r.set("馒头");
        }
    }
}
public class Consumer implements Runnable{
    ResourceClass r;

    public Consumer(ResourceClass r){
        this.r = r;
    }

    @Override
    public void run() {
        while (true){
            r.out();
        }

    }
}
/**
 * Created by ZC-16-012 on 2018/2/5.
 */
public class ProducerAndConsumer {

    /**
    *  Thread.currentThread().toString() 会打印线程的名称,优先级,线程组
     *  线程的优先级 MAX_PRIORITY 10,MIN_PRIORITY 1, NORM_PRIORITY 5,数字越高,表示优先级越大,
     *  cpu执行该线程的几率也就越大,例如:t0.setPriority(Thread.MAX_PRIORITY); 将t0线程的优先级设置成最大
    *
    * */

    public static  void  main(String[] args){
        ResourceClass r= new ResourceClass();
        Producer pro= new Producer(r);
        Consumer con= new Consumer(r);
        Thread t0 = new Thread(pro);
        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(con);
        Thread t3 = new Thread(con);

        t0.start();
        /**
        * try {
                t0.join(); //t0线程要申请加入进来,运行。让这个临时线程先执行完
            } catch (InterruptedException e) {
             e.printStackTrace();
           }
        * */
        //t0.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
        /**
        *  t2.setDaemon(true) 设置成守护线程(后台线程),前台线程其余线程结束,这个线程也结束
        * */

        t3.start();



    }
}

以上例子即是多生产者多消费者的例子

3.0 volatile关键字

不少时候不会区分volatile和synchronized,先看一下这个例子

public class RunThread extends Thread{
    private  boolean flag = true;

    public  void  setFlag(boolean flag){
        this.flag=flag;
    }

    @Override
    public  void run() {
      while (flag){

      }
      System.out.println(Thread.currentThread().getName() + "...线程运行结束");
    }
    /**
    * 分析:main方法中建立了t0线程,休眠3s后,将t线程中的全局变量设置了false,主线程结束后,却没有打印t0线程中的 "...线程运行结束"
    * 说明t0线程一直while循环中运行,对于t0线程而言,flag的值并无改变
     *
     * 总结:在java中,每个线程都有一个内存区,其中存放着全部线程共享的主内存中的变量值的拷贝,当线程执行时,他在本身的工做
     * 内存区中操做这些变量,为了存取一个共享的变量,一个线程一般先获取锁定并去清楚它的内存工做区,把这些变量从全部线程的共享内存区中
     * 正确的装入到他本身所在的工做内存区中,当线程解锁时保证该工做内存区中变量的值写回到共享内存中
     *
     * volatile 关键字强制线程到主内存中(共享内存中)去读取变量,而不是去线程工做内存区中读取,从而实现了多个线程见的变量可见
    * */
    public static void main(String[] args) throws InterruptedException {
        RunThread t0= new RunThread();
        t0.start();
        Thread.sleep(3000);
        t0.setFlag(false);
        System.out.println("flag的值已经被设置成了false。。");
        Thread.sleep(1000);
        System.out.println(t0.flag);
    }

}

网上也搜了不少博客,https://www.cnblogs.com/yzwall/p/6661528.html 这遍博客说的很清楚

3.0 Atomic原子类

/**
 * Created by ZC-16-012 on 2018/2/8.
 */
public class VolatileNoAtomic extends Thread{

    @Override
    public void run() {
        addCount();
    }

    /**
    * volatile关键字具备可见性,并不具有原子性
     * Atomic原子类 能够保证结果是原子性,并不能保证方法自己是原子性
    * */

//    private volatile static  int count;

    private static  AtomicInteger count =new AtomicInteger(0);

    private static void addCount(){

            for (int i=0;i<1000;i++){
//                count++;
                count.incrementAndGet();
            }
            System.out.println(count);


    }

    public static void main(String[] args){
        VolatileNoAtomic[] arr= new VolatileNoAtomic[10];
        for (int i=0;i<10;i++){
            arr[i]=new VolatileNoAtomic();
        }
        for (int i=0;i<10;i++){
            arr[i].start();
        }
    }

}

能够经过这个例子看一下Atomic对象和 volatile关键字的一些区别

3.1 网上有一个联系题 有4个线程分别获取C D E F 盘的大小,第5个线程统计总大小

public class ContactThread {

    public static void main(String[] args){
        /**
        * CountDownLatch 是一个同步倒数计数器,构造时传入int参数,每调用一次countDown()方法
         * 计数器减1
        * */
        final CountDownLatch countDownLatch= new CountDownLatch(4);
        ExecutorService executorService= Executors.newFixedThreadPool(6);
        final DiskMemory diskMemory= new DiskMemory();
        for (int i=0;i<4;i++)
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    int timer = new Random().nextInt(5);
                    try {
                        Thread.sleep(timer * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    int diskSize = diskMemory.getSize();
                    System.out.printf("完成磁盘的统计任务,耗时%d秒,磁盘大小为%d.\n", timer, diskSize);
                    //统计每一个磁盘的大小
                    diskMemory.setSize(diskSize);
                    //任务完成后,计数器减一
                    countDownLatch.countDown();
                    System.out.println("count num=" + countDownLatch.getCount());
                }
            });

        //主线程一直被阻塞,直到全部的线程统计完
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("所有线程统计完,全部磁盘总大小.\n" + ",totalSize = " + diskMemory.getTotalSize());
        executorService.shutdown();
    }
}
/**
 * Created by ZC-16-012 on 2018/2/27.
 *
 * 定义磁盘对象
 */
public class DiskMemory {
    private int totalSize;

    public int getSize(){
        return (new Random().nextInt(3)+1)*100;
    }

    public void setSize(int size){
        totalSize+=size;
    }

    public int getTotalSize(){
        return totalSize;
    }
}

这边用到了java.util.concurrent.CountDownLatch的一个同步倒数计数器。

固然java并发编程是一个很复杂的技术,远远不止于这些,后面学习到会陆续更新,好比java线程池的运用。 

3.2synchronized关键字总结

上述synchronized关键字经过锁的机制,保证了多线程之间共享变量的安全。那么synchronized关键字何时释放锁?

要么synchronized里面的代码执行完,要么执行过程当中出现异常,synchronized 都将自动释放锁。synchronized 具备可重入性和不可中断性。

可重入性:指的是同一线程的外层函数获取到锁后,内层函数能够直接再次持有该锁。

不可中断性:指的是当前线程一旦持有该锁,其余线程只能等待,直到当前线程释放锁。

能够看出来,synchronized关键字 经过指令monitorenter,monitorexit等指令进行控制锁,monitorenter=0的时候说明该线程没有持有锁,一旦持有锁,就经过该计数器加1,进入内层函数也是加1,同理monitorexit 进行减1,执行完计算器等于0.

可见性:因为Java线程内存模型,每一个线程的线程内存 存储的是主内存(共享内存)变量的一个副本,可见性保证了当前线程内存存储的变量副本和主内存一致(读写)。

synchronized关键字进入锁以前,先把共享变量从主内存中读取出来,释放锁以前再把共享变量从当前线程内存中写到主内存中。

synchronized 的缺陷:

1.效率低:锁释放的状况少,试图得到锁时不能设置超时,不能中断一个正在试图得到锁的过程

2.不够灵活:加锁和释放锁的时机单一,好比读写锁,读的操做不会加锁,写的时候加锁

3.没法知道是否成功获取到锁。还应该避免死锁

相关文章
相关标签/搜索