Welcome to 发呆鱼.|

发呆鱼

园龄:3年4个月粉丝:1关注:0

java 多线程学习

java 多线程学习

跟随b站“遇见狂神说”学习,地址https://www.bilibili.com/video/BV1V4411p7EF

新手上路,才学疏浅,望斧正

1 基本概念

进程和线程

进程:最小的资源分配单位,一个程序就是一个进程。

线程:最小的运行单位,一个进程可将任务分配给多个线程执行。

并发和并行

并发:一个处理器处理多个任务。在宏观上看,多个任务是同时进行的,但是事实上多个任务是交替进行。

并行:多个处理器,每个处理器只处理一个任务。真正的同时进行。

生命周期,线程同进程一样,有这生命周期。

  • 创建:一个新的线程被创建。
  • 就绪:线程获得除cpu外所有资源,只要获得cpu即可执行。
  • 运行:线程在cpu上运行。
  • 阻塞:线程因等待资源或其他原因无法运行,等待条件满足后,恢复为就绪。阻塞并不是结束
  • 死亡:线程执行完毕或者其他原因,导致线程结束。死亡后,就不能再次启动

2 进程创建三种方法

2.1 继承Thread

将一个类声明为Thread的子类。 这个子类应该重写run类的方法Thread 。

示例

//继承Thread 类
public class threadTest extends  Thread{

   //重写run方法
    @Override
    public void run() {
        super.run();
        for (int i =0; i < 1000; i++){
            System.out.println("______"+i);
        }
    }
    
    public static void main(String[] args) {
        
        threadTest test=new threadTest();
        //开启子线程
        test.start();

        for (int i = 0;i < 100; i++){
            System.out.println("*****"+i);
        }

    }
}

开启子线程需要调用start()函数,而不是调用run() 函数。

应用:多线程下载图片(借助一个:org.apache.commons.io.FileUtils 包)

package com.demo1;
import org.apache.commons.io.FileUtils;

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

//多线程下载图片
public class PictDownload extends Thread {

    String url;
    String fileName;

    public PictDownload( String url, String fileName) {
        this.url = url;
        this.fileName = fileName;
    }

    @Override
    public void run() {
        super.run();
        try {
            FileUtils.copyURLToFile(new URL(url),new File(fileName));
            System.out.println(fileName+"has downloaded");
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("图片下载错误");
        }
    }

    public static void main(String[] args) {
        PictDownload pictDownload1=new PictDownload("http://pic.bizhi360.com/bbpic/51/10551.jpg","001.jpg");
        PictDownload pictDownload2=new PictDownload("http://pic.bizhi360.com/bbpic/50/10550.jpg","002.jpg");
        PictDownload pictDownload3=new PictDownload("http://pic.bizhi360.com/bbpic/24/10524.jpg","003.jpg");

        pictDownload1.start();
        pictDownload2.start();
        pictDownload3.start();

    }


}

2.2 实现类Runnable接口

另一种方法来创建一个线程是声明实现类Runnable接口。 那个类然后实现了run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,并启动。

示例

public class threadTest extends  Thread{

    @Override
    public void run() {
        super.run();
        for (int i =0; i < 1000; i++){
            System.out.println("______"+i);
        }
    }


    public static void main(String[] args) {
        threadTest test=new threadTest();
        new Thread(test).start();

        for (int i = 0;i < 100; i++){
            System.out.println("*****"+i);
        }

    }
}

2.3 实现callable 接口

  1. 实现callable接口,重写call方法。
  2. 创建执行服务ExecutorService executorService=Executors.newFixedThreadPool(2);
  3. 提交服务Future r1=executorService.submit(threadTest1);
  4. 获取结果r1.get();
  5. 关闭服务executorService.shutdown();
package com.demo1;

import java.util.concurrent.*;

public class ThreadTest3 implements Callable<Boolean> {

    @Override
    public Boolean call() throws Exception {
        for (int i =0; i < 1000; i++){
            System.out.println("______"+i);
        }

        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadTest3 threadTest1=new ThreadTest3();
        ThreadTest3 threadTest2=new ThreadTest3();

        ExecutorService executorService=Executors.newFixedThreadPool(2);

        Future<Boolean> r1=executorService.submit(threadTest1);
        Future<Boolean> r2=executorService.submit(threadTest2);

        r1.get();
        r2.get();

        executorService.shutdown();


    }
}

3 lamda 表达式

函数式接口:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口

package com.demo2;

interface Test{
    void testfun();
}

//外部类
class Test1 implements Test{
    @Override
    public void testfun() {
        System.out.println("Test1");
    }
}


public class LamdaTest {

    //静态内部类
    static class Test2 implements Test{
        @Override
        public void testfun() {
            System.out.println("Test2");
        }
    }


    public static void main(String[] args) {

        //1使用外部类
        Test1 test1=new Test1();
        test1.testfun();

        //2 使用静态内部类
        Test2 test2=new Test2();
        test2.testfun();


        //3使用局部内部类
        class Test3 implements Test{
            @Override
            public void testfun() {
                System.out.println("Test3");
            }
        }
        Test3 test3=new Test3();
        test3.testfun();

        //4 使用匿名内部类
        Test test4 = new Test() {
            @Override
            public void testfun() {
                System.out.println("Test4");
            }
        };
        test4.testfun();

        //5 使用lamda
        Test test5=()->System.out.println("Test5");
        test5.testfun();

    }
}

4 线程控制

4.1 线程停止

注意事项

  • 建议让进程正常停止,不建议使用死循环
  • 建议使用标志位来控制线程停止
  • 不使用官方不推荐的方法,如:stop(),destroy()等。

示例

package com.demo3;
public class TestStop implements Runnable {

    //设置标志位
    boolean flag=true;
    @Override
    public void run() {
        int i=0;
        while (flag){
            System.out.println("子线程"+i++);
        }
    }

    private void stop(){
        flag=false;
    }

    public static void main(String[] args) {
        TestStop testStop=new TestStop();
        new Thread(testStop).start();
        
        for (int i=0;i<1000;i++){
            if(i==800)
                testStop.stop();
            System.out.println("main:"+i);

        }
    }
}

4.2 线程睡眠

线程睡眠即可令线程进入阻塞态,可以用于网络延时和计时器。

示例

package com.demo3;

import java.text.SimpleDateFormat;
import java.util.Date;
public class TestSleep {

    public static void main(String[] args) throws InterruptedException {
      
        Date date=null;
        while (true){
            Thread.sleep(1000);
             //获取系统当前时间
            date=new Date(System.currentTimeMillis());
            System.out.println(new SimpleDateFormat("hh:mm:ss").format(date));
        }


    }
}

4.3 线程礼让

礼让不一定成功,全看脸

示例

package com.demo3;

class MyYield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"结束");
    }
}
public class TestYield {
    public static void main(String[] args) {
        MyYield myYield=new MyYield();

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

4.4 线程插入

让cpu强制执行该线程

示例

package com.demo3;

public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<1000;i++){
            System.out.println("子:"+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==500){
                thread.join();
            }
            System.out.println("主:"+i);
        }
    }
}

5 线程同步和互斥

线程不安全示例

package com.demo4;

public class BuyTicket  implements Runnable{

    private boolean tag=true;
    private int ticketNum=10;

    private void buy(){
        if(ticketNum<0){
            tag=false;
            return;
        }
        System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNum--);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void run() {
        while (tag){
            buy();
        }
    }

    public static void main(String[] args) {
        BuyTicket buyTicket=new BuyTicket();

        new Thread(buyTicket,"一号").start();
        new Thread(buyTicket,"二号").start();
    }
}

image-20220109211827161

拿到了相同的票,这显然是不合理的。

synchronized

同步块:synchronized(obj){}

obj称为同步监视器,obj可以是任何对象,但是推荐使用共享资源作为同步监视器。

同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身。

ReentrantLock lock 锁

显示的锁,手动开启和关闭。

 while (true){
     reentrantLock.lock();
     try {
         System.out.println(Thread.currentThread().getId()+"拿到了第"+ticketNum--);
     }finally {
         reentrantLock.unlock();
     }
     if(ticketNum>0){
         try {
             Thread.sleep(1000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
     }else {
         break;
     }
 }

6 线程通信

利用缓存区:管程法解决生产者消费者问题

package com.demo5;

import java.util.Collection;

class Productor extends Thread{

    TheBuffer theBuffer;

    Productor(TheBuffer theBuffer){
        this.theBuffer=theBuffer;
    }

    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 100; i++) {
            System.out.println("生产了"+i);
            theBuffer.push(new Good(i));
        }
    }
}

class Comsumer extends Thread{

    TheBuffer theBuffer;

    Comsumer(TheBuffer theBuffer){
        this.theBuffer=theBuffer;
    }

    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 100; i++) {
            Good good=theBuffer.pop();
            System.out.println("消费了第"+good.id);
        }
    }
}

class Good{
    int id;
    Good(int i){
        this.id=i;
    }
}
class TheBuffer{

    Good[] goodList=new Good[10];
    int count=0;

    public synchronized void push(Good good){
        if(count==9) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        goodList[count]=good;
        count++;
        this.notifyAll();

    }

    public synchronized Good pop(){
        Good good=null;
        if(count==0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;
        good=goodList[count];
        this.notifyAll();
        return good;
    }
}

public class TestPC {

    public static void main(String[] args) {
        TheBuffer theBuffer=new TheBuffer();
        new Productor(theBuffer).start();
        new Comsumer(theBuffer).start();
    }
}

信号灯方法

package com.demo5;

import sun.security.krb5.internal.TGSRep;

class  Player extends Thread {
    TV tv;
    Player(TV tv){
        this.tv=tv;
    }
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 10; i++) {
            tv.play(String.valueOf(i));
        }
    }
}

class  Audience extends Thread{
    TV tv;
    Audience(TV tv){
        this.tv=tv;
    }

    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 10; i++) {
            tv.watch();
        }
    }
}

class TV{
    boolean flag=true;


    public synchronized void play(String name){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("表演了"+name);
        this.notifyAll();
        this.flag=!this.flag;

    }

    public synchronized void watch(){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观看了");
        this.notifyAll();
        this.flag=!this.flag;
    }

}


public class TestPC2 {
    public static void main(String[] args) {
        TV tv=new TV();
        new Player(tv).start();
        new Audience(tv).start();

    }

}

本文作者:发呆鱼

本文链接:https://www.cnblogs.com/dyiblog/articles/15782805.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   发呆鱼  阅读(28)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起