JAVA学习-多线程

多线程

Thread

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程

注意:如果调用run()方法,则只有主线程一条执行路径,但调用start()方法,则多条执行路径,主线程和子线程并行交替执行

package com.myThread.demo01;

public class Demo01 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        for (int i = 0; i < 10000; i++) {
            System.out.println("我是主线程--"+i);
        }
    }
}
///////////////////////////
package com.myThread.demo01;

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("我是子线程--"+i);
        }
    }
}

**线程的执行由CPU决定 **

网络图片下载

需要用到commons io包 百度下载二进制文件 然后复制粘贴到idea新建一个lib包里即可

package com.myThread.demo02;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

public class Demo01 {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread("https://img2022.cnblogs.com/blog/1755845/202202/1755845-20220206182923194-2117509516.png","4.jpg");
        MyThread myThread2 = new MyThread("https://img2022.cnblogs.com/blog/1755845/202203/1755845-20220303112203654-338817152.png","5.jpg");
        MyThread myThread3 = new MyThread("https://img2022.cnblogs.com/blog/1755845/202202/1755845-20220223223043340-1183125272.png","6.jpg");
        myThread1.start();
        myThread2.start();
        myThread3.start();
    }
}
//子线程写下载,实现多线程下载资源
class MyThread extends Thread{
    private String myUrl = null;
    private String myname = null;

    public MyThread(String url,String name) {
        this.myname = name;
        this.myUrl = url;
    }

    @Override
    public void run() {
        WebDownload webDownload = new WebDownload();
        webDownload.download(myUrl,myname);
        System.out.println("下载好了"+myname);
    }
}
//下载器
class WebDownload{
    public void download(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("下载器方法出现异常!");
        } finally {
        }

    }
}

再次证明了线程是由CUP分配的,人为无法干预

Runnable接口

创建线程方式2:实现runnable接口,重写run方法,执行线程需要丢入对象

package com.myThread.demo03;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("我在看书--"+i);
        }
    }
}
////////////////////////
package com.myThread.demo03;

public class Demo01 {
    public static void main(String[] args) {
        new Thread(new MyRunnable()).start();//这里new一个Thread 然后把实现runnable接口的对象丢进去
        for (int i = 0; i < 2000; i++) {
            System.out.println("我在玩手机--"+i);
        }


    }
}

最好使用runnable接口实现多线程,避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

初识并发问题

package com.myThread.demo04;

public class MyTicket implements Runnable{
    private int ticketNums = 1;

    @Override
    public void run() {
        while (true){
            if (ticketNums>=20){
                break;
            }
            try {
                Thread.sleep(200);//模拟延时
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
            }
            //Thread.currentThread().getName() 可以拿到当前线程的名字 这里三线程同用一个对象 
            System.out.println(Thread.currentThread().getName()+"--抢到了第"+ticketNums+++"张票!");
        }
    }
}
////////////////////
package com.myThread.demo04;

public class Demo01 {
    public static void main(String[] args) {
        MyTicket ticket = new MyTicket();//对象new一个就行了
        //三线程同用一个对象
        new Thread(ticket,"张三").start();
        new Thread(ticket,"李四").start();
        new Thread(ticket,"王五").start();

    }
}

运行结果:

李四和王五都抢到了第二张票,说明线程不安全,出现了数据紊乱。这就是线程并发出现的问题。

龟兔赛跑

要求跑道100,兔子乌龟一起跑,但兔子要睡觉,乌龟取得胜利

package com.myThread.demo05;

public class Race implements Runnable{
    private int steps = 1;
    private static String winner;
    @Override
    public void run() {
        while (true){

            if (Thread.currentThread().getName().equals("兔子") && steps%10 == 0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                }
            }

            if (theWinner(steps)){//小细节,判断要放在兔子休眠后,可以避免兔子在接受比赛后又多跑一步
                break;
            }

            System.out.println(Thread.currentThread().getName()+"--已经跑了"+steps+++"步");


        }
    }
    public boolean theWinner(int steps){
        if (winner != null){
            return true;//这句话必须写,要不然兔子或乌龟一个跑完了另一个线程不会停止!!
        }
        else if (steps>=100 && winner == null){
            winner = Thread.currentThread().getName();
            System.out.println(winner+"获得了胜利!");
            return true;
        }
        return false;

    }
}
////////////////////////////
package com.myThread.demo05;

public class Demo01 {
    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race,"乌龟").start();
        new Thread(race,"兔子").start();
    }
}

Callable接口

不是很明白,以后再来!

静态代理

真实对象与代理对象实现同一个接口,代理对象要代理真实对象。真实对象通过构造器传给代理对象,代理对象就可以不仅完成真实对象的方法,还可以做很多真实对象没有做的事情,真实对象只需要专注自己的事情。

Lambda表达式

Lambda可以简化代码,但注意:

  • 接口必须是函数式接口,也就是接口只能存在一种带实现的方法
  • 方法内代码如果只有一行,可以简化不写花括号,如果有多行,必须使用代码块包裹,也就是花括号
  • 接口待实现方法含参,可以去掉参数类型,多个参数也能去掉参数类型,要去掉就都去掉,必须加上括号(参数只有一个,括号也能去掉)
  • 格式为 接口 名字 = (参数) -> {代码};
package com.myThread.demo06;

public class Demo01 {
    public static void main(String[] args) {
        Marry marry = null;
        marry = ()-> System.out.println("lalla");
        marry.makeMarry();
    }
}
interface Marry{
    void makeMarry();
}

线程停止

线程停止,自己在while循环里用一个flag标志位来控制

.sleep

线程休眠

利用休眠做一个定时器,每一秒刷新一次时间

package com.myThread.demo07;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class Demo01 {
    public static void main(String[] args) {
        Runnable runnable = null;
        runnable = ()->{
            while (true){
                LocalDateTime localDateTime = LocalDateTime.now();//获取时间
                DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");//调整格式
                System.out.println(dateTimeFormatter.format(localDateTime));//按照格式输出时间
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                }

            }
        };
        new Thread(runnable).start();
    }
}

.yield

线程礼让

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让cpu重新调度,礼让不一定成功!看cpu心情

.join

合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞

可以理解为插队

package com.myThread.demo08;

public class Demo08 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("我是vip--"+i);
        }
    }

    public static void main(String[] args) {
        Demo08 demo08 = new Demo08();
        Thread thread = new Thread(demo08);
        thread.start();

        for (int i = 0; i < 2000; i++) {
            System.out.println("我是主线程--"+i);
            if (i == 1000){
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                }
            }
        }

    }
}

由上,让线程插队,等主线程1000时插队,但主线程没到1000时也会跑,完全听从cpu调度,但一旦主线程到达1000就强行插队,知道线程跑完再开始主线程

观测线程

Thread.State state = thread.getState();
System.out.println(state);

线程优先级

**set priority() get priority() **

守护线程

set daemon()

守护线程会随着用户线程结束,虚拟机停止就停止了。虚拟机不会等待守护线程的运行。

package com.myThread.demo09;

public class Demo01 {
    public static void main(String[] args) {
        You you = new You();
        God god = new God();
        new Thread(you).start();
        Thread thread = new Thread(god);
        thread.setDaemon(true);
        thread.start();
    }
}

class You implements Runnable{

    @Override
    public void run() {
        for (int i = 1; i < 36500; i++) {
            System.out.println("活着的第--"+i+"天");
        }
        System.out.println("=====goodbye,world!=====");
    }
}
class God implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("老天爷看着你");
        }
    }
}

上面的god线程虽然用了while(true)循环,但程序也会随着用户线程的停止然后虚拟机的停止而停止。(虚拟机的停止需要一小点时间)

不安全问题

银行取钱的不安全问题,代码稍微有点不理想,将就吧,也确实出现了线程不安全

package com.unsafeDemo.demo01;
//银行取钱线程不安全
public class Demo01 {
    public static void main(String[] args) {
        Account account = new Account(100,"全部家当");
        Bank bank = new Bank(account,50,"小明");
        Bank bank1 = new Bank(account,70,"小红");
        new Thread(bank).start();
        new Thread(bank1).start();
    }
}
class Account {
    int money;
    String name;
    public Account(int money, String name){
        this.money = money;
        this.name = name;

    }
}

class Bank implements Runnable{
    Account account ;
    private int drawingMoney;
    private String name;
    public Bank(Account account, int drawingMoney, String name){

       this.drawingMoney = drawingMoney;
       this.account = account;
       this.name = name;
    }
    public void drawingMoney(){
        account.money = account.money - drawingMoney;
        System.out.println(this.name+"取了"+drawingMoney);
        System.out.println("余额为"+account.money);
    }

    @Override
    public void run() {
        if (drawingMoney > account.money){
            System.out.println("没钱了,取鸡毛");
            return;
        }
        try {
            Thread.sleep(100);//利用延迟放大问题
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        drawingMoney();


    }
}

同步块

synchronized (Obj){}

Obj可以是任何对象,要填入需要被锁的对象,也就是有变化的对象,然后再将变化过程的代码放在这个同步块里面。

Lock

可显示话的锁,一般使用ReentrantLock类(可重入锁)

先复习不加锁:

package com.unsafeDemo.demo02;

public class Demo02 {
    public static void main(String[] args) {
        BuyTicks ticks = new BuyTicks();
        Thread t1 = new Thread(ticks,"我");
        Thread t2 = new Thread(ticks,"你");
        Thread t3 = new Thread(ticks,"他");
        t1.start();
        t2.start();
        t3.start();
    }
}
class BuyTicks implements Runnable{
    private int ticks = 10;
    boolean flag = true;

    @Override
    public void run() {
        while(flag) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (ticks > 0) {
                System.out.println(Thread.currentThread().getName() + "抢到了第" + ticks-- + "票");
            }else {
                flag = false;
            }
        }
    }
}

加锁代码:

package com.unsafeDemo.demo02;

import java.util.concurrent.locks.ReentrantLock;

public class Demo02 {
    public static void main(String[] args) {
        BuyTicks ticks = new BuyTicks();
        Thread t1 = new Thread(ticks,"我");
        Thread t2 = new Thread(ticks,"你");
        Thread t3 = new Thread(ticks,"他");
        t1.start();
        t2.start();
        t3.start();
    }
}
class BuyTicks implements Runnable{
    private int ticks = 10;
    boolean flag = true;
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {

        try {
            lock.lock();
            while(flag) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (ticks > 0) {
                    System.out.println(Thread.currentThread().getName() + "抢到了第" + ticks-- + "票");
                }else {
                    flag = false;
                }
            }
        } finally {
            lock.unlock();

        }
    }
}

注意,new ReentrantLock的时候给他加修饰符private final,不懂,但是照葫芦画瓢,然后就是加锁和解锁,放在catch-finally里面,不懂,继续照葫芦画瓢。

生产者消费者

wait() notifyAll() 是只能写在synchronized里面的

package com.myPC.demo03;

public class Demo03 {
    public static void main(String[] args) {
        MyContainer myContainer = new MyContainer();
        new Productor(myContainer).start();
        new Consumer(myContainer).start();

    }
}
class Productor extends Thread{
    MyContainer myContainer;

    public Productor(MyContainer myContainer) {
        this.myContainer = myContainer;
    }

    @Override
    public void run() {
        for (int i = 1; i < 100; i++) {
            myContainer.push(new Chicken(i));
            System.out.println("生产了第"+i+"只鸡");

        }

    }
}
class Consumer extends Thread{
    MyContainer myContainer;

    public Consumer(MyContainer myContainer) {
        this.myContainer = myContainer;
    }

    @Override
    public void run() {
        for (int i = 1; i < 100; i++) {
            System.out.println("消费了第"+myContainer.del().id+"只鸡");
        }


    }

}
class Chicken{
    int id;

    public Chicken(int id) {
        this.id = id;
    }
}
class  MyContainer{
    Chicken[] chickens = new Chicken[10];
    int count = 0;
    public synchronized void push(Chicken chicken){
        while (count >= chickens.length){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        chickens[count] = chicken;
        count++;
        notifyAll();//唤醒所有线程,包括没有鸡时停止的消费线程

    }

    public synchronized Chicken del(){
        if (count <= 0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;
        Chicken chicken = chickens[count];
        notifyAll();//唤醒所有线程,包括鸡满了停止的生产线程
        return chicken;
    }


}

关于wait() 与 notifyAll() 的思考

package com.myPC.demo04;

import java.util.concurrent.locks.ReentrantLock;

public class Demo04 {
    public static void main(String[] args) {
        abc abc = new abc();
        new Thread(abc,"a").start();
        new Thread(abc,"b").start();
    }

}
class abc implements Runnable{
    int count = 1;
    @Override
    public synchronized void run() {
        for (int i = 0; i < 19; i++) {


            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            while (Thread.currentThread().getName().equals("a") && count%5==0  ){
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("a被唤醒!");
                }
            while (Thread.currentThread().getName().equals("b") && count%4==0 ){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("b被唤醒!");
            }
                System.out.println(Thread.currentThread().getName()+"--得到了"+count);
                count++;
                notifyAll();
            
        }
    }

}

这段代码,就是让ab去获取数字,一开始,a被cpu指派去获取,但到a走进while循环里面碰见wait的时候,a停了,这个时候,cpu会直接让b接进来继续获取数字,并且通过notifyAll把a唤醒,a会不会获取数字看cpu调度,但是一旦b走进while循环碰见wait,则会让a继续从a停止的wait那里开始往下走。只要a从休眠状态被唤醒再被cpu调度,都是继续从wait那里开始执行。

信号灯法

利用一个flag标志位来解决生产者消费者的问题


线程池

ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

Executors:工具类,线程池的工厂类,用于创建并返回不同类型的线程池

package com.myPC.demo05;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo05 {
    public static void main(String[] args) {
        //创建一个线程池服务
        ExecutorService service = Executors.newFixedThreadPool(3);//参数的意义是线程池的大小
        //执行
        service.execute(new MyPool());
        service.execute(new MyPool());
        service.execute(new MyPool());
        //关闭线程池
        service.shutdown();

    }
}
class MyPool implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

这里与Callable有点相似,这是属于Runnable接口的线程池

posted @ 2022-03-07 18:44  老哥不老  阅读(31)  评论(0编辑  收藏  举报