【Java】多线程与JUC
文章目录
多线程是什么?
让一个程序同时做多件事,能够提高效率。
主要是当我想让多个功能同时运作的时候可以使用多线程。
举例: 放置游戏、某软件后台挂载更新、聊天软件、后台服务器…
JUC是什么?
Java处理线程相关的类
一、并发与并行
并发
概念上: 同一时刻,单个CPU执行多条指令的交替行为。
个人理解:一个人同时操作多个事件,快速、轮换、交替
进行。
并行
概念上:同一时刻,多个CPU执行多条指令
个人理解: 多个人同时操作多个事件,每个人都有分工 然后同时执行 (即:并在一起行动)
二、实现多线程的方式
方式一 :自定义类继承Thread
- 自定义类继承Thread类
- 重写
run
方法 - 创建自定义类对象,调用
start()
启动线程
// 测试类:
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.setName("线程1 ");
m2.setName("线程2 ");
m1.start();
m2.start();
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + " HelloWorld!");
}
}
}
方式二 :实现Runnable接口
- 自定义类实现
Runnable
接口,重写run()
- new 自定义类
- 创建Thread对象,引入自定义类对象参数
- 调用
start()
启动线程
MyRun myRun = new MyRun();
Thread t1 = new Thread(myRun);
Thread t2 = new Thread(myRun);
t1.setName("线程一: ");
t2.setName("线程二: ");
t1.start();
t2.start();
public class MyRun implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "HelloThread!");
}
}
}
方式三 :实现Callable接口 (有返回值
前面两种都没有返回值
- 自定义类实现
Callable接口
,泛型是返回值类型,实现call
方法 - 创建自定义类对象
- 创建
FutureTask<>
类 ( 用于管理获取返回值类型 - 创建
Thread
线程,开启线程 FutureTask<>
对象的get()
方法获取返回值
MyCallable myC = new MyCallable();
FutureTask<Integer> iFTask = new FutureTask<Integer>(myC);
Thread t1 = new Thread(iFTask);
t1.start();
Integer sum = iFTask.get();
System.out.println(sum);
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}
三种实现方法对比
三、常见的成员方法
四、线程的生命周期
五、线程安全问题 同步代码块
需求:电影院卖票,共100张票,三个窗口卖。
没有加上同步代码块 会导致的问题:
- 超出总票数
- 重复卖同一张票
使用同步代码块
方式避免以上问题:
同步代码块:可以将需要共享的代码锁起来。
特点:
- 锁是默认打开,当一个线程进入,就锁上,其他线程不能进入
- 等到这个线程 执行完毕出来后,锁才会自动打开
WicketThread w1 = new WicketThread("窗口一:");
WicketThread w2 = new WicketThread("窗口二:");
WicketThread w3 = new WicketThread("窗口三:");
w1.start();
w2.start();
w3.start();
public class WicketThread extends Thread {
public WicketThread() {
}
public WicketThread(String name) {
super(name);
}
// 这里使用的是实现线程方式一,会创建多个对象,所以需要static确定只有一个票仓
static int tickets = 0;
//锁对象 随意 , 但是一定要唯一
//不唯一会出现重复
//static Object obj = new Object();
@Override
public void run() {
while (true) {
//锁 同步代码块
synchronized (WicketThread.class) {
if (tickets < 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
tickets++;
System.out.println(getName() + "卖出第" + tickets + "张票");
} else {
break;
}
}
}
}
}
六、同步方法 synchronized
当我们需要把整个方法锁起来,就不需要使用同步代码块,而是直接加到方法定义上。
格式:
特点:
- 锁对象 不能自己指定
- 静态 : 当前类的字节码文件对象(Xxx.class)
- 非静态: this
先写出同步代码块 然后提取代码块中的方法 在方法定义上加上synchronized即可
public class WicketRunnable implements Runnable {
//这里使用的是实现线程方式二,不会创建多个对象,所以不用static修饰也可以
int tickets = 0;
@Override
public void run() {
while (true) {
if (method()) break;
}
}
private synchronized boolean method() {
if (tickets == 100) {
return true;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
tickets++;
System.out.println(Thread.currentThread().getName() + "卖出第" + tickets + "张票");
}
return false;
}
}
七、Lock锁
使用同步锁 是属于 自动上锁 自动解锁,
但需要 手动设置 开关锁 的时候 使用 Lock
接口的ReentrantLock
实现类
void lock();
锁
void unlock()
解锁
public class WicketThread extends Thread {
public WicketThread() {
}
public WicketThread(String name) {
super(name);
}
static int tickets = 0;
//创建了多个线程对象,所以static修饰,只能有一个锁对象
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//synchronized (WicketThread.class) {
lock.lock();
try {
if (tickets < 100) {
Thread.sleep(100);
tickets++;
System.out.println(getName() + "卖出第" + tickets + "张票");
} else {
break;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
// 不管什么情况 finally 都会执行 也就是都会解锁
} finally {
lock.unlock();
}
//}
}
}
}
八、死锁
不同的线程分别占用对方的资源不放,都在等待对方放弃,就形成死锁。
出现死锁不会发生异常或提示,而且 处于线程阻塞状态 , 无法继续。
锁的嵌套会导致死锁,要避免。
九、等待唤醒机制(生产者和消费者)
线程具有随机性 所以使用等待唤醒机制 避免随机性
常见方法
消费者代码实现
public class Customer extends Thread {
@Override
public void run() {
/*
* 1. 循环
* 2. 同步代码块/方法/lock
* 3. 判断结束条件
* 4. 判断没结束时候的执行(核心逻辑)
* */
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
} else {
if (Desk.productFlag == 0) {
//桌子上没东西,当前线程->消费者等待,生产者开做
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
//桌子有东西,开买
Desk.count--;
System.out.println("买了一个!当前还能再买" + Desk.count + "个");
//买完一个通知生产者
Desk.lock.notifyAll();
Desk.productFlag = 0;
}
}
}
}
}
}
生产者代码实现
public class Productor extends Thread {
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
} else {
//桌子上已有东西就等待
if (Desk.productFlag == 1) {
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
System.out.println("放桌上一个了");
Desk.productFlag = 1;
Desk.lock.notifyAll();
}
}
}
}
}
}
生产者消费者中转代码
/**
* @Description: 生产者消费者 的 中转站
*/
public class Desk {
//判断桌子上是否有东西 0无,1有
static public int productFlag = 0;
//消费者只能买十个 限定条件
static public int count = 10;
//锁对象
final static public Object lock = new Object();
}
//测试main类
Productor p = new Productor();
Customer c = new Customer();
p.setName("生产者");
c.setName("消费者");
p.start();
c.start();
十、等待唤醒机制(阻塞队列方式实现)
厨师生产食物给消费者(Consumer)
阻塞队列继承结构:
其实就是使用内部封装的方法实现消费者生产者模式,是队列的形式
使用ArrayBlockingQueue<>
中的 put()
take()
方法
举例put()
的源码:内部是用了lock锁 所以外部调用不用再写。take()
是一样的。
两个线程类通过构造接收 的是 同一个ArrayBlockingQueue对象
public class Cook extends Thread {
ArrayBlockingQueue<String> abq;
public Cook(ArrayBlockingQueue<String> abq) {
this.abq = abq;
}
@Override
public void run() {
while (true) {
try {
abq.put("食物"); // put
System.out.println("放一碗吃的");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Consumer extends Thread {
ArrayBlockingQueue<String> abq;
public Consumer(ArrayBlockingQueue<String> abq) {
this.abq = abq;
}
@Override
public void run() {
while (true) {
try {
String food = abq.take(); // take
System.out.println("消费者吃一碗" + food);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
//测试类
ArrayBlockingQueue<String> abq = new ArrayBlockingQueue<String>(1);
Cook cook = new Cook(abq);
Consumer consumer = new Consumer(abq);
cook.setName("厨师 ");
consumer.setName("消费者 ");
cook.start();
consumer.start();
打印的语句存在问题是因为:输出语句sout 在锁的外面,但是里面的数值是没有问题的。
十一、线程的六种状态
十二、线程池
Executors 工具类提供的静态方法:
ExecutorService es = Executors.newCachedThreadPool();
es.submit(new MyRunnable());
Thread.sleep(1000);
线程池原理:
类似C#对象池
十三、自定义线程池
综合练习
抢红包
需求:
? 假设:100块,分成了3个包,现在有5个人去抢。
? 其中,红包是共享数据。
? 5个人是5条线程。
? 打印结果如下:
? XXX抢到了XXX元
XXX没抢到…
方式一: Double
public class Person extends Thread {
static double money = 100;//总共100元
static int count = 3; // 分三个包抢
static final double MIN = 0.01;//最小红包
@Override
public void run() {
synchronized (Person.class) {
if(count == 0){
System.out.println(getName()+" 没抢到");
}else{
double prize = 0;
if(count == 1){
//最后一次
prize = money;
}else{
Random r = new Random();
//最极限的情况: 第一次 最多只能抢到99.98 , 第二次 第三次 为0.01
double bounds = money - (count - 1) * MIN;
prize = r.nextDouble(bounds);
if(prize < MIN){
prize = MIN;
}
}
money -= prize;
count--;
System.out.println(getName() + " 抢到 " + prize+" 元");
}
}
}
}
使用double 钱的数值会有问题。
方式二:BigDecimal
public class Person extends Thread {
static BigDecimal money = BigDecimal.valueOf(100);//总共100元
static int count = 3; // 分三个包抢
static final BigDecimal MIN = BigDecimal.valueOf(0.01);//最小红包
@Override
public void run() {
synchronized (Person.class) {
if (count == 0) {
System.out.println(getName() + " 没抢到");
} else {
BigDecimal prize;
if (count == 1) {
//最后一次
prize = money;
} else {
Random r = new Random();
//最极限的情况: 第一次 最多只能抢到99.98 , 第二次 第三次 为0.01
double bounds = money.subtract(BigDecimal.valueOf(count - 1)).multiply(MIN).doubleValue();
prize = BigDecimal.valueOf(r.nextDouble(bounds));
}
prize = prize.setScale(2, RoundingMode.HALF_UP);
money = money.subtract(prize);
count--;
System.out.println(getName() + " 抢到 " + prize + " 元");
}
}
}
}
抽奖
需求:
抽奖池中的奖项为 : {10,5,20,50,100,200,500,800,2,80,300,700};
创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
每次抽出一个奖项就打印一个(随机)
? 抽奖箱1 又产生了一个 10 元大奖
抽奖箱2 又产生了一个 500 元大奖 …
//测试类
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
MyThread m1 = new MyThread(list);
MyThread m2 = new MyThread(list);
m1.setName("抽奖箱1");
m2.setName("抽奖箱2");
m1.start();
m2.start();
public class MyThread extends Thread {
ArrayList<Integer> list;
public MyThread(ArrayList<Integer> list) {
this.list = list;
}
@Override
public void run() {
while (true) {
synchronized (MyThread.class) {
if (list.size() == 0) {
break;
} else {
// 打乱集合,每次拿到集合的第一个数并且删除
Collections.shuffle(list);
Integer num = list.remove(0);
System.out.println(getName() + "抽到了" + num + " 元大奖");
}
}
//让结果均匀一点,写在锁的外面 让出来的线程休息10毫秒,让另一个线程进去
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
抽奖 - 多线程统计并求最大值
需求:
? 在上一题基础上继续完成如下需求:
? 每次抽的过程中不打印,抽完时一次性打印(随机)
? 在此次抽奖过程中,抽奖箱1总共产生了6个奖项。
? 分别为:10,20,100,500,2,300最高奖项为300元,总计额为932元
? 在此次抽奖过程中,抽奖箱2总共产生了6个奖项。
? 分别为:5,50,200,800,80,700最高奖项为800元,总计额为1835元
public class MyThread extends Thread {
ArrayList<Integer> list;
int count = 0;//几个奖项
int sum = 0; //总和多少钱
int max = 0;
public MyThread(ArrayList<Integer> list) {
this.list = list;
}
@Override
public void run() {
ArrayList<Integer> newList = new ArrayList<Integer>();
while (true) {
synchronized (MyThread.class) {
if (list.size() == 0) {
System.out.println(getName() + "一共产生了" + count + "个奖项" + "分别是:" + newList + "总计额为" + sum + "元,"+"最大奖为"+max+"元");
break;
} else {
// 打乱集合,每次拿到集合的第一个数并且删除
Collections.shuffle(list);
Integer num = list.remove(0);
sum += num;
count++;
newList.add(num);
if(num > max){
max = num;
}
}
}
//让结果均匀一点,写在锁的外面 让出来的线程休息10毫秒,让另一个线程进去
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
MyThread m1 = new MyThread(list);
MyThread m2 = new MyThread(list);
m1.setName("抽奖箱1");
m2.setName("抽奖箱2");
m1.start();
m2.start();
抽奖 - 多线程之间的比较
在上一题基础上继续完成如下需求:
“在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元“”
对比两个线程之间的最大奖项。(也就是获取线程的结果)
public class MyCallable implements Callable<Integer> {
ArrayList<Integer> list;
int count = 0;//几个奖项
int sum = 0; //总和多少钱
int max = 0;
public MyCallable(ArrayList<Integer> list) {
this.list = list;
}
@Override
public Integer call() throws Exception {
ArrayList<Integer> newList = new ArrayList<Integer>();
while (true) {
synchronized (MyCallable.class) {
if (list.size() == 0) {
System.out.println(Thread.currentThread().getName() + "一共产生了" + count + "个奖项" + "分别是:"
+ newList + "总计额为" + sum + "元," + "最大奖为" + max + "元");
break;
} else {
// 打乱集合,每次拿到集合的第一个数并且删除
Collections.shuffle(list);
Integer num = list.remove(0);
sum += num;
count++;
newList.add(num);
max = Collections.max(newList);
}
}
//让结果均匀一点,写在锁的外面 让出来的线程休息10毫秒,让另一个线程进去
Thread.sleep(10);
}
if (newList.size() == 0) {
return null;
} else {
return max;
}
}
}
//测试类
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);
MyCallable mc1 = new MyCallable(list);
MyCallable mc2 = new MyCallable(list);
FutureTask<Integer> ift1 = new FutureTask<Integer>(mc1);
FutureTask<Integer> ift2 = new FutureTask<Integer>(mc2);
Thread t1 = new Thread(ift1);
Thread t2 = new Thread(ift2);
t1.setName("抽奖箱一:");
t2.setName("抽奖箱二:");
t1.start();
t2.start();
Integer max1 = ift1.get();
Integer max2 = ift2.get();
if(max1 > max2){
System.out.println(t1.getName()+"产生最大奖"+max1);
}else{
System.out.println(t2.getName()+"产生最大奖"+max2);
}
总结
一个线程执行一段代码逻辑,多个线程就是执行多次同一段代码逻辑。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!