2020-01-31-Java多线程

2020-01-31  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读