Java笔记_20
一、多线程
1.1、多线程概述
进程
- 进程是程序的基本执行实体
线程
- 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
- 简单理解:应用软件中互相独立,可以同时运行的功能
什么是多线程?
- 有了多线程,我们就可以让程序同时做多件事情
多线程的作用?
- 提高效率
多线程的应用场景?
- 只要你想让多个事情同时运行就需要用到多线程
比如∶软件中的耗时操作、所有的聊天软件、所有的服务器
1.2、并发和并行
并发: 在同一时刻,有多个指令在单个CPU上交替执行
并行: 在同一时刻,有多个指令在多个CPU上同时执行
1.3、多线程的实现方式
- 继承Thread类的方式进行实现
- 自己定义一个类继承
Thread
- 重写run方法
- 创建子类的对象,并启动线程
- 使用
start
方法
- 使用
- 自己定义一个类继承
package thread;
public class Dome1 {
public static void main(String[] args) {
thread1 t1 = new thread1();
thread1 t2 = new thread1();
t1.setName("线程一");
t2.setName("线程二");
//线程一和线程二会同时执行
t1.start();
t2.start();
}
}
package thread;
public class thread1 extends Thread {
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(getName() +"你好啊");
}
}
}
- 实现Runnable接口的方式进行实现
- 在自己定义的类中接上Runnable接口
- 重写里面的run方法创建自己的类的对象
- 在自己创建的类中使用
Thread.currentThread()
方法可以获取到当前线程的对象
- 在自己创建的类中使用
- 创建一个Thread类的对象,并开启线程
- 测试类中将自己创建的类传入到Thread类中
package thread;
public class Dome2 {
public static void main(String[] args) {
//创建第一个线程
thread2 t1 = new thread2();
Thread tt1 = new Thread(t1);
tt1.setName("线程一");
tt1.start();
//创建第二个线程
thread2 t2 = new thread2();
Thread tt2 = new Thread(t2);
tt2.setName("线程二");
tt2.start();
}
}
package thread;
public class thread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//获取到当前线程的对象
/*
Thread t = Thread.currentThread();
System.out.println(t.getName()+"hello world");
*/
//第二种获取对象的方式
System.out.println(Thread.currentThread().getName()+"hello world");
}
}
}
- 利用
Callable接口
和Future接口
方式实现
特点: 可以获取到多线程运行的结果- 创建一个类MyCallable实现callable接口
- 重写call(是有返回值的,表示多线程运行的结果)
- 创建Mycallable的对象(表示多线程要执行的任务)
- 创建FutureTask的对象(作用管理多线程运行的结果)
- 创建Thread类的对象,并启动(表示线程)
package thread;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Dome3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建thread3对象
thread3 t1 = new thread3();
//创建FutureTask对象(管理多线程的结果)
FutureTask<Integer> ft = new FutureTask<>(t1);
Thread tt1 = new Thread(ft);
tt1.start();
//获取多线程运行的结果
Integer integer = ft.get();
System.out.println(integer);
}
}
package thread;
import java.util.concurrent.Callable;
public class thread3 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int num = 0;
for (int i = 0; i <= 100; i++) {
num = num+i;
}
return num;
}
}
1.4、多线程中常见的成员方法
方法名称 | 说明 |
---|---|
String getName() | 返口此线程的名称 |
void setName(String name) | 设置线程的名字(构造方法也可以设置名字) |
static Thread currentThread() | 获取当前线程的对象 |
static void sleep( long time) | 让线程休眠指定的时间,单位为毫秒 |
setPriority( int newPriority) | 设置线程的优先级 |
final int getPriority() | 获取线程的优先级 |
final void setDaemon( boolean on) | 设置为守护线程 |
public static void yield() | 出让线程/礼让线程 |
public static void join() | 插入线程/插队线程 |
getName()
细节- 如果我们没有给线程设置名字,线程也是有默认的名字的
- 格式: Thread-X(X序号,从0开始的)
- 如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置
currentThread()
细节- 当JVM虚拟机启动之后,会自动的启动多条线程其中有一条线程就叫做main线程
- 他的作用就是去调用main方法,并执行里面的代码
- 在以前,我们写的所有的代码,其实都是运行在main线程当中
sleep( long time)
细节- 哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
- 方法的参数:就表示睡眠的时间,单位毫秒
- 当时间到了之后,线程会自动的醒来,继续执行下面的其他代码
package thread;
public class Dome4 {
public static void main(String[] args) throws InterruptedException {
//可以在类中重写构造方法来实现给多线程赋值
thread4 tr1 = new thread4("aaa");
thread4 tr2 = new thread4("bbb");
Thread tt1 = new Thread(tr1);
Thread tt2 = new Thread(tr2);
tt1.start();
tt2.start();
System.out.println(Thread.currentThread().getName());//main
System.out.println("asaaaaaaaaaaaaaaaaaaaaaa");
//可以时jvm机睡眠 n毫秒
Thread.sleep(5000);
System.out.println("bbbbbbbbbbbbbbbbbbbbbbbb");
Thread.sleep(5000);
System.out.println("dddddddddddddddddddddddd");
}
}
方法名称 | 说明 |
---|---|
setPriority( int newPriority) | 设置线程的优先级 |
final int getPriority() | 获取线程的优先级 |
- 线程的优先级默认是5(包括main线程)
- 改变了优先级只是先执行的概率变高了,并不是绝对先执行
package thread;
public class Dome5 {
public static void main(String[] args) {
thread2 t1 = new thread2();
thread2 t2 = new thread2();
Thread tt1 = new Thread(t1);
Thread tt2 = new Thread(t2);
tt1.setName("aaaa");
tt2.setName("bbbb");
//System.out.println(tt1.getPriority());//5
//System.out.println(tt2.getPriority());//5
//改变了优先级只是先执行的概率变高了,并不是绝对先执行
tt1.setPriority(1);
tt2.setPriority(10);
tt1.start();
tt2.start();
}
}
package thread;
public class thread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//获取到当前线程的对象
/*
Thread t = Thread.currentThread();
System.out.println(t.getName()+"hello world");
*/
//第二种获取对象的方式
System.out.println(Thread.currentThread().getName()+"hello world");
}
}
}
方法名称 | 说明 |
---|---|
final void setDaemon( boolean on) | 设置为守护线程 |
- 当其他的非守护线程执行完毕之后,守护线程会陆续结束
- 通俗易懂:当女神线程结束了,那么备胎也没有存在的必要了
package thread;
public class Dome6 {
public static void main(String[] args) {
thread1 t1 = new thread1();
thread1_1 t2 = new thread1_1();
Thread t11 = new Thread(t1);
Thread t22 = new Thread(t2);
t22.setDaemon(true);//创建一个守护线程,当t11执行完毕时t22也会陆续结束
t11.start();
t22.start();
}
}
package thread;
public class thread1 extends Thread {
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 10; i++) {
System.out.println(getName() +"你好啊");
}
}
}
package thread;
public class thread1_1 extends Thread{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(getName() +"你不好吗");
}
}
}
方法名称 | 说明 |
---|---|
public static void yield() | 出让线程/礼让线程 |
public static void join() | 插入线程/插队线程 |
- 尽可能地让结果均匀,由于是让出当前CPU的执行权,多线程再次抢夺执行权时,同样可能再次被原来线程抢夺到
package thread;
public class Dome7 {
public static void main(String[] args) {
thread5 t1 = new thread5();
thread5 t2 = new thread5();
Thread tt1 = new Thread(t1);
Thread tt2 = new Thread(t2);
tt1.start();
tt2.start();
}
}
package thread;
public class thread5 extends Thread {
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 10; i++) {
System.out.println(getName() +"你不好吗");
}
//表示让出当前CPU的执行权
Thread.yield();
}
}
方法名称 | 说明 |
---|---|
public static void join() | 插入线程/插队线程 |
package thread;
public class Dome8 {
public static void main(String[] args) throws InterruptedException {
thread1 t = new thread1();
Thread t1 = new Thread(t);
t1.start();
//表示把t这个线程,插入到当前线程之前。
//当前线程:main线程
t1.join();
/*
Thread-0你好啊
Thread-0你好啊
Thread-0你好啊
Thread-0你好啊
Thread-0你好啊
Thread-0你好啊
Thread-0你好啊
Thread-0你好啊
Thread-0你好啊
Thread-0你好啊
你好
你好
你好
你好
你好
* */
//当前执行在mian线程下
for (int i = 0; i < 5; i++) {
System.out.println("你好");
}
}
}
package thread;
public class thread1_1 extends Thread{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(getName() +"你不好吗");
}
}
}
1.5、线程的生命周期
sleep()
方法执行结束之后会编程就绪状态
1.6、线程安全的问题
把操作共享数据的代码锁起来
格式:
synchronized(锁){
操作共享数据的代码
}
特点1: 锁默认打开,有一个线程进去了,锁自动关闭
特点2: 里面的代码全部执行完毕,线程出来,锁自动打开
- 细节:
- synchronized锁的代码必须在循坏外面,否则会导致一个线程执行完所有的代码
- synchronized锁对象一定要是唯一的,一般使用当前类的字节码文件:
类.class
如果没有锁代码会出现的问题:
- 静态变量会发生重复
- 静态变量会超出方法里给限定的范围
- 问题原因:
- 线程执行时,会有随机性
package threadTest;
public class test1 {
public static void main(String[] args) {
test1_thead t1 = new test1_thead();
test1_thead t2 = new test1_thead();
test1_thead t3 = new test1_thead();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
package threadTest;
public class test1_thead extends Thread{
static int ticket = 0;
static Object obj = new Object();
@Override
public void run() {
while (true){
synchronized(test1_thead.class){
if (ticket<100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(getName()+"正在销售第"+ticket+"张票");
}else {
break;
}
}
}
}
}
1.7、同步方法
同步方法
就是把synchronized关键字加到方法上
- 格式:
修饰符 synchronized 返回值类型 方法名(方法参数){...}
特点1: 同步方法是锁住方法里面所有的代码
特点2: 锁对象不能自己指定
-
非静态:this
-
静态:当前类的字节码文件对象
-
StringBuilder和StringBuffer的区别
- StringBuffer是线程安全的
- 两者的方法是完全相同的
package threadTest;
public class test2 {
public static void main(String[] args) {
test2_thread tt = new test2_thread();
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
Thread t3 = new Thread(tt);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
package threadTest;
public class test2_thread implements Runnable{
int ticket = 0;
@Override
public void run() {
while (true){
if (monted())break;
}
}
public synchronized boolean monted(){
if (ticket == 100){
return true;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(Thread.currentThread().getName()+"正在售卖第"+ticket+"张票");
}
return false;
}
}
1.8、lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,
但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象LOCK
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法
void lock():
获得锁void unlock():
释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock
的构造方法
ReentrantLock():
创建一个ReentrantLock的实例
package threadTest;
public class test2 {
public static void main(String[] args) {
test2_thread tt = new test2_thread();
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
Thread t3 = new Thread(tt);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
package threadTest;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class test1_thead extends Thread{
static int ticket = 0;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock .lock();
try {
if (ticket<100){
Thread.sleep(100);
ticket++;
System.out.println(getName()+"正在销售第"+ticket+"张票");
}else {
break;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
lock.unlock();
}
}
}
}
1.9、死锁
- 两个锁嵌套,导致无法出锁
package threadTest;
public class test3 {
public static void main(String[] args) {
test3_thread t1 = new test3_thread();
test3_thread t2 = new test3_thread();
t1.setName("线程A");
t2.setName("线程B");
t1.start();
t2.start();
}
}
package threadTest;
public class test3_thread extends Thread {
static Object A = new Object();
static Object B = new Object();
@Override
public void run() {
while (true) {
if ("线程A".equals(getName())){
synchronized (A){
System.out.println("线程A拿到了A锁,准备拿B锁");
synchronized (B){
System.out.println("线程A拿到了B锁,顺利执行完一轮");
}
}
}else if ("线程B".equals(getName())){
if ("线程B".equals(getName())){
synchronized (B){
System.out.println("线程B拿到了B锁,准备拿A锁");
synchronized (A){
System.out.println("线程B拿到了A锁,顺利执行完一轮");
}
}
}
}
}
}
}
1.10、生产者和消费者(等待唤醒机制)
生产者消费者模式是一个十分经典的多线程协作的模式
常见方法
方法名称 | 说明 |
---|---|
void wait() | 当前线程等待,直到被其他线程唤醒 |
void notify() | 随机唤醒单个线程 |
void notifyAll() | 唤醒所有线程 |
package threadTest;
public class test4 {
public static void main(String[] args) {
test4_Foodie f = new test4_Foodie();
test4_Cook c = new test4_Cook();
f.start();
c.start();
}
}
package threadTest;
public class test4_Desk extends Thread {
//总个数
public static int count = 10;
//食物标志
public static int foodflog = 1;
//锁对象
public static Object lock = new Object();
}
package threadTest;
public class test4_Cook extends Thread{
@Override
public void run() {
while (true){
synchronized (test4_Desk.lock){
if (test4_Desk.count==0){
break;
}else {
//判断桌子上是否有面条
if (test4_Desk.foodflog == 1){
try {
test4_Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
//制作食物
System.out.println("厨师正在制作食物");
//制作结束唤醒吃货
test4_Desk.lock.notifyAll();
//改变桌子状态
test4_Desk.foodflog = 1;
}
}
}
}
}
}
package threadTest;
public class test4_Foodie extends Thread {
@Override
public void run() {
while (true){
synchronized (test4_Desk.lock){
if (test4_Desk.count==0){
break;
}else {
//判断桌子上是否有面条
if (test4_Desk.foodflog == 0){
try {
test4_Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
//把吃的总数-1
test4_Desk.count--;
//如果有
System.out.println("吃货还能吃"+test4_Desk.count+"碗面条");
//吃完之后唤醒厨师
test4_Desk.lock.notifyAll();
//改变桌子的状态
test4_Desk.foodflog = 0 ;
}
}
}
}
}
}
1.11、等待唤醒机制(阻塞队列方式实现)
put数据时:放不进去,会等着,也叫做阻塞
take数据时:取出第一个数据,取不到会等着,也叫做阻塞。
生产者和消费者必须使用同一个队列
package threadTest;
import java.util.concurrent.ArrayBlockingQueue;
public class test5 {
public static void main(String[] args) {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);//需要指定上限
test5_Cook c = new test5_Cook(queue);
test5_Fooder f = new test5_Fooder(queue);
c.start();
f.start();
}
}
package threadTest;
import java.util.concurrent.ArrayBlockingQueue;
public class test5_Cook extends Thread {
ArrayBlockingQueue<String> queue;
test5_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);
}
}
}
}
package threadTest;
import java.util.concurrent.ArrayBlockingQueue;
public class test5_Fooder extends Thread{
ArrayBlockingQueue<String> queue;
test5_Fooder(ArrayBlockingQueue<String> queue){
this.queue = queue;
}
@Override
public void run() {
while (true){
try {
queue.take();
System.out.println("吃货吃了一碗面条");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
1.12、线程的状态
二、综合练习
2.1、卖电影票
package thread_Integrated_exercises;
public class test1 {
public static void main(String[] args) {
test1_thread t1 = new test1_thread();
test1_thread t2 = new test1_thread();
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
package thread_Integrated_exercises;
import java.util.EmptyStackException;
public class test1_thread extends Thread {
static int tacket = 0;
@Override
public void run() {
while (true){
synchronized (test1_thread.class){
if (tacket==1000) {
break;
}else {
tacket++;
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName()+"正在销售第"+tacket+"张票");
}
}
}
}
}
2.2、送礼品
package thread_Integrated_exercises.test2;
public class test2_ {
public static void main(String[] args) {
test2_thread t1 = new test2_thread();
test2_thread t2 = new test2_thread();
t1.setName("用户一");
t2.setName("用户二");
t1.start();
t2.start();
}
}
package thread_Integrated_exercises.test2;
public class test2_thread extends Thread{
static int giving = 100;
@Override
public void run() {
while (true){
synchronized (test2_thread.class){
if (giving<10){
break;
}else {
try {
sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName()+"送出"+"第"+giving+"份礼物");
giving--;
System.out.println("还剩"+giving+"份礼物");
}
}
}
}
}
2.3、打印奇数数字
package thread_Integrated_exercises.test3;
public class test3_ {
public static void main(String[] args) {
test3_thread t1 = new test3_thread();
test3_thread t2 = new test3_thread();
Thread tt1 = new Thread(t1);
Thread tt2 = new Thread(t2);
tt1.start();
tt2.start();
}
}
package thread_Integrated_exercises.test3;
public class test3_thread implements Runnable {
static int num = 100;
@Override
public void run() {
while (true){
synchronized (test3_thread.class){
if (num<=0){
break;
}else {
if (num%2==0){
num--;
continue;
}else {
System.out.println(num);
num--;
}
}
}
}
}
}
2.4、抢红包
package thread_Integrated_exercises.test4;
public class test4_ {
public static void main(String[] args) {
test4_thread t1 = new test4_thread();
test4_thread t2 = new test4_thread();
test4_thread t3 = new test4_thread();
test4_thread t4 = new test4_thread();
test4_thread t5 = new test4_thread();
t1.setName("用户1");
t2.setName("用户2");
t3.setName("用户3");
t4.setName("用户4");
t5.setName("用户5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
package thread_Integrated_exercises.test4;
import java.util.Random;
public class test4_thread extends Thread{
//设置红包个数
static int count = 3;
//设置红包的大小
static double money = 100;
//设置最小金额
final double MIN = 0.01;
private double prize;
@Override
public void run() {
synchronized (test4_thread.class){
if (count==0){
//如果红包没了则显示没有抢到红包
System.out.println(getName()+"没抢到红包");
}else {
if (count==1){
//如果红包就剩一个了则全部的都是最后一个用户的红包
prize = money;
}else {
Random r = new Random();
//判断随机数的范围
//要留出最小金额
double bounds = money - (count -1)*MIN;
prize = r.nextDouble(bounds);
//当金额小于最小金额时则抢到的金额就是最小的金额
if (prize<MIN){
prize = MIN;
}
}
money = money - prize;
count--;
System.out.println(getName()+"抢到了"+prize+"元");
}
}
}
}
类的第二种写法(精确金额)
package thread_Integrated_exercises.test4;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Random;
public class test4_thread2 extends Thread {
//设置红包个数
static int count = 3;
//设置红包的大小
static BigDecimal money = BigDecimal.valueOf(100.0);
//设置最小金额
final BigDecimal MIN = BigDecimal.valueOf(0.01);
@Override
public void run() {
synchronized (test4_thread.class){
BigDecimal prize;
if (count==0){
//如果红包没了则显示没有抢到红包
System.out.println(getName()+"没抢到红包");
}else {
if (count==1){
//如果红包就剩一个了则全部的都是最后一个用户的红包
prize = money;
}else {
Random r = new Random();
//判断随机数的范围
//要留出最小金额
double bounds = money.subtract(BigDecimal.valueOf(count-1).multiply(MIN)).doubleValue();
//抽奖金额
prize = BigDecimal.valueOf(r.nextDouble(bounds));
//当金额小于最小金额时则抢到的金额就是最小的金额
if (prize.equals(MIN)){
prize = MIN;
}
}
//设置抽中红包,小数点后保留两位
prize = prize.setScale(2, RoundingMode.HALF_UP);
money = money.subtract(prize);
count--;
System.out.println(getName()+"抢到了"+prize+"元");
}
}
}
}
2.5、抽奖箱抽奖
package thread_Integrated_exercises.test5;
import java.util.ArrayList;
import java.util.Collections;
public class test_ {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,100,200,20,600,30,100,2000,800,60,30,770,20,1100,260,50,40,300,90,60);
test_thread t1 = new test_thread(list);
test_thread t2 = new test_thread(list);
t1.setName("抽奖箱一");
t2.setName("抽奖箱二");
t1.start();
t2.start();
}
}
package thread_Integrated_exercises.test5;
import java.util.ArrayList;
import java.util.Collections;
public class test_thread extends Thread{
ArrayList<Integer> list;
public test_thread(ArrayList<Integer> list){
this.list = list;
}
@Override
public void run() {
while (true){
synchronized (test_thread.class){
if (list.size() == 0){
break;
}else {
Collections.shuffle(list);
int prize = list.remove(0);
System.out.println(getName()+"抽到了"+prize+"元");
}
}
try {
sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
2.6、多线程统计并求最大值
package thread_Integrated_exercises.test6;
import java.util.ArrayList;
import java.util.Collections;
public class tset6_ {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,100,200,20,600,30,100,2000,800,60,30,770,20,1100,260,50,40,300,90,60);
test6_thread t1 = new test6_thread(list);
test6_thread t2= new test6_thread(list);
t1.setName("抽奖箱一");
t2.setName("抽奖箱二");
t1.start();
t2.start();
}
}
package thread_Integrated_exercises.test6;
import java.util.ArrayList;
import java.util.Collections;
public class test6_thread extends Thread{
ArrayList<Integer> list;
public test6_thread(ArrayList<Integer> list){
this.list = list;
}
@Override
public void run() {
ArrayList<Integer> list_ = new ArrayList<>();
while (true){
synchronized (test6_thread.class){
if (list.size() == 0){
System.out.println(getName()+list_);
System.out.println(getName()+"最大值是:"+Collections.max(list_));
int sum = 0;
for (Integer integer : list_) {
sum = sum + integer;
}
System.out.println("总和是"+sum);
break;
}else {
Collections.shuffle(list);
int prize = list.remove(0);
//System.out.println(getName()+"抽到了"+prize+"元");
list_.add(prize);
}
}
try {
sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
2.7、多线程统计并求最大值
package thread_Integrated_exercises.test7;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class test7_ {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,100,200,20,600,30,100,2000,800,60,30,770,20,1100,260,50,40,300,90,60);
test7_thread t1 = new test7_thread(list);
test7_thread t2 = new test7_thread(list);
FutureTask<Integer> ft1 = new FutureTask<>(t1);
FutureTask<Integer> ft2 = new FutureTask<>(t2);
Thread tt1 = new Thread(ft1);
Thread tt2 = new Thread(ft2);
tt1.setName("抽奖箱一");
tt2.setName("抽奖箱二");
tt1.start();
tt2.start();
Integer max1 = ft1.get();
Integer max2 = ft2.get();
int max = 0;
if (max1>max2){
max = max1;
}else {
max = max2;
}
System.out.println(max);
}
}
package thread_Integrated_exercises.test7;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.Callable;
public class test7_thread implements Callable<Integer> {
ArrayList<Integer> list;
public test7_thread(ArrayList<Integer> list){
this.list = list;
}
@Override
public Integer call() throws Exception {
ArrayList<Integer> list_ = new ArrayList<>();
while (true){
synchronized (test7_thread.class){
if (list.size() == 0){
System.out.println(Thread.currentThread().getName()+list_);
System.out.println(Thread.currentThread().getName()+"最大值是:"+ Collections.max(list_));
int sum = 0;
for (Integer integer : list_) {
sum = sum + integer;
}
System.out.println("总和是"+sum);
break;
}else {
Collections.shuffle(list);
int prize = list.remove(0);
//System.out.println(getName()+"抽到了"+prize+"元");
list_.add(prize);
}
}
Thread.sleep(100);
}
if (list_==null){
return null;
}else {
return Collections.max(list_);
}
}
}
三、线程池
3.1、线程池概述
- 以前写多线程的弊端
- 用到线程的时候就创建
- 用完之后线程消失
线程池主要核心原理
- 创建一个池子,池子中是空的
- 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子。下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
- 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
Executors: 线程池的工具类通过调用方法返回不同类型的线程池对象
创建线程池方法:
方法名称 | 说明 |
---|---|
public static Executorservice newCachedThreadPool() | 创建一个没有上限的线程池(int的上限) |
public static ExecutorService newFixedThreadPool(int nThreads) | 创建有上限的线程池 |
package threadPool.Dome1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Dome {
public static void main(String[] args) throws InterruptedException {
//获取线程池对象
ExecutorService pool1 = Executors.newCachedThreadPool();
//提交任务
pool1.submit(new myThread());
pool1.submit(new myThread());
pool1.submit(new myThread());
pool1.submit(new myThread());
pool1.submit(new myThread());
pool1.submit(new myThread());
//获取有限的线程池
ExecutorService pool2 = Executors.newFixedThreadPool(3);
//无论添加多少个线程,线程池中都只有3个线程
pool2 .submit(new myThread());
pool2 .submit(new myThread());
pool2 .submit(new myThread());
pool2 .submit(new myThread());
//销毁线程池
pool1.shutdown();
pool2.shutdown();
}
}
package threadPool.Dome1;
public class myThread implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+"======"+"在打印"+i);
}
}
}
3.2、自定义线程池
任务拒绝策略
任务拒绝策略 | 说明 |
---|---|
ThreadPoolExecutor.AbortPolicy | 默认策略: 丢弃任务并抛出RejectedExecutionException异常 |
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常这是不推荐的做法 |
ThreadPoolExecutor.Discardoldestpolicy | 抛弃队列中等待最久的任务然后把当前任务加入队列中 |
ThreadPoolExecutor.CallerRunsPolicy | 调用任务的run()方法绕过线程池直接执行 |
package threadPool.Dome2;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Dome {
public static void main(String[] args) {
/*
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略)
参数一:核心线程数量 不能小于e
参数二:最大线程数 不能小于e,最大数量>=核心线程数量
参数三:空闲线程最大存活时间 不能小于0
参数四:时间单位 用TimeUnit指定
参数五:任务队列 不能为null
参数六:创建线程工厂 不能为null
参数七:任务的拒绝策略 不能为null
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
3,//核心线程数量
6,//线程总数量
60,//空闲线程最大存活时间
TimeUnit.SECONDS,//时间单位
new ArrayBlockingQueue<>(3),//任务队列
Executors.defaultThreadFactory(),//创建线程工厂
new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
);
threadPoolExecutor.submit(new Thread());
}
}
不断的提交任务,会有以下三个临界点:
- 当核心线程满时,再提交任务就会排队
- 当核心线程满,队伍满时,会创建临时线程
- 当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略
最大并行数:
- 就是处理器的最大线程数
可以通过Runtime.getRuntime().availableProcessors()
获取
线程池多大合适
-
CPU密集型运算
最大并行数+1
-
IO密集型运算
最大并行数 * 期望CPU利用率 *总时间(CPU计算时间+等待时间)/CPU计算时间
-
thread dump 可以计算出CPU计算时间和等待时间