多线程
Process与Thread
程序:程序是指令和数据的有序集合,它本身没有任何运行的含义,是一个静态的概念。
进程:是执行程序的一次执行过程,它是一个动态的的概念,是系统资源分配的单位
普通方法调用和多线程
三种创建方式
Thread class:继承Thread类
-
自定义线程类继承Thread类
-
重写run()方法,编写线程执行体
-
创建线程对象,调用start()方法启动线程
例子
package com.kevin.lesson.lesson6;
//创建线程方式1:继承Thread类 重写run方法 再调用start启动
public class TextThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i <200 ; i++) {
System.out.println("我在++写代码!!");
}
}
public static void main(String[] args) {
TextThread2 textThread2 = new TextThread2();
textThread2.start();
for (int i = 0; i <800 ; i++) {
System.out.println("我在看电视----");
}
}
}
Runnable接口:实现Runnable接口
-
定义MyRunnable类实现Runnable接口
-
实现ru()方法,编写线程执行体
-
创建线程对象,调用start()方法启动线程
例子
package com.kevin.lesson.lesson6; //创建线程方式2:实现runnable接口 重写run方法 执行线程需要放入runnable接口实现类 调用start方法 public class TextThread3 implements Runnable { @Override public void run() { for (int i = 0; i <100 ; i++) { System.out.println("我+++在看代码"); } } public static void main(String[] args) { TextThread3 textThread3 = new TextThread3(); // Thread thread = new Thread(textThread3); // thread.start(); new Thread(textThread3).start(); for (int i = 0; i <500 ; i++) { System.out.println("我在休息!!"); } } }
Callable接口:实现Callable接口
-
实现Callable接口,需要返回值类型
-
重写call方法,需要抛出异常
-
创建目标对象
-
创建执行服务
-
提交执行
-
获取结果
-
关闭服务
好处:可以定义返回值,可以抛出异常
练习
import java.util.concurrent.*; public class TextCallable implements Callable<Boolean>{ private String url; private String name; public TextCallable(String url,String name){ this.name= name; this.url = url; } public TextCallable(String s) { } @Override public Boolean call() throws Exception { WebDownloader webDownloader = new WebDownloader(); webDownloader.downloader(url,name); System.out.println("下载了文件名为"+name); return true; } public static void main(String[] args) throws ExecutionException, InterruptedException { TextCallable t1 = new TextCallable("输入URL地址"); TextCallable t2 = new TextCallable("输入URL地址"); TextCallable t3 = new TextCallable("输入URL地址"); //创建执行服务 ExecutorService ser = Executors.newFixedThreadPool(3); //提交执行 Future<Boolean> r1 = ser.submit(t1); Future<Boolean> r2 = ser.submit(t2); Future<Boolean> r3 = ser.submit(t3); //获取结果 Boolean rs1 = r1.get(); Boolean rs2 = r2.get(); Boolean rs3 = r3.get(); //关闭服务 ser.shutdown(); } }
小结
继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 不建议使用:避免OOP单继承局限性
实现Runnable接口
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便用一个对象被多个线程使用
静态代理
静态代理模式总结
1.真实对象和代理对象都要实现同一个接口
2.代理对象要代理真实角色
好处:代理对象可以做很多真实对象做不了的事;真实对象专注做自己的事情
Lambda表达式
- 避免匿名内部类定义过多
- 使代码简洁
- 去掉没有意义的代码,只留下核心逻辑
函数式接口的定义:任何接口,如果只包含唯一一个抽象方法,那么他就是一个函数式接口。
public interface Runnable{
public abstract void run();
}
练习
package com.kevin.lesson.lesson6;
public class TextLambda {
//静态内部类
static class like2 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda3");
}
}
public static void main(String[] args) {
ILike like = new Like();
like.lambda();
like = new like2();
like.lambda();
//局部内部类
class like3 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda4");
}
}
like=new like3();
like.lambda();
//匿名内部类,没有名次,需要借助接口或父类
like = new ILike() {
@Override
public void lambda() {
System.out.println("I like lambda5");
}
};
like.lambda();
//用lambda简化
like = ()->{
System.out.println("I like lambda2");
};
like.lambda();
}
}
//定义一个函数式接口
interface ILike{
void lambda();
}
//实现类
class Like implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda");
}
}
线程停止
线程了解
停止线程
- 不推荐使用JDK提供的stop()、destroy()方法。(已废弃)
- 推荐线程自己停止下来
- 建议使用一个标志位进行终止变量 当flag=false则终止线程运行
练习
//测试线程停止
//1.不要使用stop destory等过时或JDK不建议使用的方法
//2.建议使线程自己停止--->利用次数,不建议死循环
//3.建议使用标志位--》设置一个标志位
public class TestStop implements Runnable{
private boolean ture;
//设置一个标志位
private boolean flag = ture;
@Override
public void run() {
int i=0;
while (flag){
System.out.println("run...Thread"+i++);
}
}
//设置一个公开的方式停止线程,转换标志位
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 500; i++) {
System.out.println("main"+i);
if (i==400){
//调用stop方法切换标志位,停止线程
testStop.stop();
System.out.println("线程该停止了");
}
}
}
}
线程休眠–sleep
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
练习
import java.util.Date;
import java.text.SimpleDateFormat;
//模拟倒计时
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
//打印系统当前时间
Date startTime =new Date(System.currentTimeMillis()); //获取系统当前时间
while (true){
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime =new Date(System.currentTimeMillis()); //更新时间
}
}
//模拟倒计时
public static void tenDown() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if (num<=0){
break;
}
}
}
}
线程礼让–yield
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让CPU重新调度,礼让不一定成功
练习
//测试礼让,礼让不一定成功,是随机的
public class TextYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"A").start();
new Thread(myYield,"B").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield(); //礼让
System.out.println(Thread.currentThread().getName()+"线程停止");
}
}
线程强制执行–join
Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
练习
//测试线程强制执行,插队
public class TextJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("VIP插队"+i);
}
}
public static void main(String[] args) throws InterruptedException {
TextJoin textJoin = new TextJoin();
Thread thread = new Thread(textJoin);
thread.start();
for (int i = 0; i < 200; i++) {
if (i==80){
thread.join(); //插队
}
System.out.println("main"+i);
}
}
}
观测线程状态
Thread.State
线程状态。线程可以处于以下状态之一:
New
尚未启动的线程处于此状态。
RUNNABLE
在Java虚拟机中执行的线程处于此状态。
BLOCKED
被阻塞等待监视器锁定的线程处于此状态。
WAITING
正在等待另一个线程执行特定动作的线程处于此状态。
TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
TERMINATED
已退出的线程处于此状态。
一个线程可以在给定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。
练习
public class TextState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{ //lambda方法
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("+++++");
});
//观察状态
Thread.State state = thread.getState();
System.out.println(state);
//观察启动之后
thread.start(); //启动线程
state=thread.getState();
System.out.println(state); //Run
while (state != Thread.State.TERMINATED){
Thread.sleep(100);
state=thread.getState(); //更新状态
System.out.println(state); //输出状态
}
}
}
线程优先级
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
- 线程的优先级用数字表示,范围从1~10.
Thread.MIN_PRIORITY= 1;
Thread.MAX_PRIORITY= 10;
Thread.NORM_PRIORITY = 5; - 使用以下方式改变或获取优先级
getPriority() . setPriority(int xxx)
练习
public class TextPriority {
public static void main(String[] args) {
//主线程默认优先级
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1= new Thread(myPriority);
Thread t2= new Thread(myPriority);
Thread t3= new Thread(myPriority);
Thread t4= new Thread(myPriority);
Thread t5= new Thread(myPriority);
//先设置优先级,再启动
t1.start();
t2.setPriority(3);
t2.start();
t3.setPriority(6);
t3.start();
t4.setPriority(7);
t4.start();
t5.setPriority(Thread.MAX_PRIORITY); //最大优先级 10
t5.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
练习
//测试守护线程
public class TextDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true); //默认为false 表示用户线程,正常的线程都是用户线程
thread.start(); //上帝守护线程启动
new Thread(you).start(); //用户线程启动
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝守护");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 1; i < 200; i++) {
System.out.println("开心的活着");
}
System.out.println("=====goodbye=====");
}
}
线程同步
并发:同一个对象被多个线程同时操作。
线程同步
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问
冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制
synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,
使用后释放锁即可.存在以下问题:
一个线程持有锁会导致其他所有需要此锁的线程挂起;
在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引
起性能问题;
如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒
置,引起性能问题.
死锁
产生死锁的四个必要条件:
1.互斥条件:一个资源每次只能被一个进程使用
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
上面四个必要条件,我们组要想办法破其中任意一个或多个条件就可以避免死锁发生。
import java.util.concurrent.locks.ReentrantLock;
public class TextLock{
public static void main(String[] args) {
TextLock2 textLock2 = new TextLock2();
new Thread(textLock2).start();
new Thread(textLock2).start();
new Thread(textLock2).start();
}
}
class TextLock2 implements Runnable {
int ticketNum = 10;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock(); //加锁
if (ticketNum > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNum--);
}
}finally {
//解锁
lock.unlock();
}
}
}
}
Synchronized与Lock的对比
-
Lock是显示锁(手动开启和关闭)synchronized是隐式锁,出了作用域自动释放
-
Lock只有代码块锁,synchronized有代码块锁和方法锁
-
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)
-
优先使用顺序:
Lock>同步代码块(已进入了方法体,分配了相应资源)>同步方法(在方法体之外)
线程协作
生产者消费者问题
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费。
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中中再次放入产品。
//测试 生产者消费者模型---利用缓冲区解决
//生产者,消费者,产品,缓冲区
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Producer(container).start();
new Consumer(container).start();
}
}
//生产者
class Producer extends Thread{
SynContainer container;
public Producer(SynContainer container){
this.container = container;
}
//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了"+i+"只鸡");
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container = container;
}
//消费
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了"+container.pop().id+"只鸡");
}
}
}
//产品
class Chicken{
int id; //产品编号
public Chicken(int id){
this.id = id;
}
}
//缓冲区
class SynContainer{
//需要一个容器大小
Chicken[] chickens = new Chicken[100];
//容器计数器
int count = 0;
//生产者放入产品
public synchronized void push(Chicken chicken){
//如果容器满了,需要等待消费者消费
if (count==chickens.length){
//消费者消费,生产等待
}
//如果没有满,需要放入产品
chickens[count] = chicken;
count++;
}
//消费者购买产品
public synchronized Chicken pop(){
//判断能否消费
if (count==0){
//等待生产
}
//如果可以消费
count--;
Chicken chicken = chickens[count];
return chicken;
}
}
管程法
并发协作模型“生产者Ⅰ消费者模式”—>管程法
生产者:负责生产数据的模块(可能是方法﹐对象﹐线程﹐进程);
消费者:负责处理数据的模块(可能是方法,对象﹐线程﹐进程);
缓冲区:消费者不能直接使用生产者的数据﹐他们之间有个“缓冲区
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
信号灯法
借助标志位
线程池
使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影
响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。
可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理(.….)
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
- JDK 5.0起提供了线程池相关API:ExecutorService和Executors
- ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
- Furturesubmit(Callabletask):执行任务,有返回值,一般用来执行Callable
- void shutdown():关闭连接池
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
public static void main(String[] args) {
//1.创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//结束
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
总结
- 线程就是独立的执行路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程
- main()称之为主线程,为系统的入口,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器与操作系统紧密相关,先后顺序是不能人为的干预
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
- 线程会带来额外的开销,如CPU调度时间,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//总结:线程的创建
public class ThreadSum {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//启动Thread
new MyThread3().start();
//启动Runnable
new Thread(new MyThread4()).start();
//启动callable
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread5());
new Thread(futureTask).start();
Integer integer = futureTask.get();
System.out.println(integer);
}
}
//1.继承Thread类
class MyThread3 extends Thread{
@Override
public void run() {
System.out.println("MyThread++");
}
}
//2.实现Runnable接口
class MyThread4 implements Runnable{
@Override
public void run() {
System.out.println("MyThread!!!");
}
}
//3.实现callable接口
class MyThread5 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("MyThread5***");
return 100;
}
}