美文网首页
2020-01-31-Java多线程

2020-01-31-Java多线程

作者: 耿望 | 来源:发表于2020-01-31 22:43 被阅读0次

    线程生命周期

    线程生命周期.jpg

    NEW:创建了一个线程对象,但是还没有调用start()方法。此时称为初始状态NEW
    RUNNABLE:调用了start()方法后,进入就绪状态,此时已经进入run()方法
    BLOCKED:如果run()方法中有用synchronized包含的代码块,则需要等其他线程释放锁对象才能进入synchronized代码块。
    BLOCKED状态是不能被interrupt打断的,只有其他线程释放锁之后,才能回到RUNNABLE状态。
    WAITING:如果线程主动调用wait()或join()方法,则进入WAITING状态,
    直到join()的线程返回;
    或者wait()的对象执行notify()或notifyAll()方法被唤醒;
    或者其他线程调用interrupt()方法。
    如果没有其它线程唤醒,WATING状态会无限期等待下去,形成死锁。
    TIMED_WAITING:如果线程主动调用sleep(long millis),wait(long millis),或join(long millis)方法,则进入TIMED_WAITING状态,
    直到超时返回或join()的线程返回;
    或者wait()的对象执行notify()或notifyAll()方法被唤醒;
    或者其他线程调用interrupt()方法。
    TERMINATED:如果run()方法执行完毕,或者线程异常退出,进入TERMINATED状态。进入此状态后不能再调用start()方法。

    线程等待

    1. sleep()和wait()
      sleep()没有锁的概念,wait()有。wait()方法会释放线程本身持有的锁。
      sleep()方法会进入TIMED_WAITING,直到超时返回,跟锁没有关系,如果sleep()方法被synchronized代码块包含,线程本身持有的锁不会释放。
      不带参数的wait()方法会进入WAITING状态,无限期等待,直到满足条件,或者interrupt()方法被调用。
      带参数的wait(long millis)方法会进入TIMED_WAITING状态,能实现超时返回。
    2. sleep()和join()
      sleep()和join()都没有锁的概念,只是等待返回的条件不一样。
      不带参数的join()方法会进入WAITING状态,无限期等待,直到该线程返回,或者interrupt()方法被调用。
      带参数的join(long millis)方法会进入TIMED_WAITING状态,能实现超时返回。
    3. sleep()和yield()
      sleep()和yield()都没有锁的概念。
      yield()方法不会改变线程的状态,只是让出CPU使用权。有可能下一次CPU会立即执行。
      sleep()方法会让出CPU使用权,直到超时返回。
    4. sleep()和suspend()
      suspend()方法不会改变线程状态,依然是RUNNABLE,但是被暂停了不会执行,只有通过resume()方法能够唤醒。suspend()和resume()已经不推荐使用了。

    线程安全

    多线程并发可能会遇到的问题是同样的输入,每次的输出不一样,需要了解Java内存模型(JMM)的几个概念:

    1. 原子性:操作中途是不能被其他线程打断的;
    2. 可见性:一个线程对共享变量的修改,其他线程是立即可见的;
    3. 有序性:在多核处理器的环境下,代码的执行顺序是没保障的,需要其他条件来保证。
      在Java内存模型中,允许编译器和处理器对指令进行重排序。重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。


      内存模型.jpg

    为了实现操作的原子性,可见性和有序性,通常会用到锁。
    Java提供了synchronized关键字来实现内部锁。被 synchronized 关键字修饰的方法和代码块叫同步方法和同步代码块。

    1. synchronized 方法
      锁的对象是每个类实例,只有两个线程对同一个类实例的synchronized方法进行访问才会竞争锁资源,分别对两个实例的同一个synchronized方法访问是不影响的。
      例如下面这个例子:
      当两个线程分别去访问同个对象的两个synchronized方法时,就可能出现时序问题。
      如果count()方法先执行,两个线程都能够正常运行。
      如果waitCount()方法先执行,就会出现死锁,waitCount线程进入死循环,一直在TIMED_WAITING状态,count线程没有获取到对象锁,一直在BLOCKED状态。
    public class ThreadTest {
        
        private volatile int count = 0;
        
        public synchronized void count() {
            for (int i = 0 ; i < 10; i++) {
                count++;
            }
            System.out.println(Thread.currentThread().getName() + " count=" + count);
        }
        
        public synchronized void waitCount() {
            System.out.println(Thread.currentThread().getName() + " wait start");
            while (count < 10) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
            System.out.println(Thread.currentThread().getName() + " wait finish");
        }
    }
    
            ThreadTest test = new ThreadTest();
            Thread thread1 = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    test.waitCount();
                }
            }, "thread1");
            Thread thread2 = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    test.count();
                }
            }, "thread2");
    

    synchronized方法保证了操作的原子性,所以synchronized内部最好不要对共享变量使用一些while判断,防止出现死循环。

    1. synchronized代码块
      synchronized代码块跟synchronized方法是类似的,只不过可以指定锁的对象,使用比较灵活,可以把尽可能少的操作放进同步代码,避免其他线程等待。
      例如上面的例子,只需要把count()的同步方法改成同步代码块,就能解决死循环的问题。
    public class ThreadTest {
        
        private volatile int count = 0;
        
        public void count() {
            for (int i = 0 ; i < 10; i++) {
                count++;
            }
            synchronized (this) {
                System.out.println(Thread.currentThread().getName() + " count=" + count);
            }
        }
        
        public synchronized void waitCount() {
            System.out.println(Thread.currentThread().getName() + " wait start");
            while (count < 10) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
            System.out.println(Thread.currentThread().getName() + " wait finish");
        }
    }
    
    1. 显式锁
      常用的显式锁有ReentrantLock,实现了Lock接口的四个方法:
      lock()和lock.unlock()方法类似synchronized,等待锁的过程中线程阻塞,只不过synchronized是进入BLOCKED状态,lock()是进入WAITING状态。
      lockInterruptibly()可以被中断,需要catch InterruptedException异常。
      tryLock()成功获取锁返回true,否则false,不会阻塞。
      tryLock(long time, TimeUnit unit),阻塞等待一段时间,然后返回。
      此外还有读写锁,这里不介绍,可以看下ReadWriteLock这个类。
    2. volatile
      一般变量保存在高速缓存区,不会立刻同步到主内存,导致不同线程间的数据不同步,使用volatile关键字就是为了保证数据的可见性。

    下面写了一个简单例子,两个线程thread1和thread2会竞争lock对象锁

    package com.one.thread;
    
    import java.util.Random;
    
    import com.one.log.Log;
    
    public class ThreadTest {
        
        private volatile int count = 0;
        private Log log = new Log("ThreadTest");
        private Object lock = new Object();
        
        public void run() {
            thread1.start();
            thread2.start();
        }
        
        private void sleepRandom() {
            try {
                Thread.currentThread().sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                log.log("sleep interrupt");
            }
        }
        
        private Thread thread1 = new Thread(new Runnable() {
            
            @Override
            public void run() {
                try {
                    thread2.join(1000);
                } catch (InterruptedException e) {
                    log.log("thread1 joinInterrupt");
                }
                for (int i = 0; i < 10; i++) {
                    synchronized (lock) {
                        sleepRandom();
                        count++;
                        log.log("count=" + count + " thread2=" + thread2.getState());
                        lock.notifyAll();
                    }
                }
            }
        }, "thread1");
        
        private Thread thread2 = new Thread(new Runnable() {
            
            @Override
            public void run() {
                synchronized (lock) {
                    sleepRandom();
                    log.log("thread1=" + thread1.getState());
                    while (count < 6) {
                        try {
                            lock.wait();
                            sleepRandom();
                            log.log("waitting count=" + count+ " thread1=" + thread1.getState());
                        } catch (InterruptedException e) {
                            log.log("thread2 waitInterrupt");
                            break;
                        }
                    }
                    log.log("thread2 waitFinished");
                }
            }
        }, "thread2");
    }
    
    

    这两个线程的状态如下


    Thread.png

    如果代码中的join()方法没有加超时,就会形成死锁。
    参考:

    https://www.cnblogs.com/dolphin0520/p/3920373.html
    https://www.cnblogs.com/trust-freedom/p/6606594.html

    相关文章

      网友评论

          本文标题:2020-01-31-Java多线程

          本文链接:https://www.haomeiwen.com/subject/wounthtx.html