多线程
进程和线程
区别
- 定义:(1) 单核处理器相当于一个CPU = 一个车间,多核处理器相当于多个CPU = 一座工厂 (一座工厂包含多个车间)每个车间都相当于一个进程,进程是程序的一次执行过程,是程序在执行过程汇总分配和管理资源的基本单位,是操作系统资源分配的基本单位,每个进程都有自己的地址空间;(2) 每个车间都有工人,每个工人都相当于一个线程,线程是CPU调度和执行的基本单位;
- 联系:线程是进程的一部分,一个线程只能存在于一个进程内,而一个进程有多个线程,并且至少有一个线程在操作系统中能够同时运行多个进程,同一个进程可以有多个线程同时执行
- 开销:进程有独立的代码和数据空间(程序上下文切换),进程(程序)切换开销较大;线程可以看作是轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换开销小
- 内存分配:系统在运行的时候会为每个进程分配不同的内存空间;对线程而言,系统不会为线程分配空间(线程所具有空间来源于所属进程),线程之间只能共享资源
线程状态
-
新建:新建一个线程对象
-
就绪:线程被创建后,被其他线程调用start()方法后,该状态线程可以位于运行线程池汇总,等待被线程调度选中,获取CPU的使用权
-
运行:就绪状态的线程获取了CPU的时间片,执行程序代码
-
阻塞:运行中的线程遇到某些事件放弃CPU使用权,暂时停止运行。直到线程重新进入就绪状态,才有机会获取CPU使用权进入运行状态。阻塞类别:
4.1 等待阻塞:运行线程遇到wait()方法,JVM会把线程放入等待队列,直到遇到notify(),或notifyAll()才会被唤醒进入就绪状态
4.2 同步阻塞 :获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把此线程放入锁池,等待其他线程释放锁,本线程由就绪到运行抢占锁后继续运行
4.3 其他阻塞:运行线程遇到sleep()或join()或发出I/O请求时,JVM会把线程置为阻塞状态。当sleep()或join()超时或I/O处理完毕,线程重新进入就绪状态
-
死亡:线程执行结束或者因异常退出
线程的流程图
创建线程的四种方式
-
继承Thread类
public class ThreadTest { public static void main(String[] args) { Demo demo = new Demo(); demo.start(); } static class Demo extends Thread{ public void run(){ System.out.println("继承Thread创建线程"); } } }
-
实现Runnale接口
public class RunnableTest { public static void main(String[] args) { Demo demo = new Demo(); Thread thread = new Thread(demo); thread.start(); } static class Demo implements Runnable { @Override public void run() { System.out.println("实现Runnable接口创建线程"); } } }
-
实现Callable接
(1) 创建一个类实现Callable接口
(2) 重写call()方法,注意有返回值
(3) 创建FutureTask,调用该对象的get()方法可获取call()方法的返回值
public class CallableTest { public static void main(String[] args) { FutureTask<String> futureTask = new FutureTask<>(new Demo("继承Callable创建线程")); Thread thread = new Thread(futureTask); thread.start(); try { String string = futureTask.get(); System.out.println(string); } catch (InterruptedException | ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } static class Demo implements Callable<String> { private String str; public Demo(String str) { this.str = str; } @Override public String call() throws Exception { return str; } } }
-
使用线程池创建
//使用Executors框架创建线程 public class ThreadPool { public static void main(String[] args) { // ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();//不重复线程 // ExecutorService newCachedThreadPool = Executors.newSingleThreadExecutor();//一个线程 ExecutorService newCachedThreadPool = Executors.newFixedThreadPool(2);//2个线程执行 newCachedThreadPool.execute(new WorkTask()); Future submit = newCachedThreadPool.submit(new Demo()); try { Object object = submit.get(); System.out.println("实现Callable接口 创建线程:" + object); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } newCachedThreadPool.shutdown(); } //实现Runnable创建线程 static class WorkTask implements Runnable{ @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ "输出结果"); } } //实现Callable创建线程 static class Demo implements Callable{ @Override public String call() throws Exception { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } String str = Thread.currentThread().getName()+ "输出结果"; return str; } } }
//使用ThreadPoolExecutor框架创建线程 public class ThreadPoolTest { public static void main(String[] args) { //创建线程池 int corePoolSize(线程池大小) int maximumPoolSize(线程池最大容量) keepAliveTime(多久没有任务执行就关闭线程) workQueue(一个阻塞队列,用来存储等待执行的任务) ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); //计数器 (目的:子线程执行完再执行主线程)new CountDownLatch(2)判定有俩线程计数为2 CountDownLatch latch = new CountDownLatch(2); executor.execute(new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + "子线程1"); } catch (Exception e) { }finally { //计数器减一 latch.countDown(); } } }); executor.execute(new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + "子线程2"); } catch (Exception e) { }finally { //计数器减一 latch.countDown(); } } }); try { //主线程等待,直到子线程结束,latch为0后主线程再执行 latch.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } executor.shutdown(); System.out.println("主线程"); } }
线程通信
线程通信的模型主要分两种:共享内存和消息传递
方式一:volatile
基于volatile关键字实现线程通讯是体现共享内存的思想。多个线程监听一个变量,当这个变量发生变化时,线程能感知并执行相应业务。备注:VOLATILE 关键字并不能保证线程的安全性
public class VolatileTest {
private volatile static int temp = 10;
public static void main(String[] args) {
Thread A = new Thread(new ThreadA());
Thread B = new Thread(new ThreadB());
A.start();
B.start();
}
static class ThreadA implements Runnable {
@Override
public void run() {
while (temp > 0) {
System.out.println("线程A:" + Thread.currentThread().getName() + "正在执行");
temp--;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
static class ThreadB implements Runnable {
@Override
public void run() {
while (true) {
if (temp == 5) {
System.out.println("线程B:" + Thread.currentThread().getName() + "响应线程A后执行");
break;
}
}
}
}
}
方式二:wait() 和 notify()
注意:wait()方法会释放锁,notify()不会释放锁,会继续执行完知道结束。被唤醒后继续执行wait()方法后内容,但共享变量会取主内存值。这俩都是Object类中的方法,在调用这俩方法前必须先获得锁,这限制其使用场合,必须在同步代码块中。其次,当等待队列有多个线程时,notify()只能随机唤醒一个线程,不能唤醒指定线程。还有就是必须wait在前notify在后才能正常唤醒。
public class VolatileTest {
private volatile static int temp = 10;
static Object ob = new Object();
public static void main(String[] args) {
Thread A = new Thread(new ThreadA());
Thread B = new Thread(new ThreadB());
A.start();
B.start();
}
static class ThreadA implements Runnable {
@Override
public void run() {
synchronized (ob) {
while (temp > 0) {
System.out.println("线程A获得锁:" + Thread.currentThread().getName() + "正在执行temp--" + temp );
temp--;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (temp == 5) {
try {
System.out.println("线程A即将等待");
ob.wait();
System.out.println("线程A被线程B唤醒,此时temp = " + temp );
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
static class ThreadB implements Runnable {
@Override
public void run() {
synchronized (ob) {
while (temp>0) {
ob.notify();
temp--;
System.out.println("线程B:" + Thread.currentThread().getName() + "获得锁,并执行:temp--" + temp );
}
}
}
}
}
输出结果:
线程A获得锁:Thread-0正在执行temp--10
线程A获得锁:Thread-0正在执行temp--9
线程A获得锁:Thread-0正在执行temp--8
线程A获得锁:Thread-0正在执行temp--7
线程A获得锁:Thread-0正在执行temp--6
线程A即将等待
线程B:Thread-1获得锁,并执行:temp--4
线程B:Thread-1获得锁,并执行:temp--3
线程B:Thread-1获得锁,并执行:temp--2
线程B:Thread-1获得锁,并执行:temp--1
线程B:Thread-1获得锁,并执行:temp--0
线程A被线程B唤醒,此时temp = 0
方式三:CountDownLatch
CountDownLatch基于AQS框架,相当于维护一个线程间共享变量state
public class VolatileTest {
private volatile static int temp = 10;
static CountDownLatch countDownLatch = new CountDownLatch(2);
public static void main(String[] args) {
Thread A = new Thread(new ThreadA());
Thread B = new Thread(new ThreadB());
B.start();
A.start();
try {
//执行await()方法,此线程就会阻塞,直到计数器为0
countDownLatch.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("主线程执行");
}
static class ThreadA implements Runnable {
@Override
public void run() {
while (temp > 0) {
System.out.println("线程A:" + Thread.currentThread().getName() + "正在执行");
temp--;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
countDownLatch.countDown();
System.out.println("此时计数器数量为:" + countDownLatch.getCount());
}
}
static class ThreadB implements Runnable {
@Override
public void run() {
while (true) {
countDownLatch.countDown();
System.out.println("线程B:" + Thread.currentThread().getName() + "执行");
System.out.println("此时计数器数量为:" + countDownLatch.getCount());
break;
}
}
}
}
输出结果:
线程B:Thread-1执行
此时计数器数量为:1
线程A:Thread-0正在执行
线程A:Thread-0正在执行
线程A:Thread-0正在执行
线程A:Thread-0正在执行
线程A:Thread-0正在执行
线程A:Thread-0正在执行
线程A:Thread-0正在执行
线程A:Thread-0正在执行
线程A:Thread-0正在执行
线程A:Thread-0正在执行
此时计数器数量为:0
主线程执行
方式四 : LockSupport
备注:lockSupport不需要写在同步代码块中,实现了线程间的解锁。其次,park方法和unpark方法,没有固定顺序要求,皆可正常唤醒线程。unpark不会释放锁
public class LockSupportTest {
public static void main(String[] args) {
Thread threadA = new Thread(){
public void run(){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("当前线程A:" + Thread.currentThread().getName() + "正在执行,即将被阻塞");
LockSupport.park();
System.out.println("当前线程A:" + Thread.currentThread().getName() + "被唤醒");
}
};
Thread threadB = new Thread(){
public void run(){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("当前线程B:" + Thread.currentThread().getName() + "正在执行,即将解锁A线程");
LockSupport.unpark(threadA);
System.out.println("当前线程B:" + Thread.currentThread().getName() + "结束");
}
};
threadA.start();
threadB.start();
}
}
方式五 : Condition
备注:使用Condition的await()和signal()方法来代替传统的Object的wait()和notify()方法,这俩方法必须在lock保护之内。
public class ConditionTest {
public static void main(String[] args) {
Myservice sercive = new Myservice();
ThreadA a = new ThreadA(sercive);
a.setName("A");
a.start();
ThreadB b = new ThreadB(sercive);
b.setName("B");
b.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sercive.signalAll_A();
}
static class ThreadA extends Thread{
private Myservice service;
public ThreadA(Myservice service) {
super();
this.service = service;
}
public void run(){
service.awaitA();
}
}
static class ThreadB extends Thread{
private Myservice service;
public ThreadB(Myservice service) {
super();
this.service = service;
}
public void run(){
service.awaitB();
}
}
static class Myservice {
private ReentrantLock lock = new ReentrantLock();
public Condition conditionA = lock.newCondition();
public Condition conditionB = lock.newCondition();
public void awaitA(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "线程A调用方法await前");
conditionA.await();
System.out.println(Thread.currentThread().getName() + "线程A调用方法await后");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void awaitB(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "线程B调用方法await前");
conditionB.await();
System.out.println(Thread.currentThread().getName() + "线程B调用方法await后");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void signalAll_A(){
lock.lock();
System.out.println(Thread.currentThread().getName() + "线程A调用方法signalAll前");
conditionA.signalAll();
System.out.println(Thread.currentThread().getName() + "线程A调用方法signalAll后");
lock.unlock();
}
public void signalAll_B(){
lock.lock();
System.out.println(Thread.currentThread().getName() + "线程B调用方法signalAll前");
conditionB.signalAll();
System.out.println(Thread.currentThread().getName() + "线程B调用方法signalAll后");
lock.unlock();
}
}
}
输出结果:
A线程A调用方法await前
B线程B调用方法await前
main线程A调用方法signalAll前
main线程A调用方法signalAll后
A线程A调用方法await后
从输出的结果中分析,线程B仍处于阻塞状态没有被唤醒。Condition实现一种分组机制,对临界资源进行访问的线程进行分组,以便实现线程间更精细化的协作,例如只通知部分线程。