当前位置 : 主页 > 编程语言 > 其它开发 >

关于Java的多线程

来源:互联网 收集:自由互联 发布时间:2022-05-30
进程 process 线程 Thread main称之为主线程,为系统的入口,用于执行整个程序 线程创建的三种方式: 继承Thread类 实现Runnable接口 实现Callable接口 继承Thread类: public class Test extends Thread

进程 process   线程 Thread

main称之为主线程,为系统的入口,用于执行整个程序

 

线程创建的三种方式:

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口

 

继承Thread类:

public class Test extends Thread {
    @Override
    public void run() {
        //run 方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println(i);
        }
    }
    public static void main(String[] args) {
        //创建一个线程对象
        Test t=new Test();
        //开启线程
        t.start();  //知道跟run方法的区别
        //main线程 主线程
        for (int i = 0; i < 2000; i++) {
            System.out.println("main方法"+i);
        }
    }
}

分析调用start跟run方法的区别:

  调用start方法主线程跟子线程会并行交替执行。   run方法主线程会先执行完之后再执行子线程。

 

 

 

总结:注意,线程开启不一定立即执行,是由cpu调度的。

 

实现Runnable接口:

  实现该接口,重写run方法,执行线程需要丢入runnable接口实现类。调用start方法

public class Test implements Runnable {
    @Override
    public void run() {
        //run 方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println(i);
        }
    }
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        Test t=new Test();
        
        //创建线程对象,通过线程对象来开启我们的线程,代理
        Thread t1=new Thread(t);
        t1.start();  
        //main线程 主线程
        for (int i = 0; i < 2000; i++) {
            System.out.println("main方法"+i);
        }
    }
}

推荐使用Runnable接口实现多线程

 

小结:

 

 

 

 

关于线程的不安全性示例代码:

public class Test implements Runnable {

    private int ticketNum=10;
    @Override
    public void run() {
        while(true){
            if (ticketNum<=0){break;}
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //run 方法线程体
            System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNum--+"张票");
        }

    }
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        Test t=new Test();
        //创建线程对象,通过线程对象来开启我们的线程,代理
        new Thread(t,"小红").start();
        new Thread(t,"小明").start();
        new Thread(t,"小张").start();

    }
}

 

输出结果为:

 

 

 此时可以看到 同一张票被两个不同的人抢到,此时就表明存在线程安全问题。当多个线程操作一个对象的时候会出现线程安全问题。

 

继续看以下龟兔赛跑代码示例:

public class Test3 implements Runnable{
    private String winner;
    @Override
    public void run() {

        for (int i = 0; i < 101; i++) {
            //模拟兔子休息
            if (Thread.currentThread().getName().equals("兔子")&&i%10==0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (GameOver(i)){
                break;
            }

            System.out.println(Thread.currentThread().getName()+"--->跑了"+i+"步");
        }
    }

    //判断比赛是否结束
    private Boolean GameOver(int step){
        if (winner!=null){
            return true;
        }else {
            if (step>=100){
                winner=Thread.currentThread().getName();
                System.out.println("胜利者是---->"+winner);
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Test3 t=new Test3();
        new Thread(t,"乌龟").start();
        new Thread(t,"兔子").start();

    }

}

当线程为兔子时模拟兔子睡觉,即Thread.sleep()。胜利者一定是乌龟。

 

实现Callable接口:

  

 

 

---------------------------------------------------------------------------------------

扩展知识:

关于静态代理:

    静态代理模式总结:真实对象跟代理对象都要实现同一个接口,代理对象要代理真实角色。

    好处:代理对象可以在做很多真实对象无法实现的事情,真实对象可以专注自己可以做的事。

Thread类的底层实现原理就是使用静态代理类。

 

函数式接口:

  • 任何接口只包含唯一一个抽象方法,那么它就是一个函数式接口。
  • 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。

-----------------------------------------------------------------------------------------

 

线程状态:

  

 

 

线程方法:

  

 

 

停止线程:

 

 

线程礼让:yield 方法。因为线程执行由cpu调度,所以礼让不一定成功,看cpu心情。

线程插队:join 方法。即两个线程在执行的时候,本来主线程会先执行完毕再执行子线程,但是此刻我设定一个条件当主线程执行到某一步的时候

我使用join方法,此时主线程会进行等待,等子线程执行完毕之后才会继续执行。

 

关于线程的状态有以下几种;

 

 

线程优先级:

 

 

 

 

线程分为 用户线程  守护线程 

 

 

 

 

关于线程锁:

    每个对象都有一把锁

  重点:synchronized

1.  同步方法,锁的是This。上面抢票示例代码会出现线程安全问题,当我们在“抢票”的方法上使用 synchronized 时,表示该方法

在执行时会被上锁,只有该方法执行完之后,锁会被释放,后续对象才可以拿到该锁进行处理。Buy()方法上使用锁代表锁的是BuyTicket对象。

 

 

 

  2 . 同步块

 示例代码:

public class Test2 {
    public static void main(String[] args) throws InterruptedException {

        List<String> arrList=new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread((
            )->{
                arrList.add(Thread.currentThread().getName());
            }).start();
        }
        Thread.sleep(2000);
        System.out.println(arrList.stream().count());
    }
}

结果:

 

 从输出结果可看出并未讲10000个线程对象添加到集合中,原因是 两个线程在同一时间将同一个对象添加到了同一个内存位置,因此集合中的对象会出现覆盖的情况,所以产生了此种情况。

处理方法:

public class Test2 {
    public static void main(String[] args) throws InterruptedException {

        List<String> arrList=new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread((
            )->{
                synchronized(arrList){
                    arrList.add(Thread.currentThread().getName());
                }
                
            }).start();
        }
        Thread.sleep(2000);
        System.out.println(arrList.stream().count());
    }
}

将“arrList”对象添加锁,同步代码块。

JUC:CopyOnWriteArrayList  此集合为一个线程安全的集合,与arrayList集合不同。是java.util.concurrent包下的

 

关于死锁:

不要在一个锁还未被释放的时候就想着去取另一个锁,这种情况就容易造成死锁的产生。即在“synchronized”代码块中嵌套"synhronized"

 

 

 

 

 

Lock锁:

 

 

 

Lock锁与synchronized对比:

 

 

生产消费者模型:

1.管程法 (生产者,消费者,产品,缓冲区)

public class Test3 {
    public static void main(String[] args) {
        SynContainer synContainer=new SynContainer();
        new Productor(synContainer).start();
        new Customer(synContainer).start();
    }
}

//生产者
class Productor extends Thread{

    SynContainer synContainer;

    public Productor(SynContainer synContainer){
        this.synContainer=synContainer;
    }

    @Override
    public void run() {
        //生产100只鸡仔
        for (int i = 1; i < 100; i++) {
            try {
                synContainer.push(new Chicken(i));
                System.out.println("生产了---->"+i+"只鸡仔");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}
//消费者
class Customer extends Thread{
    SynContainer synContainer;

    public Customer(SynContainer synContainer){
        this.synContainer=synContainer;
    }
    @Override
    public void run() {
       //消费100只鸡仔
        for (int i = 1; i < 100; i++) {
            try {
                System.out.println("消费了"+synContainer.Consumer().id+"鸡仔");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//产品
class Chicken{
    int id;
    public Chicken(int id) {
        this.id = id;
    }
}


//缓冲区
class SynContainer{
    //定义一个缓冲区的大小
    Chicken[] chickens=new Chicken[10];

    //定义鸡的个数
    int count=0;

    //生产鸡仔
    public synchronized void push(Chicken chicken) throws InterruptedException {

        //当生产的鸡仔达到容量时
        if (count== chickens.length){
            //此时应该暂停生产
            this.wait();
        }

        //如果没有满 我们需要丢入鸡仔
        chickens[count]=chicken;
        count++;
        //通知消费者可以开始消费
        this.notifyAll();

    }
    //消费鸡仔
    public synchronized Chicken Consumer() throws InterruptedException {
        //当没有鸡仔可以消费时
        if (count==0){  //此时消费者应该处于等待,等待生产者继续生产
            this.wait();
        }

        //否则鸡仔将被消费
        count--;
        Chicken chicken = chickens[count];
        //并告知生产者鸡仔已经被消费
        this.notifyAll();
        return chicken;
    }

}

 

线程池:

 

网友评论