面试准备--多线程

一、创建线程的3种方式

1.继承Thread类 重写run方法

public class CreateThread extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new CreateThread().start();
        }
        
        System.out.println("Main Thread!!!");
    }
}

2.实现Runnable接口 实现run方法 

public class CreateThread2 implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(new CreateThread2());
            t.start();
        }
        
        System.out.println("Main Thread!!!");
    }
}

3.创建带返回值的线程 --- 实现Callable接口 实现call方法 

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 创建带有返回值的线程
 */
public class CreateThread3 implements Callable {
    @Override
    public Object call() {
        System.out.println(Thread.currentThread().getName());
        return "print: " + Thread.currentThread().getName();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Callable callable = new CreateThread3();
            //使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
            FutureTask task = new FutureTask(callable);
            Thread thread = new Thread(task);
            thread.start();
            try {
                //调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
                //调用task.get() 主线程会阻塞,直到新创建的线程执行结束
                System.out.println(task.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
}
View Code

二、线程池

 3个常用线程池

//线程数固定的线程池,一次性支付高昂的创建线程的开销,之后再使用的时候就不再需要这种开销
ExecutorService fixedTP = Executors.newFixedThreadPool(5);
//线程数量为1的线程池,先后顺序执行,由于任务之间没有并发执行,因此提交到线程池种的任务之间不会相互干扰
ExecutorService singleTP = Executors.newSingleThreadExecutor();
//缓存 如果有任务,且池中没有空闲线程,就创建,如果一个线程60秒内没被使用,就会被销毁
ExecutorService cachedTP = Executors.newCachedThreadPool();
ExecutorService 是所有的线程池都实现了的接口

线程池创建线程

for (int i = 0; i < 5; i++) {
            fixedTP.execute(new CreateThread2());
            singleTP.execute(new CreateThread2());
            cachedTP.execute(new CreateThread2());
        }

关闭线程池

//执行中的任务还是会执行,线程池不会再接收其它任务了
cachedTP.shutdown();
//向所有线程发送中断信号(如果线程选择忽略,这个线程还是会继续执行),并且不再执行排队中的任务,将排队中的任务作为返回值返回(List<Runnable>)
cachedTP.shutdownNow();

如果是通过实现Callable创建的带返回值的线程,则要通过submit方法 该方法返回一个Future,用于得到线程返回的值

Future future = cachedTP.submit(new CreateThread3());
        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

三、锁

1.内置锁 synchronized

两种使用方法

a.作为修饰词定义在方法中

public synchronized void test() {
    //临界区
}

b.指定一个对象,后面接一个代码块

public void test() {
    synchronized(object) {
        //临界区
    }
}
每个内置锁 synchronized 都对应一个对象,如果是第一种方法 相当于获取当前对象的锁 --- synchronized(this){}

1个线程得到了这个对象的锁,其它线程就只能等待,这样就达到了同步的目的

分析同步问题的时候,记住2个问题:1.锁的对象是谁 2.谁持有了锁 进一步解释:哪条线程获得哪个对象的锁

另外,synchronized是可重入的锁 -- 一个线程对同一个对象的锁可以反复获得

如果synchronized修饰的是static,那么锁定的就不是当前对象而是当前类对象,也就是ClassName.class对象

2.显示锁 Lock
Lock是一个接口,实现了Lock接口的类也能实现的锁的功能
ReentrantLock类是Lock接口的一个实现
class IncreaseThread implements Runnable {
    private static Lock lock = new ReentrantLock();
    public void run() {
        for(int i=0; i <100000; i++){
            lock.lock();
            try {
                //连界区
            }
            finally {
                lock.unlock();
            }
        }
    }
}

把lock定义成static,这样不同线程就共享lock对象,如果不是同一个lock对象,获取的就不同一个锁,就不会起到同步作用了。

使用lock设置的临界区从调用lock()方法开始,到调用unlock()方法结束,把连界区放在try里,把unlock放在finally里,即使临界区里抛出异常,也能保证释放锁,避免出现死锁

tryLock():试图获取锁,如果锁没有被占用则获得锁,并返回true,继续执行下面的代码;如果锁被占用,则返回立即false(不会阻塞),继续执行下面的代码

tryLock(long time, TimeUnit unit):与tryLock()类似,这个方法可以指定等待锁的最长时间,在这段时间内当前线程会被阻塞。如果时间内获得了锁则返回true并提前结束阻塞,反之返回false。

/**
 * 子线程获得锁等待3秒释放,主线程尝试获得锁,等待2秒
 * 最后输出:
 * 子线程拿到锁
 * 主线程没有拿到锁
 * 子线程释放锁
 */
public class ThreadLock {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService tp = Executors.newCachedThreadPool();
        tp.execute(new TThread2());
        tp.shutdown();
        Thread.sleep(1000);
        //if(TThread2.lock.tryLock()) { //如果锁没被占用,获得锁,返回true,继续执行
        if(TThread2.lock.tryLock(2, TimeUnit.SECONDS)) { //如果锁被占用,等待2秒,2秒内获得锁,获得锁,马上返回true,继续执行
            try {
                System.out.println("主线程拿到锁");
            } finally {
                TThread2.lock.unlock();
            }
        }else {
            System.out.println("主线程没有拿到锁");
        }

    }
}

class TThread2 implements Runnable {
    public static Lock lock = new ReentrantLock();
    @Override
    public void run() {
        lock.lock();
        try {
            System.out.println("子线程拿到锁");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("子线程释放锁");
            lock.unlock();
        }
    }
}
View Code

Lock没有锁对应的对象,因此synchronized关键字和显式锁之间不能产生互斥效果

内置锁和显示锁都是非公平锁--不是按照访问锁的顺序,先到先得;而是每个线程都尝试获得锁

三、volatile

Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成

原子性:

即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

a=10 是原子性的   a=b 和 a=a+1就不是原子性的,都需要先去读取变量再赋值,有2步操作

可见性:

指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

多线程同时修改一个共享变量,就可能出现可见性问题,线程1对变量i修改了之后,线程2没有立即看到线程1修改的值。

有序性:

即程序执行的顺序按照代码的先后顺序执行

在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

volatile关键字

volatile是用来修饰共性变量(类的成员变量、类的静态成员变量)的,一旦变量被volatile修饰,那么就具备了两层语义:

1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

2)禁止进行指令重排序。

四、线程终结

线程的五个状态:

1.新建:线程在被创建时会暂时处于这种状态,此时系统为线程分配资源并对其进行初始化

2.就绪:此时线程已经可以运行,只是系统没有为其分配CPU时间。

3.运行:系统为线程分配了CPU时间,线程处于运行状态

4.阻塞:线程由于等待I/O等原因无法继续运行,等待结束后就又进入就绪状态了,阻塞时系统不会再为其分配CPU时间。

5.死亡:线程执行完所有的代码,此时线程不不可以再调度

上面五种状态中,只有在运行和阻塞状态时才有被终结的机会,其它状态时都无法终结。

A.运行时终结

1.用volatile定义全局变量 volatile boolean run = true

线程1每次循环时,判断run是否为true,然后另一个线程将run设为false,这样线程1就能终结

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadShutDown {
    public static volatile boolean run = true;

    public static void main(String[] args) {
        ExecutorService tp = Executors.newCachedThreadPool();
        tp.execute(new TestThread());
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        run = false;
        tp.shutdown();
    }
}

class TestThread implements Runnable {
    int value = 0;
    @Override
    public void run() {
        while(ThreadShutDown.run) {
            value++;
        }
        System.out.println(value);
    }
}
View Code

2.发送中断信号

a.调用ExcutorService 的shutdownNow方法,但是此方法会向线程池中的所有线程发送中断信号

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 运行时中断,发送中断信号,调用ExecutorService的shutdownNow()
 * InterruptableThread可以中断,AlwaysRunThread会一直运行
 */
public class ThreadShutDown2 {
    public static void main(String[] args) {
        ExecutorService tp = Executors.newCachedThreadPool();
        tp.execute(new InterruptableThread());
        tp.execute(new AlwaysRunThread());
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tp.shutdownNow();
    }
}

class InterruptableThread implements Runnable {
    private int value = 0;

    @Override
    public void run() {
        Thread currentThread = Thread.currentThread();
        while(!currentThread.isInterrupted()) {
            value++;
        }
        System.out.println(value);
    }
}

class AlwaysRunThread implements Runnable {

    @Override
    public void run() {
        while (true) {
            System.out.println("run run");
        }
    }
}
View Code

b.使用submit()方法返回Future对象,调用Funture的cancel方法能给具体某个线程发送中断信号

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * 运行时中断,发送中断信号,调用Future的cancel()给具体线程发送中断信号
 * InterruptableThread可以中断,AlwaysRunThread会一直运行
 */
public class ThreadShutDown3 {
    public static void main(String[] args) {
        ExecutorService tp = Executors.newCachedThreadPool();

        Future f1 = tp.submit(new InterruptableThread());
        Future f2 = tp.submit(new AlwaysRunThread());

        tp.shutdown();

        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        f1.cancel(true);
        f2.cancel(true);
    }
}
View Code

 B.阻塞时终结

造成线程阻塞的原因有如下三种:

1. 线程内部调用了Thread.sleep()方法使线程进入休眠状态或者调用Object.wait()使线程被挂起

发送中断信号

2. 线程在等待I/O

关闭IO流

3. 线程在试图获取锁的时候,锁已经被其它线程获取,这时线程会被阻塞。

内部锁synchronized在阻塞时无法终结线程,显示锁Lock,可以通过方法lock.lockInterruptibly()生成一个可中断的锁,通过发送中断信号,能终结阻塞的线程

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadShutDown4 {
    public static InputStream inputStream;
    public static void main(String[] args) throws IOException {
        ExecutorService tp = Executors.newCachedThreadPool();
        tp.execute(new sleepBlockedThread());
        new ServerSocket(8010);
        inputStream = new Socket("localhost", 8010).getInputStream();
        tp.execute(new IOBlockedThread());
        Lock lock = new ReentrantLock();
        lock.lock();
        tp.execute(new LockBlockedThread(lock));
        tp.shutdownNow();
        inputStream.close();
    }
}

/**
 * 线程内部调用了Thread.sleep()方法使线程进入休眠状态或者调用Object.wait()使线程被挂起
 * 发送中断信号能终结
 */
class sleepBlockedThread implements Runnable {

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            System.out.println("SleepThread interrupted");
        }
    }
}

/**
 * 线程在等待I/O
 * 关闭IO流能终结
 */
class IOBlockedThread implements Runnable {

    @Override
    public void run() {
        try {
            ThreadShutDown4.inputStream.read();
        } catch (IOException e) {
            //e.printStackTrace();
        }
        System.out.println("IOThread interrupted");
    }
}

/**
 * 线程在试图获取锁的时候,锁已经被其它线程获取,这时线程会被阻塞
 * lock.lockInterruptibly()代替lock.lock()
 * lock.lockInterruptibly()方法实现一个可中断的锁
 */
class LockBlockedThread implements Runnable {

    private Lock lock;
    public LockBlockedThread(Lock lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        try {
            //lock.lock();
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            System.out.println("LockThread interrupted");
        }
    }
}
View Code

五、线程间协作

Java提供了线程间合作的机制,即Object.wait()方法、Object.notify()和Object.notifyAll()方法。

wait()方法:使当前线程阻塞,等待其它线程调用notify()方法,释放当前获取的锁

notify()方法:唤醒一个等待着的线程,这个线程唤醒之后尝试获取锁,其它线程继续等待。

notifyAll()方法:唤醒所有等待着的线程尝试获取锁,这些线程排队等待锁。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 使用内置锁协作synchronized
 * 线程1输出AC,线程2输出B;C必须要线程2输出B之后再输出,B必须要线程1输出A后再输出
 * 最后输出结果是 A B C
 */
public class ThreadWaitNotify {
    public static Object obj = new Object();

    public static void main(String[] args) {
        ExecutorService tp = Executors.newCachedThreadPool();
        //确保线程2先拿到锁
        tp.execute(new TestThreadB());
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tp.execute(new TestThreadAC());
        tp.shutdown();
    }
}

/**
 * 线程输出AC
 */
class TestThreadAC implements Runnable {

    @Override
    public void run() {
        synchronized (ThreadWaitNotify.obj) {
            System.out.println("A");
            //输出A后,唤醒线程2
            ThreadWaitNotify.obj.notify();
            try {
                //阻塞后,释放锁
                ThreadWaitNotify.obj.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //线程2运行完释放锁后,输出C
            System.out.println("C");
        }
    }
}

/**
 * 线程输出B
 */
class TestThreadB implements Runnable {

    @Override
    public void run() {
        synchronized (ThreadWaitNotify.obj) {
            try {
                //获取锁后调用wait方法阻塞,并释放锁
                ThreadWaitNotify.obj.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("B");
            //输出B后,唤醒线程1
            ThreadWaitNotify.obj.notify();
        }
    }
}
View Code

上面是针对内置锁所实现的线程间协作,使用synchronized获得对象的锁后,才能用wait/notify/notifyall方法

如果是显示锁Lock,不能获取对象的锁,因此就不能上述的方法

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 显示锁协作Lock
 */
public class ThreadAwaitSignal {
    public static Lock lock = new ReentrantLock();
    public static Condition condition;

    public static void main(String[] args) {
        //显示锁Lock需要调用lock.newCondition()获得一个Condition对象,然后操作Condition来实现多线程的协作
        condition = lock.newCondition();

        ExecutorService tp = Executors.newCachedThreadPool();
        tp.execute(new TestThreadB1());
        tp.execute(new TestThreadAC1());
        tp.shutdown();
    }
}

class TestThreadAC1 implements Runnable {

    @Override
    public void run() {
        ThreadAwaitSignal.lock.lock();
        try {
            System.out.println("A");
            ThreadAwaitSignal.condition.signal(); //对应Object.notify()
            ThreadAwaitSignal.condition.await(); //对应Object.wait()
            System.out.println("C");
        } catch (InterruptedException e) {

        } finally {
            ThreadAwaitSignal.lock.unlock();
        }
    }
}

class TestThreadB1 implements Runnable {

    @Override
    public void run() {
        ThreadAwaitSignal.lock.lock();
        try {
            ThreadAwaitSignal.condition.await();
            System.out.println("B");
            ThreadAwaitSignal.condition.signal();
        } catch (InterruptedException e) {

        } finally {
            ThreadAwaitSignal.lock.unlock();
        }
    }
}
View Code

六、死锁

所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

锁发生有四个条件,必须每个条件都满足才有可能发生死锁,只要破坏其中一个条件就不会死锁。

(1)互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

(2)不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。

(3)请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

(4)循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, ..., pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的资源被P0占有

死锁的例子:哲学家进餐问题

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 哲学家进餐问题
 * 5个哲学家坐在一个桌子上,桌子上有5根筷子,每个哲学家的左手边和右手边各有一根筷子
 * 5个哲学家都拿自己左手边的筷子,都在等右手边的筷子,导致死锁
 */
public class ThreadDiedLock {
    //模拟5根筷子
    public static Object[] chopsticks = new Object[5];
    
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            chopsticks[i] = new Object();
        }

        ExecutorService tp = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            tp.execute(new Philosopher(i));
        }

        tp.shutdown();
    }
}

//每个哲学家都是一个线程
class Philosopher implements Runnable {
    private int id;

    public Philosopher(int id) {
        this.id = id;
    }
    @Override
    public void run() {
        int left = id; //左手边筷子的编号
        int right = (id + 1) % 5; //右手边筷子的编号

        //首先获取左手边筷子的锁
        synchronized (ThreadDiedLock.chopsticks[left]) {
            System.out.println("编号为" + id + "的哲学家拿到左手边的筷子了!");
            try {
                //确保每个哲学家都拿了自己左手边的筷子
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //然后获取右手边筷子的锁
            synchronized (ThreadDiedLock.chopsticks[right]) {
                System.out.println("编号为" + id + "的哲学家拿到右手边的筷子了!");
                System.out.println("编号为" + id + "的哲学家吃到饭了!");
            }
        }
    }
}
View Code

解决方案一:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 改变顺序,比如让第5个哲学家先拿右边的筷子再拿左边的筷子,其余4个哲学家还是先拿左边的再拿右边的。
 * 避免死锁方式一:指定获取锁的顺序,并强制线程按照指定的顺序获取锁
 */
public class ThreadDiedLock2 {
    //模拟5根筷子
    public static Object[] chopsticks = new Object[5];

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            chopsticks[i] = new Object();
        }

        ExecutorService tp = Executors.newCachedThreadPool();
        for (int i = 0; i < 4; i++) {
            tp.execute(new Philosopher2(i));
        }

        tp.shutdown();

        int left = 4;
        int right = 0;
        synchronized (chopsticks[right]) {
            System.out.println("编号为" + 4 + "的哲学家拿到右手边的筷子了!");
            synchronized (ThreadDiedLock2.chopsticks[left]) {
                System.out.println("编号为" + 4 + "的哲学家拿到右手边的筷子了!");
                System.out.println("编号为" + 4 + "的哲学家吃到饭了!");
            }
        }
    }
}

//每个哲学家都是一个线程
class Philosopher2 implements Runnable {
    private int id;

    public Philosopher2(int id) {
        this.id = id;
    }
    @Override
    public void run() {
        int left = id; //左手边筷子的编号
        int right = (id + 1) % 5; //右手边筷子的编号

        //首先获取左手边筷子的锁
        synchronized (ThreadDiedLock2.chopsticks[left]) {
            System.out.println("编号为" + id + "的哲学家拿到左手边的筷子了!");
            //然后获取右手边筷子的锁
            synchronized (ThreadDiedLock2.chopsticks[right]) {
                System.out.println("编号为" + id + "的哲学家拿到右手边的筷子了!");
                System.out.println("编号为" + id + "的哲学家吃到饭了!");
            }
        }
    }
}
View Code

解决方案二:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 哲学家必须2只手都可以拿到筷子才拿起筷子吃饭
 */
public class ThreadDiedLock3 {
    //模拟5根筷子
    volatile public static Boolean[] chopsticks = new Boolean[5];

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            chopsticks[i] = false;
        }

        ExecutorService tp = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            tp.execute(new Philosopher3(i));
        }

        tp.shutdown();
    }
}

//每个哲学家都是一个线程
class Philosopher3 implements Runnable {
    private int id;

    public Philosopher3(int id) {
        this.id = id;
    }
    @Override
    public void run() {
        int left = id; //左手边筷子的编号
        int right = (id + 1) % 5; //右手边筷子的编号

        synchronized (this) {
            while (ThreadDiedLock3.chopsticks[left] || ThreadDiedLock3.chopsticks[right]) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("编号为" + id + "的哲学家2只手都拿到筷子了,可以吃饭了!");
            ThreadDiedLock3.chopsticks[left] = false;
            ThreadDiedLock3.chopsticks[right] = false;
            notifyAll();
        }
    }
}
View Code

猜你喜欢

转载自www.cnblogs.com/caishengkai/p/10126523.html
今日推荐