目录
- 引入
- 多进程与多线程的作用
- 线程五态模型
- 线程创建的三种方法
- 线程的基本操作
- 临界区问题(临界资源同步)
- 同步代码块
- 同步非静态方法
- 同步静态方法
- 生产者消费者问题
- BlockingQueue
- 线程池(ExecutorService)
- 如何证明线程中指令会发生乱序?
引入一(Semaphore)
编写两个进程,循环交替输出数字和26个大写英文字母;
输出示例:1A2B3C4D5E6F7G8H9I10J11K12L13M14N15O16P17Q18R19S20T21U22V23W24X25Y26Z27A28B
package xyz.xx;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 两个线程交替输出1A2B3C4D5E...
* 由于此处使用了信号量,两个线程不会出现同时操作临界区的情况,程序中的num可以使用一般的int类型进行替代;
* 解决方案:
* 使用信号量Semaphore类(JDK1.5中新增的额java.util.concurrent包下的类)
* 两个信号量交替使用;
* sem.acquire() -> P
* sem.release() -> V
*/
public class T01_interlaced {
private static AtomicInteger num = new AtomicInteger(0);
private static final Object lock = new Object();
private static Semaphore sem1 = new Semaphore(1);
private static Semaphore sem2 = new Semaphore(0);
public static void main(String[] args) {
Thread numberThread = new Thread("number_thread"){
@Override
public void run() {
for (; ; ) {
try {
sem1.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
num.incrementAndGet();
System.out.print(num);
// CAS : Compare And Swap
sem2.release();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread charThread = new Thread(()->{
for (; ; ) {
try {
sem2.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(Character.toChars(((num.get()-1) % 26) + 65));
sem1.release();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"charThread");
numberThread.start();
charThread.start();
}
}
引入二(CountDownLatch)
import java.util.concurrent.CountDownLatch;
/**
* 测试CountDownLatch类的使用(本质上是一个计数器
*
* JDK1.5中新增的API,同时增加的类还有Semaphore等
*
* 初始化构造参数为线程的数量
* 线程执行完毕后手动调用countDown进行-1
*
* 主线程使用await进行阻塞式等待,当CountDownLatch对象值为0时再次开始执行
*/
public class CountDownLatchTest {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(2);
Thread th1 = new Thread("th1"){
@Override
public void run() {
for(int i=0;i<100000;i++){
System.out.println("hello");
}
latch.countDown();
}
};
Thread th2 = new Thread(()->{
for(int i=0;i<100000;i++){
System.out.println("world");
}
latch.countDown();
},"th2");
th1.start();
th2.start();
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
<一> 多进程与多线程的作用
1> 多进程:提高CPU利用率
2> 多线程:提高程序执行效率
<二> 线程五态模型
<三> 线程创建的三种方法
1> Thread类构造函数
2> 实例
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 测试线程的三种创建方式
* 1. Thread
* start
* 2. Runnable
* start
* 3. Callable + FutureTask (可以获得线程结束返回值)
* run
* get
*
* FutureTask类
* (实现)RunnableFuture接口(->仅仅提供了run()方法)
* (继承)Runnable接口+Future接口
*/
public class ThreadTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// Thread的匿名子类
Thread th1 = new Thread("Thread_1"){
@Override
public void run() {
for(;;){
System.out.println("hello");
}
}
};
// Runnable的Lambda表达式形式
Thread th2 = new Thread(()->{
for(;;){
System.out.println("world");
}
},"Thread_2");
// 使用Callable
testThread3();
}
private static void testThread3() throws InterruptedException, ExecutionException {
Callable<String> myCallable = new Callable<String>() {
@Override
public String call() throws Exception {
return "end";
}
};
FutureTask<String> task = new FutureTask<>(myCallable);
new Thread(task).start();
// TimeUnit枚举类调用sleep底层还是调用了当前线程的sleep方法
TimeUnit.SECONDS.sleep(1);
System.out.println(task.get());
}
}
3> 相关类的继承关系
<四> 线程的基本操作
1> 获取/设置线程名称:getName()、setName()
2> 获取/设置线程优先级:getPriority()、setPriority(...)
3> 线程优先级范围:[1,10]
4> 线程开启:start()
5> 线程结束:interrupt()
6> 获取当前正在执行的线程名:Thread.currentThread().getName()
7> run()与start()有何区别?run()为类中的一般方法,start()执行时会创建线程并调用线程的run()
8> 如果子类想要实现多继承,该如何操作?
<五> 临界区问题(临界资源同步)
package kyleeo.util_02;
/*
* 新建类实现Runnable接口,该接口中只有一个run方法
* 注意这种方式与extends Thread的区别?
* a) 解决了单继承的局限性(如果子类想要实现多线程,不能多继承,但是可以多实现)
* b) 多个进程可以共享一份数据(将数据与代码解耦合)
*
* 同步(线程安全):
*
*/
public class MyThread implements Runnable {
private static int ticket = 100;
private Object lock = new Object();
同步代码块
// @Override
// public void run() {
// synchronized (lock) {
// while (true) {
// if (ticket > 0) {
// for (int i = 0; i < 100; i++) {
// System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票!");
// }
// }
// }
// }
// }
同步方法
// @Override
// public void run() {
// synchronized (this) {
// sellTicket();
// }
// }
//
// public synchronized void sellTicket() {
// while (true) {
// if (ticket > 0) {
// for (int i = 0; i < 100; i++) {
// System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票!");
// }
// }
// }
// }
同步静态方法
@Override
public void run() {
//同步类的class文件,静态方法随类的加载而产生
synchronized (MyThread.class) {
sellTicket();
}
}
public static synchronized void sellTicket() {
while (true) {
if (ticket > 0) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票!");
}
}
}
}
}
<六> 生产者消费者问题
☞ JDK1.5:BlockingQueue接口
☞ JDK1.6:BlockingDeque接口 -> LinkedBlockingDeque实现类初始化时推荐指定大小
public class Producer implements Runnable {
private final BlockingQueue<String> queue;
private int i;
public Producer(BlockingQueue<String> q){
queue = q;
}
@Override
public void run() {
try{
while(true){
queue.put(produce());
}
}catch(Exception e){
e.printStackTrace();
}
}
public synchronized String produce(){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
String putData = String.valueOf(i++);
System.out.println("Produce:"+ putData);
return putData;
}
}
public class Consumer implements Runnable {
private final BlockingQueue<String> queue;
public Consumer(BlockingQueue<String> q) {
queue = q;
}
@Override
public void run() {
try{
while(true){
consume(queue.take());
}
}catch(Exception e){
e.printStackTrace();
}
}
public void consume(String data) {
System.out.println("Consume:"+data);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ComsumerProducerDemo {
public static void main(String[] args) {
BlockingQueue<String> queue = new LinkedBlockingQueue<String>(10);
Producer p1 = new Producer(queue);
Consumer c1 = new Consumer(queue);
Consumer c2 = new Consumer(queue);
new Thread(p1).start();
new Thread(c1).start();
new Thread(c2).start();
}
}
<七> Volatile线程通信
package xyz.kyleeo.vola;
public class VolatileDemo {
public static void main(String[] args) throws Exception {
volatileTest();
}
/**
* volatile会为该变量在编译时自动添加写锁
*/
private static volatile boolean flag = false;
private static void volatileTest() throws Exception {
Thread th1 = new Thread("线程1") {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i == 5) {
flag = true;
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
System.out.println(Thread.currentThread().getName() + "====" + i);
}
}
};
Thread th2 = new Thread("线程2") {
@Override
public void run() {
while (true) {
while (flag) {
System.out.println(Thread.currentThread().getName() + "收到通知");
System.out.println("th2 do something");
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return;
}
}
}
};
Thread th3 = new Thread("线程3") {
@Override
public void run() {
while (true) {
while (flag) {
System.out.println(Thread.currentThread().getName() + "收到通知");
System.out.println("th3 do something");
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return;
}
}
}
};
th2.start();
th3.start();
Thread.sleep(1000L);
th1.start();
}
}
<八> 线程池
☞ JDK1.5新增内容,方便管理与使用线程;
☞ 不推荐使用Executors直接创建线程池(容易OOM),而是使用ThreadPoolExecutor代替(Executors实现类)!
☞ 参数分析:
- corePoolSize:线程池的核心线程数,即便线程池里没有任何任务,也会有corePoolSize个线程在候着等任务。
- maximumPoolSize:最大线程数,不管提交多少任务,线程池里最多工作线程数就是maximumPoolSize。
- keepAliveTime:线程的存活时间。当线程池里的线程数大于corePoolSize时,如果等了keepAliveTime时长还没有任务可执行,则线程池中该线程停止,通过定时来动态调整线程池大小。
- Unit:这个用来指定keepAliveTime的单位,比如秒:TimeUnit.SECONDS。
- BlockingQueue:一个阻塞队列,提交的任务将会被放到这个队列里,LinkedBlockingQueue为其一种实现类;
- threadFactory:线程工厂,用来创建线程,主要是为了给线程起名字,默认工厂的线程名字:pool-1-thread-3。
- handler:拒绝策略,当线程池里线程被耗尽,且队列也满了的时候会调用。
☞ 线程工厂一般使用默认即可,构造函数使用前两个,关于拒绝策略:
关于内部类的使用,请参考《 JAVA - 【类的嵌套】内部类 》;
☞ 具体使用
public class Main {
public static void main(String[] args) {
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(5);
// P6:默认线程工厂
// P7:LRU拒绝策略
ExecutorService threadPool1 = new ThreadPoolExecutor(5, 5, 10, TimeUnit.SECONDS, queue,
new ThreadPoolExecutor.DiscardOldestPolicy());
threadPool1.execute(new Runnable() {
private int i;
@Override
public void run() {
while (true) {
System.out.println("AAA:" + i++);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
threadPool1.execute(new MyRun());
}
}
☞ 线程池原理解析(分析基于上述程序参数)
核心线程数5:进程池到时间后收缩回到的容量(最初创建该数量的线程,结束时候不会被回收,一直运行的线程);
队列长度5:当进程数超出核心线程数时线程会被放入该队列(不会被立刻创建);
线程池大小5:当核心线程数满且队列满时,该线程立马创建,线程结束时候被放入线程池(并不是立刻终止);
如果现在立刻提交运行16个线程,前五个会立刻创建新线程,随后不释放维持核心线程,
6-10个会被放入线程队列不执行,
11-15个会立刻创建执行,11-15线程结束后并不会被立刻释放,在等待keepAliveTime后关闭这些进程,过期时间可以确保线程池动态调节大小,
第16个线程大小超出5+5+5,会被关闭,随后执行拒绝策略;
当15个线程结束时,最终剩余5个线程,虽然没有任务,但是一直在运行,有新线程任务传入时会直接调用存在的线程执行新任务。
DATE:2020-10-14更新
如何证明线程中指令会发生乱序?
/**
* 线程指令乱序证明
*/
public class MessInstruction {
private static int x = 0,y = 0;
private static int a = 0,b = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
for(;;){
i++;
Thread one = new Thread(()->{
a = 1;
x = b;
});
Thread other = new Thread(()->{
b = 1;
y = a;
});
one.start();other.start();
one.join();other.join();
if(x==0&&y==0){
String res = i+"次: x=0 y=0";
System.out.println(res);
break;
}
}
}
}