学习笔记19.多线程
线程介绍
三种创建方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
Thread
创建线程方式一:继承thread类,重写run()方法,调用start开启线程。
不建议使用:避免OPP单继承局限性
创建示例:
public class TestThrea1 extends Thread{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 200; i++) {
System.out.println("我在看代码----"+i);
}
}
public static void main(String[] args) {
//main线程,主线程
//创建一个线程对象
TestThrea1 testThrea1 = new TestThrea1();
//调用start()方法开启线程
testThrea1.start();
for (int i = 0; i < 200; i++) {
System.out.println("我在学习多线程----"+i);
}
}
}
注意:线程,线程开启不一定立即执行,由cpu调度执行。
用Thread多线程写的一个图片下载器:
public class TestThread2 extends Thread {
private String url; //网络图片地址
private String name; //保存的文件名
public TestThread2(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) {
TestThread2 t1 = new TestThread2("https://...","1.jpg");
TestThread2 t2 = new TestThread2("https://...","2.jpg");
TestThread2 t3 = new TestThread2("https://...","3.jpg");
t1.start();
t2.start();
t3.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.println("IO异常,downloader方法出现问题");
}
}
}
Runnable
创建方法:
- 定义类并实现Runnable接口
- 实现run()方法
- 创建线程对象,调用start()方法启动线程
推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用。
示例:
public static void main(String[] args) {
//创建runnable接口的实现对象
TestThread3 testThread3 = new TestThread3();
//创建线程对象,通过线程对象来开启我们的线程 代理
new Thread(testThread3).start();
}
龟兔赛跑:
public class Race implements Runnable{
//胜利者
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (Thread.currentThread().getName().equals("兔子")&& i%10 ==0 ){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果比赛结束,就停止程序
//判断比赛是否结束
boolean flag = gameOver(i);
if (flag){
break;
}
System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
}
}
//判断是否完成比赛
private boolean gameOver(int steps){
//判断是否有胜利者
if (winner!=null){//已经存在胜利者
return true;
}{
if(steps==100){
winner = Thread.currentThread().getName();
System.out.println("winner is "+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
Callable(了解)
创建方法:
- 实现Callable类
- 重写call方法
使用方法:
//创建执行服务
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.shutdownNow();
callable的好处:
- 可以定义返回值。
- 可以抛出异常。
静态代理模式
示例:
public class StaticProxy {
public static void main(String[] args) {
You you = new You(); //你要结婚
WeddingCompany weddingCompany = new WeddingCompany(you);
weddingCompany.HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
//真实角色,你去结婚
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("秦老师要结婚了,超开心");
}
}
//代理角色,帮助你结婚
class WeddingCompany implements Marry{
//代理谁--》正式目标角色
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();//这就是真实对象
after();
}
private void after() {
System.out.println("结婚之后,收尾款");
}
private void before() {
System.out.println("结婚之前,布置现场");
}
}
总结:
- 真实对象和代理对象都要实现同一个接口
- 代理对象必须代理真实角色
好处:
- 代理对象可以做很多真实对象做不了的事情
- 真实对象专注做自己的事情
Lamda表达式
为什么要使用lambda表达式:
- 避免匿名内部类定义过多
- 可以让你的代码看起来更简洁
- 去掉了一堆没有意义的代码,只留下核心的逻辑。
函数式接口的定义:
- 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
- 对于函数式接口,我们可以通过lambda表达式来创建改接口的对象。
实现函数接口方式的不断简化:
public class TestLambda1 {
//3.静态内部类
static class Like2 implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda2");
}
}
public static void main(String[] args) {
ILike like = new Like();
like.lambda();
like = new Like2();
like.lambda();
//4.局部内部类
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda3");
}
}
like = new Like3();
like.lambda();
//5.匿名内部类,没有类的名称,必须借助接口或者父类
like = new ILike() {
@Override
public void lambda() {
System.out.println("i like lambda4");
}
};
like.lambda();
//6.用lambda简化
like = () ->{
System.out.println("i like lambda4");
};
like.lambda();
}
}
//1.定义一个函数式接口
interface ILike{
void lambda();
}
//2.实现类
class Like implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda");
}
}
lambda表达式也可以简化:
//1.lambda表达式简化
Ilove love = (int a) -> {
System.out.println("i love you-->"+a);
};
//简化1.参数类型
love = (a) -> {
System.out.println("i love you-->"+a);
};
//简化2.简化括号
love = a -> {
System.out.println("i love you-->" + a);
};
//简化3.去掉花括号(前提是只有一行代码)
love = a -> System.out.println("i love you-->" + a);
love.love(521);
总结:
- lambda表达式只能有一行代码的情况下才能简化为一行,如果有多行,那么就有代码块包裹(花括号)。
- 使用lambda表达式的前提是接口为函数式接口。
- 多个参数类型也可以去掉参数类型,要去掉就都去掉,必须加上括号。
线程状态

停止线程
- 不推荐使用JDK提高的stop()、destroy()等过时或者JDK不建议的方法。
- 建议线程正常停止--->利用次数,不建议死循环
- 建议使用标志位
示例:
public class TestStop implements Runnable{
//1.设置一个标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run...Thread"+i++);
}
}
//2.设置一个公开的方法停止线程,转换标志位
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 < 1000; i++) {
System.out.println("main"+i);
if (i==900){
//调用stop方法切换标志位,让让线程停止
testStop.stop();
System.out.println("线程该停止了");
}
}
}
}
线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让cpu重写调度,礼让不一定成功!看CPU心情
Join
- Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。(可以想象成插队)
线程状态观测
线程可以处于以下状态之一:
-
NEW尚未启动的线程处于此状态。
-
RUNNABLE
在Java虚拟机中执行的线程处于此状态。 -
BLOCKED
被阻塞等待监视器锁定的线程处于此状态。 -
WAITING
正在等待另一个线程执行特定动作的线程处于此状态。 -
TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。 -
TERMINATED
已退出的线程处于此状态。
可用thread.getState();来观测线程当前处于什么状态
线程优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
线程优先级用数字表示,范围1~10。
- MIN_PRIORITY = 1
- NORM_PRIORITY = 5
- MAX_PRIORITY = 10
使用以下方法改变线程优先级(优先级的设定建议在start()调度前:
- setPriority(int xxx)
注意:优先级低只是意味着获得调度的概率低。并不是优先级低就不会被调用了。这都看CPU的调度
守护(daemon)线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕
如:后台记录操作日志,监控内存,垃圾回收等待...
设置守护线程可用:thread.setDaemon(true)
线程同步
并发:同一个对象被多个线程同时操作。
同步方法
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块。
同步方法:public synchronized viod method(int args){}
同步方法缺陷:若将一个大的方法声明为synchronized将会大大影响效率。
注意:
- synchronized默认锁的是this。
同步块
同步块:synchornized(Obj) { }
Obj 称之为 同步监视器
- Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器,锁定对象就是变化的量
- 同步方法中无需指定同步监视器,因为同步监视器就是this,就是这个对象本身,或者是class
死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程哦都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
Lock
通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
class A {
private final ReentrantLock lock = new ReentrantLock();
public void m() {
lock.lock();
try {
//保证线程安全的代码
}finally {
lock.unlock();
//如果同步代码块有异常,要将unlock()写入finally语句块
}
}
线程协作
线程通信
应用场景:生产者消费者问题
- 生产者生产,消费者消费,两条线程之间可进行通信。
java提供了几个方法解决线程之间的通信问题:
- wait() 表示线程一直等待,直到其他线程通知,与sleep不同,会一直释放锁
- wait(long timeout) 指定等待的毫秒数
- notify() 唤醒一个处于等待状态的线程
- notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度
注意:都只能在同步方法或同步代码块中使用。
解决方式1:管程法
生产者:负责生产数据的模块
消费者:负责处理数据的模块
缓冲区:消费者不能直接使用生产者的数据,他们之间有个缓冲区,生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据。
解决方式2:信号灯发
即用标志位控制线程的唤醒和停止。
补充:线程池
好处:
- 提高响应速度(减小了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
public static void main(String[] args) {
//1.创建服务,创建线程池
//newFixedThreadPool 参数为:线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//2.关闭链接
service.shutdown();
}

浙公网安备 33010602011771号