多线程
重点 线程实现&&线程同步
一、创建方式
- 继承Thread类
- 实现Runnable类
二、Thread类
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体。
- 创建线程对象,调用start()方法启动线程
//创建线程的方式一:继承Thread类,重写run()方法,调用start()开启线程
//总结:注意,线程开启不一定立即执行,由cpu调度执行
public class TestThread1 extends Thread{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码----" + i);
}
}
public static void main(String[] args) {
//主方法,main线程
//创建一个线程对象
TestThread1 thread1 = new TestThread1();
//调用start()方法,开启线程
thread1.start();
for (int i = 0; i < 200; i++) {
System.out.println("我在学习多线程----" + i);
}
}
}
网图下载练习
package com.kuang.demo01;
import com.sun.org.apache.bcel.internal.generic.NEW;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* @author yt
* @create 2021-07-31 15:49
*/
//练习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("http://www.news.cn/titlepic/112771/1127714905_1627653219979_title0h.jpg","1.jpg");
TestThread2 t2 = new TestThread2("http://www.news.cn/titlepic/112771/1127714905_1627653229676_title0h.jpg", "2.jpg");
t1.start();
t2.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()方法启动线程
四、小结
- 继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 不建议使用:避免OOP单继承局限性
- 实现Runnable接口
- 实现Runnable接口具有多线程能力
- 启动线程:new Thread(目标对象).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(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
boolean flag = gameOver(i);
if (flag){
break;
}
System.out.println(Thread.currentThread().getName() + "-->跑了" + i + "步");
}
}
public 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();
}
}
五、静态代理模式
package com.kuang.demo01;
/**
* @author yt
* @create 2021-08-01 17:18
*/
//静态代理模式总结:
//真实对象和代理对象都要实现同一个接口
//好处:
//代理对象可以做很多真实对象不能做的事情
//真实对象就专注做自己的事情
public class StacticProxy {
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("qing要结婚了,很开心!");
}
}
//代理角色,帮助你结婚
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表达式
为什么有Lamda表达式?
- 避免匿名内部类定义过多
- 让代码看起来更简洁
- 去掉一些没有意见的代码,只留下核心的逻辑
函数式接口的定义:
-
任何一个接口只包含唯一一个抽象方法,那么它就是一个函数式接口
public interface Runnable{ public abstract void run(); }
-
对于函数式接口,我们可以通过Lamda表达式来创建该接口的对象
package com.kuang.lamda;
/**
* @author yt
* @create 2021-08-01 20:35
*/
//推导lamda表达式
public class TestLamda1 {
//3.静态内部类
static class Like2 implements ILike{
@Override
public void lamda() {
System.out.println("i like lamda2!");
}
}
public static void main(String[] args) {
ILike like = new Like();
like.lamda();
like = new Like2();
like.lamda();
//4.局部内部类
class Like3 implements ILike{
@Override
public void lamda() {
System.out.println("i like lamda3!");
}
}
like = new Like3();
like.lamda();
//5.匿名内部类,没有类的名称,必须借助接口或者父类
like = new ILike() {
@Override
public void lamda() {
System.out.println("i like lamda4!");
}
};
like.lamda();
//6.用lamda表示
like = ()-> {
System.out.println("i like lamda5!");
};
like.lamda();
}
}
//1、定义一个函数式接口
interface ILike{
public abstract void lamda();
}
//2.实现类
class Like implements ILike{
@Override
public void lamda() {
System.out.println("i like lamda!");
}
}
总结
- lamda表达式只能有一行代码的情况下才能简化为一行,如果有多行,就写成代码块
- 前提必须是函数式接口
- 多个参数也可以去掉参数类型,要去掉都得去掉
love = a -> System.out.println("i like you " + a);
七、线程停止
package com.kuang.state;
/**
* @author yt
* @create 2021-08-02 10:17
*/
//测试stop
//1.建议线程正常停止--->利用次数,不建议使用死循环
//2.建议使用标志位--->立一个flag
//3.不要使用stop或者destroy等过时或者JDK不推荐使用的方法
public class TestStop implements Runnable{
//设置标志位
private boolean flag = true;
@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 < 1000; i++) {
System.out.println("main " + i);
if (i == 900){
//切换标志位,让线程停止
testStop.stop();
System.out.println("线程停止!");
}
}
}
}
八、线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数;
- sleep()存在异常InterruptedException;
- sleep()时间到达后线程进入就绪状态;
- sleep()可以模拟网络延迟或倒计时等;
- 每个对象都有一把锁,sleep不会释放锁。
九、线程礼让——yield
//礼让不一定成功,看CPU的心情
public class TestYield {
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
//想象为插队
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("线程vip来了" + i);
}
}
public static void main(String[] args) throws InterruptedException {
//启动我们的线程
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
//主线程
for (int i = 0; i < 1000; i++) {
if(i == 200){
thread.join();//插队
}
System.out.println("main" + i);
}
}
}
十一、守护(daemon)线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不需要确保守护线程执行完毕
thread.setDaemon(true).默认是false表示用户线程,正常的线程都是用户线程
十三、线程同步
//不安全的取钱 //两个人去取钱public class UnsafeBank { public static void main(String[] args) { Account account = new Account(100, "结婚基金"); Drawing you = new Drawing(account, 50, "你"); Drawing girlFriend = new Drawing(account, 100, "girlFriend"); you.start(); girlFriend.start(); }}//账户class Account{ int money;//余额 String name;//卡名 public Account(int money, String name) { this.money = money; this.name = name; }}//银行:模拟取款class Drawing extends Thread{ Account account;//账户 int drawingMoney;//取多少钱 int nowMoney;//还有多少钱 public Drawing (Account account, int drawingMoney,String name){ super(name); this.account = account; this.drawingMoney = drawingMoney; } //取钱 //synchronized 默认锁的是this @Override public void run() { //锁的对象就是变化的量,需要增删改的对象 synchronized (account){ //判断有没有钱 if (account.money - drawingMoney < 0){ System.out.println(Thread.currentThread().getName() + "钱不够,不能取"); return; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } account.money = account.money - drawingMoney; nowMoney = nowMoney + drawingMoney; System.out.println(account.name + "余额为" + account.money); //Thread.currentThread().getName()==this.getName() System.out.println(this.getName() + "手里的钱" + nowMoney); } }}
//不安全的买票public class UnsafeBuyTiket { public static void main(String[] args) { BuyTicket ticket = new BuyTicket(); new Thread(ticket,"小明").start(); new Thread(ticket,"老师").start(); new Thread(ticket,"黄牛").start(); }}class BuyTicket implements Runnable{ //票 private int ticketNums = 10; boolean flag = true; @Override public void run() { //买票 while (flag){ try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } //synchronized 同步方法,锁的是this private synchronized void buy() throws InterruptedException { //判断是否有票 if (ticketNums <= 0){ flag = false; return; } //模拟延时 Thread.sleep(100); System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--); }}
十四、Lock锁
package com.kuang.senior;import java.util.concurrent.locks.ReentrantLock;/** * @author yt * @create 2021-08-03 14:23 */public class TestLock { public static void main(String[] args) { TestLock2 testLock2 = new TestLock2(); new Thread(testLock2).start(); new Thread(testLock2).start(); new Thread(testLock2).start(); }}class TestLock2 implements Runnable{ int ticketNums = 10; //定义锁 private final ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true){ try { lock.lock();//加锁 }finally { if (ticketNums > 0 ){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(ticketNums--); }else { break; } //解锁 lock.unlock(); } } }}
小结:synchronized 与 Lock 的对比
- Lock是显示锁(手动开关锁,不要忘记关锁),synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序
- lock-> 同步代码块->同步方法