基本概念
/**
* 1. 进程:就是我们在电脑上运行的一个一个程序
* 2. 线程:操作系统进行运算的最小控制单位,一个进程内有多个线程
*
* 1. 并发: 统一时刻,多个指令在单个cpu上面交替执行
* 2. 并行: 同一时刻,多个指令在多个cpu上面同时执行
*/
创建线程的几种方式
/**
* 多线程实现的几种方式
* 1. 继承Thread类,重写run方法,使用start方法调用
* 2. 实现Runnable接口, new一个任务对象作为Thread的构造参数
* 3 利用Callable和FutureTask接口实现,Callable表示要执行的任务,FutureTask管理多线程的结果
*/
class MyThread extends Thread{
@Override
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + ": 线程被调用");
}
}
}
class MyRun implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "------" + i);
}
//出让cpu的执行权仍有可能被抢回
Thread.yield();
}
}
//Integer表示线程返回的结果是Integer
class MyCall implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
}
@Test
public void test1() throws ExecutionException, InterruptedException {
//1. 通过继承Thread的开启线程
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("Thread1");
t2.setName("Thread2");
t1.start();
t2.start();
//2. 通过实现Runnable接口实现
//表示多线程要执行的对象
MyRun myRun = new MyRun();
Thread t1 = new Thread(myRun);
Thread t2 = new Thread(myRun);
t1.setName("Thread1");
t2.setName("Thread2");
t1.start();
t2.start();
//3. 实现Callable和FutureTask接口
//创建我们的任务对象
MyCall myCall = new MyCall();
//创建线程运行结果的管理对象
FutureTask<Integer> future = new FutureTask<>(myCall);
//创建线程
Thread thread = new Thread(future);
//启动线程
thread.start();
//获取线程的结果
System.out.println("result: " + future.get());
}
线程常用的几种方法
/**
* 线程常用的成员方法:
* 1. String getName() 获取线程名称
* 2. void setName(String name) 设置线程名称
* 3. static Thread currentThread() 获取当前线程
* 4. static void sleep(Long time) 睡眠时间
* 5. void setPriority(int newPriority) 设置线程优先级,默认5级,优先级越高越容易抢占cpu
* 6. final int getPriority() 获取线程优先级
* 7. final void setDaemon(boolean on) 设置为守护线程,当其他非守护线程结束后,守护线程也会随即结束
* 8. static void yield() 出让线程 / 礼让线程,出让cpu的执行权
* 9. static void join() 插入线程 / 插队线程
*/
//设置线程优先级
//创建任务对象
MyRun myRun = new MyRun();
//创建线程
Thread t1 = new Thread(myRun, "飞机");
Thread t2 = new Thread(myRun, "坦克");
//设置优先级
t1.setPriority(1);
t2.setPriority(10);
//启动线程
t1.start();
t2.start();
//设置守护线程
MyRun myRun = new MyRun();
//创建线程
Thread t1 = new Thread(myRun, "飞机");
Thread t2 = new Thread(myRun, "坦克");
//设置守护线程
t2.setDaemon(true);
//启动线程
t1.start();
t2.start();
//设置礼让线程,在Runnable中写上Thread.yield()
MyRun myRun = new MyRun();
//创建线程
Thread t1 = new Thread(myRun, "飞机");
Thread t2 = new Thread(myRun, "坦克");
//启动线程
t1.start();
t2.start();
//插入线程,在线程1启动后,插入线程,则会在线程1执行完后再执行线程2
MyRun myRun = new MyRun();
//创建线程
Thread t1 = new Thread(myRun, "飞机");
Thread t2 = new Thread(myRun, "坦克");
//启动线程
t1.start();
t1.join();
t2.start();
同步代码块
class MyThread extends Thread{
//表示所有的对象共享这个ticket对象
static int ticket = 0;
//锁对象需要保持全局唯一,因此我们需要设置为static
static Object obj = new Object();
@Override
public void run(){
while(true){
//这里我们要求是唯一的对象,所以我们一般会写MyThread.class
synchronized (obj){
if(ticket < 100){
System.out.println(this.getName() + "--------------正在卖第" + ticket++);
}else{
break;
}
}
}
}
}
//同步代码快
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
同步方法
/**
* 同步方法:就是将synchronized关键字放在方法上
*
* 特点:1. 同步方法锁住的是方法里所有的代码
* 2. 不能指定锁对象: static > 类字节码文件对象 非static > this
*
* 技巧:我们只需要价将同步代码块中synchronized中的代码块按住 ctrl + alt + m 直接生成对应的同步方法
*/
public class MyRunnable implements Runnable{
//因为我们这里线程用的同一个任务对象
int ticket = 0;
@Override
public void run() {
while(true){
if (extracted()) break;
}
}
//这里的锁对象默认是this
private synchronized boolean extracted() {
if(ticket < 100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "-----------" + ticket++);
}else{
return true;
}
return false;
}
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
Thread t3 = new Thread(myRunnable);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
LOCK锁
/**
* LOCK锁:LOCK为借口没有实体对象,我们使用ReentrantLock对象
*
* 特点:1.lock可以手动的来开锁和关锁
* 2.注意范围 static 为全局,否则为对象的
* 3.关锁放在finnalu中
*/
class MyThread extends Thread{
//表示所有的对象共享这个ticket对象
static int ticket = 0;
//锁对象需要保持全局唯一,因此我们需要设置为static
static Object obj = new Object();
//使用静态锁对象
static Lock lock = new ReentrantLock();
@Override
public void run(){
while(true){
// synchronized (obj){
//我们使用LOCK代替synchronized
lock.lock();
try {
if(ticket < 100){
System.out.println(this.getName() + "--------------正在卖第" + ticket++);
}else{
break;
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//这里我们一定要放在finally中否则锁可能会释放失败
lock.unlock();
}
// }
}
}
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
死锁
死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。
等待唤醒机制
/**
* wait: 阻塞,synchronized后的代码不会被执行,同时立即释放锁对象
* notify: 继续执行完synchronized后面的代码,让被wait的线程重新获取锁执行之前未被执行完的代码
*/
Demo1
//消息队列
static List<String> list = new ArrayList<>();
public static void main(String[] args) {
new Thread(() -> {
while (true){
synchronized (list){
//当队列中还有数据的时候我们进行等待
if(list.size() != 0){
try {
list.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
//当里面没有数据的时候我们生产数据
list.add(UUID.randomUUID().toString());
list.notify();
System.out.println("生产者线程A-----" + list);
}
}
}
}, "生产者线程A").start();
new Thread(() -> {
while (true){
synchronized (list){
//当队列中没有数据的时候选择等待
if(list.size() == 0){
try {
list.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
//对里面的数据消费
System.out.println("消费者线程B-----" + list);
list.notify();
list.clear();
}
}
}
}, "消费者线程B").start();
}
Demo2
public class Desk {
/**
* 控制生产者和消费者的执行
*/
//表示桌子上是否有面条:0 表示没有面条 1表示有面条
public static int foodFlag = 1;
//总个数,表示就生产10次
public static int count = 10;
//锁对象
public static Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
while(true){
synchronized (lock){
if(Desk.count == 0){
break;
}else{
//表示桌子上面有面条
if(Desk.foodFlag == 1){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
System.out.println("正在做面中。。。");
//唤醒其他线程
lock.notify();
//标记座子上有面条
Desk.foodFlag = 1;
}
}
}
}
}, "生产者线程").start();
new Thread(() -> {
while (true){
synchronized (lock){
if(Desk.count == 0){
break;
}else{
//表示桌子上面没有面条
if(Desk.foodFlag == 0){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
//吃面条
Desk.count--;
System.out.println("消费者线程正在吃面条------" + Desk.count);
//唤醒其他线程
lock.notify();
//标记座子上面没有面条了
Desk.foodFlag = 0;
}
}
}
}
}, "消费者线程").start();
}
}
Demo3 基于阻塞队列实现等待唤醒机制
/**
* 阻塞队列实现等待唤醒机制
*
* ArrayBlockingQueue: 底层是数组,有界
* LinkedBlockingQueue: 底层是链表,无界,但是并不是正真的无界,最大不超过int最大值
*
* 特点:在底层方法里都有锁了,我们在循环就不用锁了
*/
class Cook extends Thread{
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run(){
while (true){
try {
queue.put("面条");
System.out.println("我们将面条放入了队列");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class Foodie extends Thread{
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run(){
while (true){
try {
String take = queue.take();
System.out.println("我们从队列中取出了》》》》》》" + take);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public static void main(String[] args) {
//两个线程用的是同一个阻塞队列
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
Cook cook = new Cook(queue);
Foodie foodie = new Foodie(queue);
//因为我们的打印在锁的外面,所以打印并不会交替出现
cook.start();
foodie.start();
}
线程池
/**
* Executors.newCachedThreadPool() 创建一个没有上限的线程池
* Executors.newCachedThreadPool(int n) 创建一个有上限的线程池
*
* 特点:当提交任务的时候,线程池没有空闲的线程也不能新建线程,任务就会处于排队状态
*/
//创建线程池对象
ExecutorService pool = Executors.newCachedThreadPool();
//提交任务
pool.submit(new MyRunnable());
Thread.sleep(1000);
pool.submit(new MyRunnable());
Thread.sleep(1000);
pool.submit(new MyRunnable());
Thread.sleep(1000);
pool.submit(new MyRunnable());
//关闭线程池
pool.shutdown();
自定义线程池
临时线程创建的时间:当核心线程被创建,且任务队列被排满的时候,这个时候开始创建临时线程,所以先被提交的线程不一定先被创建。
触发任务拒绝策略:当核心线程、临时线程、任务队列都满了的时候,就会触发任务拒绝。
/**
* ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor();
*
* 参数一:核心线程数 不能小于0
* 参数二:最大线程数 不能小于0
* 参数三:临时线程最大存活时间(值) 不能小于0
* 参数四:临时线程最大存活时间(单位) 用TimeUnit指定
* 参数五:任务队列 不能为null
* 参数六:创建线程工厂 不能为null
* 参数七:任务拒绝策略 不能为null
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
3, //核心线程数
6, //最大线程数
60, //临时线程空闲60分钟
TimeUnit.MINUTES, //单位分钟
new ArrayBlockingQueue<>(3), //阻塞队列
Executors.defaultThreadFactory(), //线程创建方式
new ThreadPoolExecutor.AbortPolicy() //线程拒绝策略
);
综合练习
抽红包:准备五个红包,其中只有三个红包有钱,且这三个红包总额为100元。
public class GrabRed extends Thread{
static double money = 100;
static int count = 3;
static final double MIN = 0.01;
@Override
public void run(){
synchronized (GrabRed.class){
if(count == 0){
System.out.println(Thread.currentThread().getName() + "...no money");
}else{
double prize = 0;
if(count == 1){
prize = money;
}else{
Random random = new Random();
prize = random.nextDouble(money - 2 * MIN);//最大值不超过这个
if(prize < MIN){
prize = MIN;
}
}
count--;
money -= prize;
System.out.println(Thread.currentThread().getName() + "...get money: " + prize);
}
}
}
}
GrabRed t1 = new GrabRed();
GrabRed t2 = new GrabRed();
GrabRed t3 = new GrabRed();
GrabRed t4 = new GrabRed();
GrabRed t5 = new GrabRed();
t1.setName("AAA");
t2.setName("BBB");
t3.setName("CCC");
t4.setName("DDD");
t5.setName("EEE");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
抽奖箱:模拟有两个抽奖箱从奖池内抽奖品
public class GrabRed extends Thread{
private List<Integer> list;
public GrabRed(List<Integer> list){
this.list = list;
}
@Override
public void run(){
while (true){
synchronized (GrabRed.class){
if(list.isEmpty()){
break;
}else{
Collections.shuffle(list);
int prize = list.remove(0);
System.out.println(Thread.currentThread().getName() + ">>>>getPrize>>>>" + prize);
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
在前一个问题的基础上统计每一个抽奖箱的数据
public class GrabRed extends Thread{
private List<Integer> list;
static List<Integer> list1;
static List<Integer> list2;
public GrabRed(List<Integer> list){
this.list = list;
list1 = new ArrayList<>();
list2 = new ArrayList<>();
}
@Override
public void run(){
while (true){
synchronized (GrabRed.class){
if(list.isEmpty()){
if("抽奖箱1".equals(this.getName())){
System.out.println("抽奖箱1》》》" + list1);
}else{
System.out.println("抽奖箱2》》》" + list2);
}
break;
}else{
//继续抽奖
Collections.shuffle(list);
int prize = list.remove(0);
if("抽奖箱1".equals(this.getName())){
list1.add(prize);
}else{
list2.add(prize);
}
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
在上面修改,为每一个抽奖箱设置一个队列
public class GrabRed extends Thread{
private List<Integer> list;
public GrabRed(List<Integer> list){
this.list = list;
}
@Override
public void run(){
List<Integer> list1 = new ArrayList<>();
while (true){
synchronized (GrabRed.class){
if(list.isEmpty()){
System.out.println(Thread.currentThread().getName() + ">>>" + list1);
break;
}else{
//继续抽奖
Collections.shuffle(list);
int prize = list.remove(0);
list1.add(prize);
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Main {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 10, 20, 5, 55, 50, 21, 20, 99);
GrabRed t1 = new GrabRed(list);
GrabRed t2 = new GrabRed(list);
GrabRed t3 = new GrabRed(list);
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
t3.setName("抽奖箱2");
t1.start();
t2.start();
t3.start();
}
}
在前面的基础上我们想知道每个抽奖箱的最大值,这个时候我们就需要使用Callable获取返回值
public class MyCallable implements Callable<Integer> {
private List<Integer> list;
public MyCallable(List list){
this.list = list;
}
@Override
public Integer call() throws Exception {
List<Integer> list1 = new ArrayList<>();
while (true){
synchronized (GrabRed.class){
if(list.isEmpty()){
System.out.println(Thread.currentThread().getName() + ">>>" + list1);
break;
}else{
//继续抽奖
Collections.shuffle(list);
int prize = list.remove(0);
list1.add(prize);
}
}
Thread.sleep(100);
}
if(list1.isEmpty()){
return null;
}else{
return Collections.max(list1);
}
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 10, 12, 55, 60, 73, 22, 11, 19, 100);
MyCallable myCallable = new MyCallable(list);
FutureTask<Integer> ft1 = new FutureTask<>(myCallable);
FutureTask<Integer> ft2 = new FutureTask<>(myCallable);
FutureTask<Integer> ft3 = new FutureTask<>(myCallable);
Thread t1 = new Thread(ft1);
Thread t2 = new Thread(ft2);
Thread t3 = new Thread(ft3);
t1.start();
t2.start();
t3.start();
System.out.println(ft1.get());
System.out.println(ft2.get());
System.out.println(ft3.get());
}