一、创建线程的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(); } } } }
二、线程池
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(); } } }
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); } }
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"); } } }
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); } }
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"); } } }
五、线程间协作
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(); } } }
上面是针对内置锁所实现的线程间协作,使用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(); } } }
六、死锁
所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
锁发生有四个条件,必须每个条件都满足才有可能发生死锁,只要破坏其中一个条件就不会死锁。
(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 + "的哲学家吃到饭了!"); } } } }
解决方案一:
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 + "的哲学家吃到饭了!"); } } } }
解决方案二:
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(); } } }