线程同步

1.线程状态转换

线程同步_第1张图片

2.使用线程

有三种使用线程的方法:

  • 实现 Runnable 接口
  • 实现 Callable 接口
  • 继承 Thread 类

实现 Runnable 接口例子

public class MyRunnable implements Runnable {
    public void run() {
        // ...
    }
}
public static void main(String[] args) {
    MyRunnable instance = new MyRunnable();
    Thread thread = new Thread(instance);
    thread.start();
}

实现 Callable 接口例子

public class MyThread extends Thread {
    public void run() {
        // ...
    }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
    MyCallable mc = new MyCallable();
    FutureTask ft = new FutureTask<>(mc);
    Thread thread = new Thread(ft);
    thread.start();
    System.out.println(ft.get());
}

继承 Thread 类例子

public class MyThread extends Thread {
    public void run() {
        // ...
    }
}
public static void main(String[] args) {
    MyThread mt = new MyThread();
    mt.start();
}

通常而言,实现接口会比继承Thread好。因为

  • Java不支持多重继承。继承了Thread类就无法继承其它类。但是可以实现多个接口
  • 类可能只要求可执行就行,继承整个Thread类开销过大

3.互斥同步

Java提供了两种锁机制来控制多个线程对共享资源的互斥访问。第一个是JVM实现的synchronize,另一个是 JDK 实现的 ReentrantLock。

synchronize可以作用于代码块,方法,静态方法,类

public synchronized void func () {
    // ...
}

ReentrantLock

public class LockExample {

    private Lock lock = new ReentrantLock();

    public void func() {
        lock.lock();
        try {
            for (int i = 0; i < 10; i++) {
                System.out.print(i + " ");
            }
        } finally {
            lock.unlock(); // 确保释放锁,从而避免发生死锁。
        }
    }
}

4.比较

1.锁的实现
synchronized 是JVM实现的,ReentrantLock 是JDK实现的。
2.性能
新版Java对synchronized进行了很多优化,例如自旋锁等,两者差异不大
3.暂停可中断
当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。

ReetrantLock可中断,而synchronized不行

4.公平锁
公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁

synchronized中的锁是非公平的,ReentrantLock 默认情况也是非公平的,但也可以是公平的。

5.锁绑定多个条件

一个ReentrantLock 可以同时绑定多个Condition对象

5.使用选择

除非需要使用ReentrantLock的高级功能,否则优先使用synchronized。因为synchronized是JVM实现的一种锁机制,JVM原生地支持它,而ReentrantLock不是所有的JDK版本都支持。并且使用synchronized不用担心没有释放锁而导致死锁的问题,因为JVM会确保锁的释放。

6.线程之间的协作

  • jion

在线程中调用另一个线程的join()方法,会将当前线程挂起,直到目标线程结束

  • wait() notify() notifyAll()

调用wait() 使得线程在等待某个条件满足时,线程在等待时会被挂起,当其他线程的运行使得某个条件满足时,其他线程会调用notify() 或者 notifyAll() 来唤醒被挂起的线程

使用wait挂起期间,线程会释放锁。这是因为,如果没有释放锁,那其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行notify,notifyAll来唤醒挂起的线程,造成死锁。

wait和sleep的区别:
1.wait是Object的方法,而sleep是Thread的静态方法
2.wait会释放锁,sleep不会

  • await signal signalAll

java.util.concurrent 类库中提供了Condition类来实现线程之间的协调。可以在Condition上调用await方法使线程等待,其他线程调用signal或signalAll方法唤醒等待的线程

相比于wait这种等待方式,await可以指定等待的条件,更加灵活

使用Lock来获取一个Condition对象。

7.CountDownLatch

用来控制一个线程等待多个线程。

维护了一个计数器cnt,每次调用countDown方法会让计数器的值减1,减到0的时候,那些因为调用await方法而在等待的线程就会被唤醒。

线程同步_第2张图片

8.CyclicBarrier

用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。

线程同步_第3张图片

和CountdownLatch相似,都是通过维护计数器来实现的。线程执行await方法之后计数器会减1,并进行等待,直到计数器为0,所有调用await方法而在等待的线程才能继续执行。

CyclicBarrier和CountdownLatch的一个区别是,CycliBarrier的计数器通过reset方法可以循环使用,所以它才叫做循环屏障。

9.Semaphore

Semaphore 类似于操作系统中的信号量,可以控制对互斥资源的访问线程数。

你可能感兴趣的:(线程同步)