多线程 【详解】| Java 学习日志 | 第 14 天
线程
线程是操作系统能够运算调度的最小单位。包含在进程当中,是进程中的实际运作单位。
拷贝迁移大文件,加载大量资源。
并发:在同一个时刻,有多个指令在单个CPU上交替执行。
并行:在同一个时刻,有多个指令在多个CPU上同时执行。
多线程实现方式
多线程实现方式一,继承Thread重写run方法。
public class Thread1 extends Thread{@Overridepublic void run() {//重写run方法,其中就是你要这个线程执行的方法for (int i = 0; i < 100; i++) {System.out.println(getName() + i);}}public static void main(String[] args) {Thread1 t1 = new Thread1();Thread1 t2 = new Thread1();t1.setName("1");t2.setName("2");t1.start();t2.start();}
}
多线程第二种实现方式,实现Runnable接口重写run方法。
public class Thread2 implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + i);}}public static void main(String[] args) {Thread2 t = new Thread2();Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.setName("1");t2.setName("2");t1.start();t2.start();}
}
多线程的第三种实现方式,实现Callable接口重写call,这是能返回线程运行的结果。
创建子类的对象,表示多线程要执行的任务。
创建FutureTask的对象,作用管理多线程的结果。
最后创建线程(Thread)对象。
public class Thread3 implements Callable<Integer> {@Overridepublic Integer call() throws Exception {int a = 100;return a;}public static void main(String[] args) throws ExecutionException, InterruptedException {Thread3 t3 = new Thread3();FutureTask<Integer> ft = new FutureTask<>(t3);Thread t = new Thread(ft);t.start();Integer res = ft.get();System.out.println(res);}
}
线程常用方法
String getName() 返回此线程的名称
void setName(String name) 设置线程的名字(构造方法也可以设置名字)
细节:
1、如果我们没有给线程设置名字,线程也是有默认的名字的
格式:Thread-X(X序号,从0开始的)
2、如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置
static Thread currentThread() 获取当前线程的对象
细节:
当JVM虚拟机启动之后,会自动的启动多条线程
其中有一条线程就叫做main线程
他的作用就是去调用main方法,并执行里面的代码
在以前,我们写的所有的代码,其实都是运行在main线程当中
static void sleep(long time) 让线程休眠指定的时间,单位为毫秒
细节:
1、哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
2、方法的参数:就表示睡眠的时间,单位毫秒
1 秒= 1000毫秒
3、当时间到了之后,线程会自动的醒来,继续执行下面的其他代码
public class Thread1 extends Thread{public Thread1() {}public Thread1(String name) {super(name);}@Overridepublic void run() {//重写run方法,其中就是你要这个线程执行的方法for (int i = 0; i < 100; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(getName() + i);}}public static void main(String[] args) throws InterruptedException {System.out.println("fljg");//相当于main线程停留Thread.sleep(4000);System.out.println("fdgg");Thread1 t1 = new Thread1("1");Thread1 t2 = new Thread1("2");t1.start();t2.start();}
}
线程的优先级
setPriority(int newPriority) 设置线程的优先级
final int getPriority() 获取线程的优先级
抢占式调度,优先级是个概率问题,优先级不一定就先执行完
public class Thread2 implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + i);}}public static void main(String[] args) {//创建线程要执行的参数对象Thread2 mr = new Thread2();//创建线程对象Thread t1 = new Thread(mr,"飞机");Thread t2 = new Thread(mr,"坦克");t1.setPriority(1);t2.setPriority(10);t1.start();t2.start();}
}
守护线程
final void setDaemon(boolean on) 设置为守护线程
当其他的非守护线程执行完毕之后,守护线程会陆续结束。
public class Thread1 extends Thread{public Thread1() {}public Thread1(String name) {super(name);}@Overridepublic void run() {if (getName().equals("1")) {for (int i = 0; i < 10; i++) {System.out.println(getName() + i);}} else {for (int i = 0; i < 100; i++) {System.out.println(getName() + i);}}}public static void main(String[] args) throws InterruptedException {Thread1 t1 = new Thread1("1");Thread1 t2 = new Thread1("2");t2.setDaemon(true);t1.start();t2.start();}
}
出让线程/插入线程
public static void yield() 出让线程/礼让线程
public class Thread1 extends Thread{public Thread1() {}public Thread1(String name) {super(name);}@Overridepublic void run() {if (getName().equals("1")) {for (int i = 0; i < 10; i++) {System.out.println(getName() + i);Thread.yield();}} else {for (int i = 0; i < 100; i++) {System.out.println(getName() + i);}}}public static void main(String[] args) throws InterruptedException {Thread1 t1 = new Thread1("1");Thread1 t2 = new Thread1("2");t2.setDaemon(true);t1.start();t2.start();}
}
public final void join() 插入线程/插队线程
public class Thread1 extends Thread{public Thread1() {}public Thread1(String name) {super(name);}@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(getName() + i);}}public static void main(String[] args) throws InterruptedException {Thread1 t1 = new Thread1("哈哈哈");t1.start();//表示把t1这个线程,插入到当前线程之前。//当前线程: main线程t1.join();//执行在main线程中for (int i = 0; i < 10; i++) {System.out.println(i);}}
}
线程的安全问题
同步代码块,注意变量用static修饰,synchronized (obj)中参数需要唯一。
public class Thread1 extends Thread{public Thread1() {}public Thread1(String name) {super(name);}static int ticket = 0;static Object obj = new Object();@Overridepublic void run() {while (true) {synchronized (obj) {if (ticket < 10000) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket++;System.out.println(getName() + "-" + ticket);} else {break;}}}}public static void main(String[] args) throws InterruptedException {/*需求:某电影院目前正在上映国产大片,共有10000张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票*///创建线程对象Thread1 t1 = new Thread1();Thread1 t2 = new Thread1();Thread1 t3 = new Thread1();//起名字t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//开启线程t1.start();t2.start();t3.start();}
}
将同步代码块修改成同步方法。
public class MyRunnable implements Runnable {int ticket = 0;@Overridepublic void run() {//1.循环while (true) {//2.同步方法(先写同步代码块再改成同步方法)if (method()) {break;}}}//thisprivate synchronized boolean method() {//3.判断共享数据是否到了末尾,如果到了末尾if (ticket == 1000) {return true;} else {//4.判断共享数据是否到了末尾,如果没有到末尾try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}ticket++;System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票!!!");}return false;}public static void main(String[] args) {/*需求:某电影院目前正在上映国产大片,共有1000张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票利用同步方法完成技巧:同步代码块*/MyRunnable mr = new MyRunnable();Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);Thread t3 = new Thread(mr);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}
多线程环境下选用StringBuffer这个是线程安全的。
lock锁
synchronized不能手动释放锁。
lock需要Lock类子类ReentrantLock创建对象,并且需要static修饰,lock锁使用try catch finally释放锁。
finally块中的代码无论是否发生异常都会执行,这保证了锁一定会被释放。
public class MyThread extends Thread{static int ticket = 0;static Lock lock = new ReentrantLock();@Overridepublic void run() {//1.循环while(true){//2.同步代码块//synchronized (MyThread.class){lock.lock(); //2 //3try {//3.判断if(ticket == 1000){break;//4.判断}else{Thread.sleep(10);ticket++;System.out.println(getName() + "在卖第" + ticket + "张票!!!");}// }} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}}public static void main(String[] args) {/*需求:某电影院目前正在上映国产大片,共有1000张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票用JDK5的lock实现*/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();}
}
死锁
当两个线程同时执行时,可能会发生:
线程A获取objA锁,
线程B获取objB锁,
线程A等待线程B释放objB锁,
线程B等待线程A释放objA锁,
双方互相等待,形成死锁。
public class MyThread extends Thread {static Object objA = new Object();static Object objB = new Object();@Overridepublic void run() {//1.循环while (true) {if ("线程A".equals(getName())) {synchronized (objA) {System.out.println("线程A拿到了A锁,准备拿B锁");//Asynchronized (objB) {System.out.println("线程A拿到了B锁,顺利执行完一轮");}}} else if ("线程B".equals(getName())) {if ("线程B".equals(getName())) {synchronized (objB) {System.out.println("线程B拿到了B锁,准备拿A锁");//Bsynchronized (objA) {System.out.println("线程B拿到了A锁,顺利执行完一轮");}}}}}}public static void main(String[] args) {/*需求:死锁*/MyThread t1 = new MyThread();MyThread t2 = new MyThread();t1.setName("线程A");t2.setName("线程B");t1.start();t2.start();}
}
等待唤醒机制
1.生产者和消费者实现等待唤醒机制
使用Desk.lock调用wait()让当前线程跟锁进行绑定,这样notify就会唤醒这个锁绑定的线程
class Desk {/** 作用:控制生产者和消费者的执行** *///是否有面条 0:没有面条 1:有面条public static int foodFlag = 0;//总个数public static int count = 10;//锁对象public static Object lock = new Object();}class Cook extends Thread{@Overridepublic void run() {/** 1. 循环* 2. 同步代码块* 3. 判断共享数据是否到了末尾(到了末尾)* 4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)* */while (true){synchronized (Desk.lock){if(Desk.count == 0){break;}else{//判断桌子上是否有食物if(Desk.foodFlag == 1){//如果有,就等待try {Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}else{//如果没有,就制作食物System.out.println("厨师做了一碗面条");//修改桌子上的食物状态Desk.foodFlag = 1;//叫醒等待的消费者开吃Desk.lock.notifyAll();}}}}}
}
class Foodie extends Thread{@Overridepublic void run() {/** 1. 循环* 2. 同步代码块* 3. 判断共享数据是否到了末尾(到了末尾)* 4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)* */while(true){synchronized (Desk.lock){if(Desk.count == 0){break;}else{//先判断桌子上是否有面条if(Desk.foodFlag == 0){//如果没有,就等待try {//使用Desk.lock调用wait()让当前线程跟锁进行绑定//这样notify就会唤醒这个锁绑定的线程Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}else{//把吃的总数-1Desk.count--;//如果有,就开吃System.out.println("吃货在吃面条,还能再吃" + Desk.count + "碗!!!");//吃完之后,修改桌子的状态Desk.foodFlag = 0;//唤醒厨师继续做Desk.lock.notifyAll();}}}}}
}
public class ThreadDemo {public static void main(String[] args) {/*** 需求:完成生产者和消费者(等待唤醒机制)的代码* 实现线程轮流交替执行的效果** *///创建线程的对象Cook c = new Cook();Foodie f = new Foodie();//给线程设置名字c.setName("厨师");f.setName("吃货");//开启线程c.start();f.start();}
}
2.阻塞队列实现等待唤醒机制
ArrayBlockingQueue底层是数组,有界。LinkedBlockingQueue底层是链表,无界但不是真正的无界,最大为int的最大值。
class Foodie extends Thread{ArrayBlockingQueue<String> queue;public Foodie(ArrayBlockingQueue<String> queue) {this.queue = queue;}@Overridepublic void run() {while(true){//不断从阻塞队列中获取面条try {String food = queue.take();//这里的打印语句写在了锁的外面所以输出会显得随机System.out.println(food);} catch (InterruptedException e) {e.printStackTrace();}}}
}class Cook extends Thread{ArrayBlockingQueue<String> queue;public Cook(ArrayBlockingQueue<String> queue) {this.queue = queue;}@Overridepublic void run() {while(true){//不断的把面条放到阻塞队列当中try {queue.put("面条");System.out.println("厨师放了一碗面条");} catch (InterruptedException e) {e.printStackTrace();}}}
}public class ThreadDemo {public static void main(String[] args) {/*** 需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码* 细节:* 生产者和消费者必须使用同一个阻塞队列** *///创建阻塞队列对象ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);//创建线程的对象Cook c = new Cook(queue);Foodie f = new Foodie(queue);//给线程设置名字c.setName("厨师");f.setName("吃货");//开启线程c.start();f.start();}
}
线程的生命周期
线程状态。线程可以处于下列状态之一:
NEW 至今尚未启动的线程处于这种状态。
RUNNABLE 正在 Java 虚拟机中执行的线程处于这种状态。(就绪)
BLOCKED 受阻塞并等待某个监视器锁的线程处于这种状态。
WAITING 无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
TIMED_WAITING 等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
TERMINATED 已退出的线程处于这种状态。
在给定时间点上,一个线程只能处于一种状态。这些状态是虚拟机状态,它们并没有反映所有操作系统线程状态。
这几个中没有 运行 状态,这个状态Java不管理。
练习:两个人共同发放礼物,数量低于10都没法发放。
class Gift implements Runnable {int num = 1000;@Overridepublic void run() {while(true) {synchronized (Gift.class) {if (num <= 10) {break;} else {num--;System.out.println(Thread.currentThread().getName() + "发放礼物" + num);}}}}
}public class ThreadDemo {public static void main(String[] args) {Gift r = new Gift();Thread t1 = new Thread(r, "1");Thread t2 = new Thread(r, "2");t1.start();t2.start();}
}
练习:同时开启两个线程,共同获取1-1000之间的所有数字,要求:将输出所有的奇数。
class Odd implements Runnable {int num = 0;@Overridepublic void run() {while(true) {synchronized (Odd.class) {if (num > 1000) {break;} else {if (num % 2 == 1) {System.out.println(Thread.currentThread().getName() + "奇数" + num);}num++;}}}}
}public class ThreadDemo {public static void main(String[] args) {/*同时开启两个线程,共同获取1-1000之间的所有数字。要求:将输出所有的奇数。*/Odd r = new Odd();Thread t1 = new Thread(r, "1");Thread t2 = new Thread(r, "2");t1.start();t2.start();}
}
练习:设置多个人抢少量的红包。
public class MyThread extends Thread {static int count = 5;static double money = 100;@Overridepublic void run() {synchronized (MyThread.class) {if (count == 0) {System.out.println("已经没有了");} else {if(count == 1) {System.out.println(getName() + "获取" + money);} else {Random r = new Random();double temp = money - count * 0.01;temp = r.nextDouble() * money + 0.01;System.out.println(getName() + "获取" + temp);money -= temp;}count--;}}}public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();MyThread t4 = new MyThread();MyThread t5 = new MyThread();MyThread t6 = new MyThread();t1.setName("线程1");t2.setName("线程2");t3.setName("线程3");t4.setName("线程4");t5.setName("线程5");t6.setName("线程6");t1.start();t2.start();t3.start();t4.start();t5.start();t6.start();}
}
练习:有一个抽奖池,该抽奖池中存放了奖励的金额,如{10,5,20,50,100,200,500,800}创建几个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”随机从抽奖池中获取奖项元素。
public class MyThread extends Thread {ArrayList<Integer> list;public MyThread(ArrayList<Integer> list) {this.list = list;}@Overridepublic void run() {while (true) {synchronized (MyThread.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);}}}public static void main(String[] args) {ArrayList<Integer> list = new ArrayList<>();Collections.addAll(list, 100, 70, 80, 90, 120, 1000);MyThread t1 = new MyThread(list);MyThread t2 = new MyThread(list);MyThread t3 = new MyThread(list);MyThread t4 = new MyThread(list);MyThread t5 = new MyThread(list);MyThread t6 = new MyThread(list);t1.setName("线程1");t2.setName("线程2");t3.setName("线程3");t4.setName("线程4");t5.setName("线程5");t6.setName("线程6");t1.start();t2.start();t3.start();t4.start();t5.start();t6.start();}
}
练习:有一个抽奖池,该抽奖池中存放了奖励的金额,如{10,5,20,50,100,200,500,800}创建几个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”随机从抽奖池中获取奖项元素,打印每个抽奖箱最后获得的元素。
public class MyThread extends Thread {ArrayList<Integer> list;public MyThread(ArrayList<Integer> list) {this.list = list;}ArrayList<Integer> list1 = new ArrayList<>();ArrayList<Integer> list2 = new ArrayList<>();@Overridepublic void run() {while (true) {synchronized (MyThread.class) {if(list.size() == 0) {if(getName().equals("线程1")) {System.out.println(getName() + list1);System.out.println(Collections.max(list1));} else {System.out.println(getName() + list2);System.out.println(Collections.max(list2));}break;} else {Collections.shuffle(list);int num = list.remove(0);if(getName().equals("线程1")) {list1.add(num);} else {list2.add(num);}System.out.println(num);}}//休息写在锁的外面给其他线程腾出时间try {sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}}public static void main(String[] args) {ArrayList<Integer> list = new ArrayList<>();Collections.addAll(list, 100, 70, 80, 90, 120, 1000);MyThread t1 = new MyThread(list);MyThread t2 = new MyThread(list);t1.setName("线程1");t2.setName("线程2");t1.start();t2.start();}
}
优化上一段程序,采用线程栈。
原理main线程额外开启两体新线程,总共三条线程,start线程后内存中总共开辟了三条线程栈,其中的变量都是自己的局部变量。
public class MyThread extends Thread {ArrayList<Integer> list;public MyThread(ArrayList<Integer> list) {this.list = list;}@Overridepublic void run() {ArrayList<Integer> list1 = new ArrayList<>();while (true) {synchronized (MyThread.class) {if(list.size() == 0) {System.out.println(getName() + list1);System.out.println(Collections.max(list1));break;} else {Collections.shuffle(list);int num = list.remove(0);list1.add(num);System.out.println(num);}}//休息写在锁的外面给其他线程腾出时间try {sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}}public static void main(String[] args) {ArrayList<Integer> list = new ArrayList<>();Collections.addAll(list, 100, 70, 80, 90, 120, 1000);MyThread t1 = new MyThread(list);MyThread t2 = new MyThread(list);t1.setName("线程1");t2.setName("线程2");t1.start();t2.start();}
}
练习:实现Callable接口重写call,返回所有线程获取的最大值。
public class MyCallable implements Callable<Integer> {ArrayList<Integer> list;public MyCallable(ArrayList<Integer> list) {this.list = list;}@Overridepublic Integer call() throws Exception {ArrayList<Integer> list1 = new ArrayList<>();while (true) {synchronized (MyThread.class) {if(list.size() == 0) {System.out.println(Thread.currentThread().getName() + list1);System.out.println(Collections.max(list1));break;} else {Collections.shuffle(list);int num = list.remove(0);list1.add(num);System.out.println(num);}}//休息写在锁的外面给其他线程腾出时间Thread.sleep(100);}return Collections.max(list1);}public static void main(String[] args) throws ExecutionException, InterruptedException {ArrayList<Integer> list = new ArrayList<>();Collections.addAll(list, 100, 70, 80, 90, 120, 1000);MyCallable mc = new MyCallable(list);FutureTask<Integer> ft1 = new FutureTask<>(mc);FutureTask<Integer> ft2 = new FutureTask<>(mc);Thread t1 = new Thread(ft1);Thread t2 = new Thread(ft2);t1.setName("线程1");t2.setName("线程2");t1.start();t2.start();int max1 = ft1.get();int max2 = ft2.get();int max = max1 > max2 ? max1 : max2;System.out.println("最大值" + max);}
}
线程池
创建池子开始是空的,提交任务池子会创建对象,任务执行完毕,线程归还池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可。如果提交任务时,池子中没有空闲
public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池
public static ExecutorService newFixedThreadPool (int nThreads) 创建有上限的线程池
public class MyRunnable implements Runnable {@Overridepublic void run() {//循环for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() +": " + i);}}public static void main(String[] args) {//1.获取线程池对象ExecutorService pool1 = Executors.newFixedThreadPool(3);//2.提交任务pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());//3.销毁线程池pool1.shutdown();}
}
1.自定义线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
参数一:核心线程数量 不能小于0
参数二:最大线程数 不能小于0,最大数量 >= 核心线程数量
参数三:空闲线程最大存活时间 不能小于0
参数四:时间单位 用TimeUnit指定
参数五:任务队列 不能为null
参数六:创建线程工厂 不能为null
参数七:任务的拒绝策略 不能为null
注意:当核心线程创建满时,再提交任务就需要排队,当核心线程创建满了排队的任务满了时,就会创建临时线程,当核心线程和临时线程满了,任务队列满了,会触发任务拒绝策略。这样解释了为什么后面的线程比中间的线程先运行。
public static void main(String[] args) {ThreadPoolExecutor pool = new ThreadPoolExecutor(3, //核心线程数量,不能小于06, //最大线程数,不能小于0,最大数量 >= 核心线程数量60, //空闲线程最大存活时间TimeUnit.SECONDS, //时间单位new ArrayBlockingQueue<>(3), //任务队列Executors.defaultThreadFactory(), //创建线程工厂new ThreadPoolExecutor.AbortPolicy() //任务的拒绝策略);}
2.线程池的大小
最大并行数,如4核8线,虽然4个CPU内核但使用超线程技术可以运行8个线程(8个逻辑处理器)。当然通过任务管理器查看逻辑处理器个数的不一定就是CPU能给程序应用的最大并行数,可以通过代码获取int count = Runtime.getRuntime().availableProcessors(); 。
CPU密集型运算 最大并行数 + 1。
I/O密集型运算 最大并行数 * 期望CPU利用率 * (总时间(CPU计算时间 + 等待时间)/ CPU计算时间)。