Java 多线程
1、简介
看起来同一时间多任务,实则同一时刻仍只在做同一件事情。
程序、进程、线程
program、process、thread
线程是CPU调度和执行的单位。进程是系统资源分配的单位。程序是指令和数据的有序集合。
2、创建
创建方式有三种:
- Thread class (继承Thread类)
- Runnable (实现Runnable接口)
- Callable接口 (实现Callable接口)
线程开启不一定立即执行,由CPU来调度执行。
实现Thread类:
public class TestThread1 extends Thread{
@Override
public void run() {
//run线程
for(int i=0;i<20;i++){
System.out.println("This is another thread!");
}
}
public static void main(String[] args) {
Thread thread=new TestThread1();
//run()会直接先执行run()方法体,start()才会开辟一个新的线程走新方法
thread.start();
//thread.run();
//主线程
for(int i=0;i<20;i++){
System.out.println("This is main thread!");
}
}
}
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//多线程下载图片
public class TestThreadPic extends Thread{
private String url;
private String name;
public TestThreadPic(String url,String name){
this.url=url;
this.name=name;
}
@Override
public void run() {
WebDownloader webDownloader=new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载的文件名为:"+name);
}
public static void main(String[] args) {
TestThreadPic testThreadPic1=new
TestThreadPic("https://hiimg.cqqnb.net/csimage/202207/01/1656662607274.jpg","1.jpg");
TestThreadPic testThreadPic2=new
TestThreadPic("https://hiimg.cqqnb.net/csimage/202207/01/1656662607274.jpg","2.jpg");
TestThreadPic testThreadPic3=new
TestThreadPic("https://hiimg.cqqnb.net/csimage/202207/01/1656662607274.jpg","3.jpg");
testThreadPic1.start();
testThreadPic2.start();
testThreadPic3.start();
}
}
class WebDownloader{
//下载方法
public void downloader(String url,String name) {
try{
FileUtils.copyURLToFile(new URL(url),new File(name));
}catch (IOException e){
e.printStackTrace();
System.out.print("IO异常");
}
}
}
下载的文件名为:2.jpg
下载的文件名为:3.jpg
下载的文件名为:1.jpg
实现Runnable接口:
推荐使用,避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用。
实现Callable接口:
可以抛出异常,可以定义返回值
实现Callable接口,需要返回值类型
重写call方法,需要抛出异常
创建目标对象
创建执行服务:
ExecutorServicer ser =Executors.newFixedThreadPool(1);
提交执行:
Future<Boolean> result1=ser.submit(t1);
获取结果:
boolean r1=result1.get();
关闭服务:
ser.shutdownNow();
3、静态代理
public class TestStaticProxy {
public static void main(String[] args) {
WeddingCompany weddingCompany=new WeddingCompany(new You());
weddingCompany.HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
//真实角色
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("Happy Marry");
}
}
//代理角色
class WeddingCompany implements Marry{
private Marry target;
public WeddingCompany(Marry marry){
target=marry;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void before(){
System.out.println("Before Marry");
}
private void after(){
System.out.println("After Marry");
}
}
特点:
- 代理对象与真实对象都实现同一个接口;
- 代理对象可以做真实对象做不了的事情;
- 真实对象专注实现核心方法(做自己的事)。
多线程中Thread类代理Runnable接口,类似。
4、Lambda表达式
避免匿名内部类明明过多
其实质属于函数式编程的概念
代码更简洁,去掉无意义代码,只留下核心逻辑
- (params)->expression[表达式]
- (params)->statement[语句]
- (params)->{statements}
例:
new Thread(()->System.out.println("Thread Learning!")).start();
函数式接口:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。(如Runnable接口中只有Run())。
public class TestLambda {
//3.静态内部类
static class Like2 implements ILike{
@Override
public void lambda() {
System.out.println("I Like lambda2");
}
}
public static void main(String[] args) {
ILike like1=new Like1();
like1.lambda();
ILike like2=new Like2();
like2.lambda();
//4.局部内部类
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("I Like lambda3");
}
}
ILike like3=new Like3();
like3.lambda();
//5.匿名内部类
ILike like4=new ILike(){
@Override
public void lambda() {
System.out.println("I Like lambda4");
}
};
like4.lambda();
//lambda简化
ILike like5=()->{
System.out.println("I Like lambda5")
};
like5.lambda();
}
}
//1.定义一个函数式接口
interface ILike{
void lambda();
}
//2.实现类
class Like1 implements ILike{
@Override
public void lambda() {
System.out.println("I Like lambda1");
}
}
5、线程状态
创建状态:
Thread t=new Thread();
就绪状态:
当调用start()方法时,线程立即进入就绪状态,但不意味着立即运行。
运行状态:
此时系统开始执行线程内的代码块。
阻塞状态:
调用sleep(),wait()或同步锁定时,线程进入阻塞状态,阻塞事件解除后,重新进入就绪状态,等待CPU调度执行。
每个对象都有一把锁,sleep()不会释放锁。
死亡状态:
线程中断或结束。进入死亡状态的线程无法再次启动。
线程优先级:
-
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
-
线程的优先级用数字表示,范围从1~10。
Thread.MIN_PRIORITY=1; Thread.MAX_PRIORITY=10; Thread.NORM_PRIORITY=5;
-
使用以下方法改变或获取优先级:
getPriority(); setPriority(int);
优先级低的线程意味着获得调度的概率低,并不意味着优先级低就不被调用了。
6、守护(daemon)线程:
- 线程分为用户线程和守护线程。
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不必等待守护线程执行完毕
- 如,后台记录操作日志,监控内存,垃圾回收等待。
thread.setDaemon(true);
默认是用户线程,需自己设置守护线程。
7、线程同步
并发:同一对象被多个线程同时操作。
同步:线程同步其实是一种等待机制,多个需要同时访问某个资源的线程进入 资源的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
锁:保证线程安全。每个对象拥有一把锁。synchronized,但一个线程获得资源的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
- 一个线程持有锁会导致其他需要此锁的线程挂起;
- 在多线程竞争下,加锁、释放锁会导致较多的上下文切换和调度延时,引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。
package syn;
public class UnSafeBuyTic {
public static void main(String[] args) {
BuyTicket buyTicket=new BuyTicket();
new Thread(buyTicket,"张三").start();
new Thread(buyTicket,"李四").start();
new Thread(buyTicket,"王五").start();
}
}
class BuyTicket implements Runnable{
private int ticketNum=10;
boolean flag=true;
@Override
public void run() {
while (flag){
try{
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
buy();
}
}
//synchronized关键字加锁
private synchronized void buy(){
if(ticketNum<=0){
flag=false;
return;
}
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNum--);
}
}
8、死锁
8.1、死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形,被称为死锁。某一个同步块同时拥有两个以上对象的锁时,就可能发生死锁问题。
8.2、Lock锁
显式定义同步锁实现同步,是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应获得Lock对象。
ReentrantLock类实现了Lock接口,拥有与Synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁。
ReentrantLock.lock();
ReentrantLock.unLock();
使用Lock锁,JVM将花费较少的时间调度线程,性能更好,并具有更好的扩展性。
Lock>同步代码块(已经进入方法体,分配了相应资源)>同步方法(在方法体之外)
9、线程协作
9.1、生产者消费者问题
方法名 | 作用 |
---|---|
wait() | 表示线程会一直等待,直到其他线程通知,与sleep()不同,会释放锁 |
wait(long) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 |
只能在同步方法与同步代码块内使用。
9.1.1管程法
9.1.2信号灯法
10、线程池
//建池
ExecutorService service=Executors.newFixedThreadPool(10);
//添加线程
service.execute(new Thread());
//关闭连接
service.shutdown();
一直等待,直到其他线程通知,与sleep()不同,会释放锁 |
| wait(long) | 指定等待的毫秒数 |
| notify() | 唤醒一个处于等待状态的线程 |
| notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 |
只能在同步方法与同步代码块内使用。
9.1.1管程法
9.1.2信号灯法
10、线程池
//建池
ExecutorService service=Executors.newFixedThreadPool(10);
//添加线程
service.execute(new Thread());
//关闭连接
service.shutdown();