目录
九、多线程拓展
前言
多线程笔记复盘【已完】,后续更新在网络通信
一、多线程的创建
1.多线程是什么?
- 多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)。
2. 如何在程序中创建出多条线程?
- Java是通过java.lang.Thread 类的对象来代表线程的。
1.1 多线程的创建方式一:继承Thread类
- 定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
- 创建MyThread类的对象
- 调用线程对象的start()方法启动线程(启动后还是执行run方法的)
MyThraed.class
package com.itheima.d1_create_thread;
//1、让子类继承Thread线程类
public class MyThread extends Thread {
//2、必须重写Thread类的run方法
@Override
public void run() {
//描述线程执行的任务
for (int i = 1; i <= 5; i++) {
System.out.println("MyThread线程输出:" + i);
}
}
}
ThreadTest1.class
package com.itheima.d1_create_thread;
public class ThreadTeat1 {
//main 方法是由一条默认的主线程执行的
public static void main(String[] args) {
//3、创建MyThread对象,来代表一个线程
Thread t = new MyThread();
//4、启动线程
t.start();// main线程 t线程
for (int i = 1; i <= 5; i++) {
System.out.println("主线程main输出" + i);
}
}
}
方式一优缺点:
- 优点:编码简单
- 缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展。
多线程的注意事项
1. 启动线程必须是调用start方法,不是调用run方法
- 直接调用run方法会当成普通方法执行,此时相当于还是单线程执行
- 只有调用start方法才是启动一个新的线程执行
2. 不要把主线程任务放在启动子线程之前。
- 这样主线程一直是先跑完的,相当于是一个单线程的效果了
1.2 多线程创建方式二:实现Runnable接口
- 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
- 创建MyRunnable任务对象
- 把MyRunnable任务对象交给Thread处理
调用线程对象的start()方法启动线程
MyRunnable.class
package com.itheima.d1_create_thread;
public class MyRunnable implements Runnable {
//2、重写runnable的run方法
@Override
//线程要执行的任务。
public void run() {
for (int i = 1; i <=5; i++) {
System.out.println("子线程输出==>"+i);
}
}
}
ThreadTest.class
package com.itheima.d1_create_thread;
public class ThreadTest2 {
public static void main(String[] args) {
//3、创建任务对象
Runnable target=new MyRunnable();
// 4、把任务对象交给一个线程对象处理。
new Thread(target).start();
for (int i = 1; i <=5; i++) {
System.out.println("主线程main输出==>"+i);
}
}
}
方式二优缺点:
- 优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强
- 缺点:需要多一个Runnable对象。
1.2.1 线程创建方式二:匿名内部类写法
- 可以创建Runnable的匿名内部类对象
- 再交给Thread线程对象
- 再调用线程对象的start()启动线程
package com.itheima.d1_create_thread;
public class ThreadTest2_2 {
public static void main(String[] args) {
//1、直接创建Runnable接口的匿名内部类形式(任务对象)
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 5; i++) {
System.out.println("子线程1输出" + i);
}
}
};
new Thread(target).start();
//简化形式1:
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 5; i++) {
System.out.println("子线程2输出" + i);
}
}
});
//简化形式2:
new Thread(()->{
for (int i = 0; i <= 5; i++) {
System.out.println("子线程3输出" + i);
}
});
for (int i = 0; i <= 5; i++) {
System.out.println("主线程main输出" + i);
}
}
}
1.3 方式三:实现Callable接口
前两种线程创建方式都存在的一个问题
- 假如线程执行完毕后有一些数据需要返回,他们重写的run方法均不能直接返回结果。
怎么解决这个问题?
- JDK 5.0提供了Callable接口和FutureTask类来实现(多线程的第三种创建方式)
- 这种方式最大的优点:可以返回线程执行完毕后的结果
多线程的第三种创建方式:利用Callable接口、FutureTask类来实现
- 创建任务对象
- 定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据
- 把Callable类型的对象封装成FutureTask(线程任务对象)。
- 把线程任务对象交给Thread对象。
- 调用Thread对象的start方法启动线程
- 线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果
线程创建方式三的优缺点
- 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果。
- 缺点:编码复杂一点。
MyCallable.class
package com.itheima.d1_create_thread;
import java.util.concurrent.Callable;
//1、让这个类实现Callable接口
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
//2、重写call方法
@Override
public String call() throws Exception {
//描述线程的任务,返回线程执行返回后的结果。
int sum = 0;
for (int i = 0; i <= n; i++) {
sum += i;
}
return "线程求出了1-" + n + "的和是:" + sum;
}
}
ThreadTest3.class
package com.itheima.d1_create_thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadTest3 {
public static void main(String[] args) throws Exception {
//3、创建一个Callable的对象
Callable<String> call = new MyCallable(100);
// 4、把Callable的对象封装成一个FutureTask对象(任务对象)
// (1)是一个任务对象,实现了Runnable对象.
// (2)可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后的结果。
FutureTask<String> f1 = new FutureTask<>(call);
//5、把任务对象交给一个Thread对象
new Thread(f1).start();
//6、获取线程执行完毕后返回的结果。
// 注意:如果执行到这儿,假如上面的线程还没有执行完毕
// 这里的代码会暂停,等待上面线程执行完毕后才会获取结果。
String rs = f1.get();
System.out.println(rs);
}
}
二、Thread的常用方法
MyThread.class
package com.itheima.d2_thread_api;
public class MyThread extends Thread {
public MyThread(String name) {
super(name);//为当前线程设置名字了
}
@Override
public void run() {
Thread t = Thread.currentThread();
for (int i = 1; i <= 3; i++) {
System.out.println(t.getName() + "线程输出:" + i);
}
}
}
ThreadTest1.class
package com.itheima.d2_thread_api;
public class ThreadTest1 {
public static void main(String[] args) {
Thread t1 = new MyThread("1号线程");
// t1.setName("1号线程");
t1.start();
System.out.println(t1.getName());//Thread-0
Thread t2 = new MyThread("2号线程");
// t2.setName("2号线程");
t2.start();
System.out.println(t2.getName());//Thread-1
//主线程对象的名字
//哪个线程执行它,他就会得到哪个线程对象
Thread m = Thread.currentThread();
m.setName("主线程");
System.out.println(m.getName());//main
for (int i = 1; i <= 5; i++) {
System.out.println(m.getName() + "线程输出:" + i);
}
}
}
ThreadTest2.class
package com.itheima.d2_thread_api;
public class ThreadTest2 {
public static void main(String[] args) throws Exception {
for (int i = 1; i <= 5; i++) {
System.out.println(i);
//休眠5秒
if (i == 3) {
//会让当前执行的线程暂停5秒,再继续执行
Thread.sleep(5000);
}
}
// join方法作用:让当前调用这个方法的线程先执行完。
Thread t1 = new MyThread("1号线程");
t1.start();
t1.join();
Thread t2 = new MyThread("2号线程");
t2.start();
t2.join();
}
}
三、线程安全
1.什么是线程安全问题?
多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题
2.线程安全问题出现的原因?
- 存在多个线程在同时执行
- 同时访问一个共享资源
- 存在修改该共享资源
3.1 用程序模拟线程安全问题
Account .class
package com.itheima.d3_thread_safe;
public class Account {
private String cardId;//卡号
private double money;//余额
@Override
public String toString() {
return "Account{" +
"cardId='" + cardId + '\'' +
", money=" + money +
'}';
}
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
//小明和小红同时来
public void drawMoney(double money) {
// 判断谁先来取钱
String name = Thread.currentThread().getName();
//1、判断余额是否足够
if (this.money >= money) {
System.out.println(name + "来取钱" + money + "成功!");
this.money -= money;
System.out.println("余额剩余为:" + this.money);
} else {
System.out.println(name + "取钱:" + "余额不足!");
}
}
}
DrawThread.class
package com.itheima.d3_thread_safe;
public class DrawThread extends Thread {
private Account acc;
public DrawThread(Account acc,String name){
super(name);
this.acc=acc;
}
@Override
public void run() {
//取钱
acc.drawMoney(100000);
}
}
ThreadTest.class
package com.itheima.d3_thread_safe;
public class ThreadTest {
public static void main(String[] args) {
//1、创建一个账户对象,代表两个人的共享账户。
Account acc = new Account("ICBC-110", 100000);
//2、创建两个线程,分别代表小明 小红,再去同一个账户对象中取钱10万。
new DrawThread(acc,"小明").start();//小明
new DrawThread(acc,"小红").start();//小红
}
}
线程安全问题发生的原因是什么?
- 多个线程,同时访问同一个共享资源,且存在修改该资源。
四、线程同步
4.1 认识线程同步
1. 什么是线程同步?
解决线程安全问题的方案
2. 线程同步的思想
让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
线程同步的常见方案
加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。
4.2 方式一:同步代码块
作用:把访问共享资源的核心代码给上锁,以此保证线程安全
原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。
同步锁的注意事项:
对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。
Accoutn.class
package com.itheima.d4_thread_synchronize;
public class Account {
private String cardId;//卡号
private double money;//余额
@Override
public String toString() {
return "Account{" +
"cardId='" + cardId + '\'' +
", money=" + money +
'}';
}
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
/*
* 静态方法的锁,可以锁住此类中的全部线程,并且每次执行只锁住一个
* */
// public static void test(){
// synchronized (Account.class){
//
// }
// }
//小明和小红同时来
public void drawMoney(double money) {
// 判断谁先来取钱
String name = Thread.currentThread().getName();
//1、判断余额是否足够
// this正好代表共享资源!
synchronized (this) {
if (this.money >= money) {
System.out.println(name + "来取钱" + money + "成功!");
this.money -= money;
System.out.println("余额剩余为:" + this.money);
} else {
System.out.println(name + "取钱:" + "余额不足!");
}
}
}
}
ThreadTest.class
package com.itheima.d4_thread_synchronize;
public class ThreadTest {
public static void main(String[] args) {
//1、创建一个账户对象,代表两个人的共享账户。
Account acc = new Account("ICBC-110", 100000);
//2、创建两个线程,分别代表小明 小红,再去同一个账户对象中取钱10万。
new DrawThread(acc,"小明").start();//小明
new DrawThread(acc,"小红").start();//小红
Account acc1 = new Account("ICBC-220", 100000);
new DrawThread(acc,"小黑").start();//小黑
new DrawThread(acc,"小白").start();//小白
}
}
锁对象随便选择一个唯一的对象好不好呢?
- 不好,会影响其他无关线程的执行。
锁对象的使用规范
- 建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象。
- 对于静态方法建议使用字节码(类名.class)对象作为锁对象。
4.3 方式二:同步方法
作用:把访问共享资源的核心方法给上锁,以此保证线程安全
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。
同步方法底层原理
- 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码
- 如果方法是实例方法:同步方法默认用this作为的锁对象。
- 如果方法是静态方法:同步方法默认用类名.class作为的锁对象,
是同步代码块好还是同步方法好一点?
- 范围上:同步代码块锁的范围更小,同步方法锁的范围更大。
- 可读性:同步方法更好
4.4 方式三:Lock锁
- Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大
- Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。
在Account里面添加lock对象和加锁,解锁对象,其他ThreadTest和DrawThread类一样
package com.itheima.d6_synchronized_lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
private String cardId;//卡号
private double money;//余额
//创建一个锁对象,并且注意添加final关键字
private final Lock lk=new ReentrantLock();
@Override
public String toString() {
return "Account{" +
"cardId='" + cardId + '\'' +
", money=" + money +
'}';
}
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
//小明和小红同时来
public void drawMoney(double money) {
// 判断谁先来取钱
String name = Thread.currentThread().getName();
//设置lock锁的时候注意加 try catch finally 关键字,如果程序遇到bug 最后finally也可以执行解锁的操作,并且最后要保持解锁的操作
try {
lk.lock();//加锁
//1、判断余额是否足够
if (this.money >= money) {
System.out.println(name + "来取钱" + money + "成功!");
this.money -= money;
System.out.println("余额剩余为:" + this.money);
} else {
System.out.println(name + "取钱:" + "余额不足!");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lk.unlock();//解锁
}
}
}
五、线程通信 【了解】
当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺
线程通信的常见模型(生产者与消费者模型)
- 生产者线程负责生产数据
- 消费者线程负责消费生产者生产的数据
- 注意:生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,再通知生产者生产
注意
- 上述方法应该使用当前同步锁对象进行调用
ThreadTest.class
package com.itheima.d7_thread_communication;
public class ThreadTest {
public static void main(String[] args) {
//需求:3个生产者线程,负责生产包子,每个线程每次只能生产1个包子放在桌子上
// 2个消费者线程负责吃包子,每人每次只能从桌子上拿1个包子吃。
Desk desk = new Desk();
// 创建3个生产者线程(3个厨师)
new Thread(() -> {
while (true) {
desk.put();
}
}, "厨师1").start();
new Thread(() -> {
while (true) {
desk.put();
}
}, "厨师2").start();
new Thread(() -> {
while (true) {
desk.put();
}
}, "厨师3").start();
//创建2个消费者线程(2个吃货)
new Thread(() -> {
while (true) {
desk.get();
}
}, "吃货1").start();
new Thread(() -> {
while (true) {
desk.get();
}
}, "吃货2").start();
}
}
Desk.class
package com.itheima.d7_thread_communication;
import java.util.ArrayList;
import java.util.List;
public class Desk {
private List<String> list = new ArrayList<>();
//往桌子上放一个包子
public synchronized void put() {
try {
String name = Thread.currentThread().getName();
//判断是否有包子
if (list.size() == 0) {
list.add(name + "做的包子");
}
System.out.println(name + "做了一个包子");
Thread.sleep(2000);
//有包子,不做了
// 等待自己,唤程别人
this.wait();
this.notifyAll();
} catch (Exception e) {
e.printStackTrace();
}
}
//从桌子上取一个包子
public synchronized void get() {
try {
String name = Thread.currentThread().getName();
if (list.size() == 1) {
//有包子,吃了
System.out.println(name + "吃了" + list.get(0));
list.clear();
Thread.sleep(1000);
this.notifyAll();
this.wait();
} else {
//没有包子
this.notifyAll();
this.wait();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
六、线程池
6.1 认识线程池
- 线程池就是一个可以复用线程的技术
不使用线程池的问题
用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的,而创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。
6.2 如何创建线程池?
谁代表线程池?
- JDK 5.0起提供了代表线程池的接口:ExecutorService
如何得到线程池对象?
- 方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。
- 方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象
- 参数一:corePoolSize:指定线程池的核心线程的数量。
- 参数二:maximumPoolSize:指定线程池的最大线程数量。R
- 参数三:keepAliveTime:指定临时线程的存活时间。
- 参数四:unit:指定临时线程存活的时间单位(秒、分、时、天)
- 参数五:workQueue:指定线程池的任务队列。
- 参数六:threadFactory:指定线程池的线程工厂。
- 参数七:handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理)
线程池的注意事项
1、临时线程什么时候创建?
- 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程
2、什么时候会开始拒绝新任务?
- 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。
package com.itheima.d8_thread_pool;
import java.util.concurrent.*;
public class ThreadPoolTest1 {
public static void main(String[] args) {
//1、通过ThreadPoolExecutor创建一个线程池对象。
/*
* int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
*
* */
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}
}
6.3 线程池处理Runnable任务
ThreadPoolTest1.class
package com.itheima.d8_thread_pool;
import java.util.concurrent.*;
public class ThreadPoolTest1 {
public static void main(String[] args) {
//1、通过ThreadPoolExecutor创建一个线程池对象。
/*
* int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
*
* */
//1、通过ThreadPoolExecutor创建一个线程池对象。
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
Runnable target = new MyRunnable();
pool.execute(target);//!线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(target);//!线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(target);//!线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(target);//复用线程
pool.execute(target);//复用线程
pool.execute(target);//复用线程
pool.execute(target);//复用线程
//到了临时线程创建的时机
pool.execute(target);
pool.execute(target);
// 到了新任务的拒绝时机了!
pool.execute(target);
//pool.shutdown();// 等着线程池的任务全部执行完毕后,再关闭线程池
// pool.shutdownNow();// 立即关闭线程池!不管任务是否执行完毕!
}
}
MyRunnable.class
package com.itheima.d8_thread_pool;
public class MyRunnable implements Runnable {
@Override
public void run() {
//任务是干啥的?
System.out.println(Thread.currentThread().getName() + "===>输出!");
try {
//Thread.sleep(1000);
Thread.sleep(Integer.MAX_VALUE);
} catch (Exception e) {
e.printStackTrace();
}
}
}
6.4 线程池处理Callable任务
ThreadPoolTest2.class
package com.itheima.d8_thread_pool;
import java.util.concurrent.*;
public class ThreadPoolTest2 {
public static void main(String[] args) throws Exception {
//1、通过ThreadPoolExecutor创建一个线程池对象。
/*
* int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
*
* */
//1、通过ThreadPoolExecutor创建一个线程池对象。
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
// 2、使用线程处理Callable任务。
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
Future<String> f3 = pool.submit(new MyCallable(300));
Future<String> f4 = pool.submit(new MyCallable(400));
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
}
}
MyCallable.class
package com.itheima.d8_thread_pool;
import java.util.concurrent.Callable;
//1、让这个类实现Callable接口
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
//2、重写call方法
@Override
public String call() throws Exception {
//描述线程的任务,返回线程执行返回后的结果。
int sum = 0;
for (int i = 0; i <= n; i++) {
sum += i;
}
return Thread.currentThread().getName()+"线程求出了1-" + n + "的和是:" + sum;
}
}
6.5 Executors工具类实现线程池
1. Executors
是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。
注意:这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象
七、其它细节知识:并发、并行
进程
- 正在运行的程序(软件)就是一个独立的进程。
- 线程是属于进程的,一个进程中可以同时运行很多个线程。
- 进程中的多个线程其实是并发和并行执行的
并发的含义
进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。
并行的理解
在同一个时刻上,同时有多个线程在被CPU调度执行。
八、其它细节知识:线程的生命周期
线程的生命周期
- 也就是线程从生到死的过程中,经历的各种状态及状态转换理解线程这些状态有利于提升并发编程的理解能力。
Java线程的状态
- Java总共定义了6种状态
- 6种状态都定义在Thread类的内部枚举类中。
九、多线程拓展
MyRunnable.class
package com.itheima.d9_extend;
public class MyRunnable implements Runnable {
private int count;//记录测览人次
@Override
public void run() {
//100次
for (int i = 0; i < 100; i++) {
System.out.println(this);
synchronized (this) {
System.out.println(Thread.currentThread().getName()+"count===>" + (++count));
}
}
}
}
MyRunnable2.class
package com.itheima.d9_extend;
import java.util.concurrent.atomic.AtomicInteger;
public class MyRunnable2 implements Runnable {
//乐观锁
// 整数修改的乐观锁:原子类实现的。
private AtomicInteger count=new AtomicInteger();
@Override
public void run() {
//100次
for (int i = 0; i < 100; i++) {
synchronized (this) {
System.out.println(Thread.currentThread().getName()+"count===>" + count.incrementAndGet());
}
}
}
}
ThreadTest1.class
package com.itheima.d9_extend;
public class ThreadTest1 {
public static void main(String[] args) {
// 目标:拓展悲观锁,乐观锁原理。
// 悲观锁:一上来就加锁,没有安全感。每次只能一个线程进入访问完毕后,再解锁。线程安全,性能较差!
// 乐观锁:一开始不上锁,认为是没有问题的,大家一起跑,等要出现线程安全问题的时候才开始控制。线程安全,性能较好。
// 需求:1个静态变量,100个线程,每个线程对其加100次。
Runnable target = new MyRunnable();
for (int i = 1; i <= 100; i++) {
new Thread(target).start();
}
}
}
Demo1.class
package com.itheima.d10_test;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Demo1 {
public static void main(String[] args) throws Exception {
// 目标:有10日份礼品,小红,小明两人同时发送,当剩下的礼品小于10份的时候则不再送出
// 利用多线程模拟该过程并将线程的名称打印出来。并最后在控制台分别打印小红,小明各自送出多少分礼物。
//1、拿00份礼品到程序中来。
List<String> gift = new ArrayList<>();
String[] names = {"手机", "电脑", "手表", "项链", "显卡"};
Random r = new Random();
for (int i = 0; i < 100; i++) {
gift.add(names[r.nextInt(names.length)] + (i + 1));
}
System.out.println(gift);
//2、定义线程类,创建线程对象,去集合中拿礼物给别人
SendThread xm = new SendThread(gift, "小明");
xm.start();
SendThread xh = new SendThread(gift, "小红");
xh.start();
xm.join();
xh.join();
System.out.println(xm.getCount());
System.out.println(xh.getCount());
}
}
SendThread.class
package com.itheima.d10_test;
import java.util.List;
import java.util.Random;
public class SendThread extends Thread {
private List<String> gift;
private int count;
public SendThread(List<String> gift, String name) {
super(name);
this.gift = gift;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
//小明,小红 发礼物出去
//实现线程安全问题
/*
注意:不能在方法上方加synchronized 且 不能再run方法里面加this。 要保证线程的唯一性,否则this调用的不知道是哪个线程,会出现bug。
如果需要用锁,要保证锁的唯一性,在括号里添加自定义的字符串名称,或者只有一个对象的参数,比如gift就可以做锁
*/
Random r = new Random();
while (true) {
synchronized (gift) {
if (gift.size() < 10) {
break;
}
String rs = gift.remove(r.nextInt(gift.size()));
System.out.println(name + "发出了:" + rs);
count++;
}
}
}
}