多线程

多线程

继承Thread类

package thread;
//创建线程方式一:继承thread类,重写run方法,调用start开启线程
public class TestThread extends Thread{   //自定义线程类 继承Thread类
   @Override
   public void run() {    //重写run方法,编写线程的执行体
       for (int i = 0; i < 20; i++) {   //run方法的线程体
           System.out.println("我在看代码"+i);
      }
  }

   public static void main(String[] args) {
       //main线程,主线程
       TestThread testThread = new TestThread();   //创建一个线程对象
       testThread.start();     //调用testThread对象的start方法启动线程

       for (int i = 0; i < 2000; i++) {   //run方法的线程体   i写2000,因为CPU运行速度过快
           System.out.println("我在写代码"+i);
      }     //输出结果是 我在写代码 和 我在看代码   交替出现,   两条线程是同时执行的,交替执行的,像钟摆一样,交替执行,一会执行A,一会执行B
  }
}

多线程就是多条执行路径,主线程和子线程并行交替执行。线程开启不一定立即执行,由CPU统一调度。

网图下载

package thread;

import org.apache.commons.io.FileUtils;

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

public class TestThread01 extends Thread{

   private String url;     //由下载器类可知,下载器中的方法需要得到两个变量的值,所以在大类定义这两个变量
   private String name;

   public TestThread01(String url,String name) {
       this.url = url;       //通过构造方法取得用户输入的这两个变量的值,url和name。使它们在程序中使用
       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) {
       TestThread01 t1 = new TestThread01("https://p.ssl.qhimg.com/t0141a77a1ba3390295.jpg","t1");   //在主程序类给TestThread01类的构造方法赋值,
       TestThread01 t2 = new TestThread01("https://p3.ssl.qhimgs0.com/dr/500_500_60/t0186212bf07423b8d8.jpg","t2");
       TestThread01 t3 = new TestThread01("https://p3.ssl.qhimgs0.com/dr/500_500_60/t01d22bac0093344d83.jpg","t3");

       t1.start();   //线程启动
       t2.start();
       t3.start();
  }
}

//下载器
class WebDownLoader{
   public void downloader(String url,String name){    //写一个下载方法,写完copyURLToFile方法,发现这个方法需要两个变量,就在外面downloader方法引入两个变量赋值。
       try {
           FileUtils.copyURLToFile(new URL(url),new File(name));   //Commons.io包下的一个类FileUtils,下面的copyURLToFile方法,需要的值是URL类型的地址,和File类型的新保存的文件名。
      } catch (IOException e) {     //new URL()会报异常,alt+enter try-catch抛出异常,打印一下,方便后期查错。
           e.printStackTrace();
           System.out.println("IO异常,downloader方法出现问题");
      }
  }
}

//在项目目录下新建一个lib包(文件夹),把Commons-io-2.7.jar粘贴到这个目录下,Add as library   -- creat library --,在project sturcture ,library中看到。

继承Runnable接口(常用)

package thread;

public class TestThread02 implements Runnable{  //继承后就会具有多线程能力,方便同一个对象被多个线程使用

   private String url;     //由下载器类可知,下载器中的方法需要得到两个变量的值,所以在大类定义这两个变量
   private String name;

   public TestThread02(String url,String name) {
       this.url = url;       //通过构造方法取得用户输入的这两个变量的值,url和name。使它们在程序中使用
       this.name = name;
  }

   @Override
   public void run() {   //run方法线程体
       WebDownLoader webDownLoader = new WebDownLoader();   //实例化下载器类,创建对象,调用下载器类的方法,下载文件
       webDownLoader.downloader(url,name);
       System.out.println(name+"下载完成了!");

  }

   public static void main(String[] args) {
       TestThread01 t1 = new TestThread01("https://p.ssl.qhimg.com/t0141a77a1ba3390295.jpg","t1");   //在主程序类给TestThread01类的构造方法赋值,
       TestThread01 t2 = new TestThread01("https://p3.ssl.qhimgs0.com/dr/500_500_60/t0186212bf07423b8d8.jpg","t2");
       TestThread01 t3 = new TestThread01("https://p3.ssl.qhimgs0.com/dr/500_500_60/t01d22bac0093344d83.jpg","t3");

       new Thread(t1).start();   //线程启动(extends Thread的话,因为是直接继承,直接t1.start(),就可以开启线程。
       new Thread(t2).start();   //继承接口Runnable后,线程启动和继承Thread类一样,都是要new Thread类的对象,然后调用它的方法start.
       new Thread(t3).start();   //new Thread(),相当于一个代理,启动必须要通过它。前面写的TestThread01类因为继承的是Runnable接口,所以必须要把new TestThread01出来的对象,放到new Thread()里面,才能调用start方法。开启线程。
  }
}

并发问题

package thread;
//发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱,很多人抢票,可能同时拿到同一张票
public class TestThread03 implements Runnable{

   private int tickets = 10;
   @Override
   public void run() {
       while (true) {
           if(tickets<=0){break;}
           try {
               Thread.sleep(200);  //Thread里面有个模拟延时,让他睡觉200毫秒
          } catch (InterruptedException e) {   //捕获异常
               e.printStackTrace();
          }
           System.out.println(Thread.currentThread().getName() + "买了第" + tickets-- + "张票。");   //Thread.currentThread()获取当前线程,返回结果是Thread类型,它的名字是getname,获取当前线程的名字
      }
  }

   public static void main(String[] args) {
       TestThread03 testThread03 = new TestThread03();

       new Thread(testThread03,"小明").start();
       new Thread(testThread03,"小红").start();
       new Thread(testThread03,"小丽").start();
  }
}
//小明买了第10张票。
//小红买了第10张票。
//小丽买了第9张票。
//小明买了第8张票。
//小红买了第7张票

龟兔赛跑

package thread;

public class Race implements Runnable{
   private static String winner;   //保证只有一个winner

   @Override
   public void run() {
       for (int i = 0; i <= 100; i++) {
           //模拟兔子休息
//           if(Thread.currentThread().getName() == "兔子" && i%10==0){ //当线程名是兔子,并且 每10步一次
//               try {
//                   Thread.sleep(20);   //模拟兔子休息
//               } catch (InterruptedException e) {
//                   e.printStackTrace();
//               }
//           }

           if(gameOver(i) == false) {  //判断比赛是否结束   ,如果比赛未结束,就一直输出
//               try {
//                   Thread.sleep(20);   //放在sleep上,alt+enter抛出异常。
//               } catch (InterruptedException e) {
//                   e.printStackTrace();
//               }
               if(Thread.currentThread().getName() == "兔子" && i%10==0){  //当线程名是兔子,并且 每10步一次
                   try {
                       Thread.sleep(20);    //模拟兔子休息
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }
              }
               System.out.println(Thread.currentThread().getName() + "-->跑了" + i + "步");
          }
      }
  }
   //判断是否完成比赛
   private boolean gameOver(int steps){
       if(winner!=null){     //如果winner不为空,说明有winner了,就返回true,代表有胜利者了,比赛结束
          return true;
      }{      //后面这个大括号其实可以省略,可以想成上下分别是两句if语句。   也类似于一个代码块,如果不满足上面条件,走下面的条件。
           if(steps >= 100){    //如果满足步数大于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接口实现

好处:可以定义返回值,可以抛出异常。需要创建服务。

package thread;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

public class TestCallable implements Callable<Boolean> {  // Callable<>接口需要返回值,需要重写call方法,<>中的返回值类型和call的返回值类型一致,就可以了,都可以是Boolean。

   private String url;     //由下载器类可知,下载器中的方法需要得到两个变量的值,所以在大类定义这两个变量
   private String name;

   public TestCallable(String url,String name) {
       this.url = url;       //通过构造方法取得用户输入的这两个变量的值,url和name。使它们在程序中使用
       this.name = name;
  }

   @Override
   public Boolean call() {
       WebDownLoader1 webDownLoader1 = new WebDownLoader1();   //实例化下载器类,创建对象,调用下载器类的方法,下载文件
       webDownLoader1.downloader(url,name);   //给下载器的方法downloader赋值
       System.out.println(name+"下载完成了!");
       return true;
  }

   public static void main(String[] args) throws ExecutionException, InterruptedException {
       TestCallable t1 = new TestCallable("https://p.ssl.qhimg.com/t0141a77a1ba3390295.jpg","t1");   //在主程序类给TestThread01类的构造方法赋值,
       TestCallable t2 = new TestCallable("https://p3.ssl.qhimgs0.com/dr/500_500_60/t0186212bf07423b8d8.jpg","t2");
       TestCallable t3 = new TestCallable("https://p3.ssl.qhimgs0.com/dr/500_500_60/t01d22bac0093344d83.jpg","t3");

       ExecutorService ser = Executors.newFixedThreadPool(3);   //创建执行服务,Executors和ExecutorService这两个类需要创建一个池子,三个线程就写三。
       Future<Boolean> r1 = ser.submit(t1);
       Future<Boolean> r2 = ser.submit(t2);
       Future<Boolean> r3 = ser.submit(t3);    //提交执行,提交后就开始执行,像run方法一样,把线程提交上去,获得返回值

       boolean rs1 = r1.get();   //get异常,鼠标点在get上,alt+enter抛出异常,add exception to method signal,直接抛出异常
       boolean rs2 = r2.get();
       boolean rs3 = r3.get();   //获取结果   获取call方法运行的结果

       ser.shutdown();   // 关闭服务
  }
}

//下载器
class WebDownLoader1{
   public void downloader(String url,String name){    //写一个下载方法,写完copyURLToFile方法,发现这个方法需要两个变量,就在外面downloader方法引入两个变量赋值。
       try {
           FileUtils.copyURLToFile(new URL(url),new File(name));   //Commons.io包下的一个类FileUtils,下面的copyURLToFile方法,需要的值是URL类型的地址,和File类型的新保存的文件名。
      } catch (IOException e) {     //new URL()会报异常,alt+enter try-catch抛出异常,打印一下,方便后期查错。
           e.printStackTrace();
           System.out.println("IO异常,downloader方法出现问题");
      }
  }
}

静态代理模式

package thread;

public class StaticProxy {
   public static void main(String[] args) {
       Company company = new Company(new You());  //需要赋值new You()这个变量,把值传递给target
       company.HappyMarry();
  }
}

interface Marry{   //结婚这个接口(功能)
   void HappyMarry();  //在接口中定义一个方法HappyMarry()
}
//真实角色,你去结婚
class You implements Marry{    // Marry接口的实现类,要结婚的人
   @Override
   public void HappyMarry() {
       System.out.println("张三要结婚了");
  }
}
//代理角色,帮助你结婚
class Company implements Marry{  //Marry接口的另一个实现类,帮咱们结婚的公司,有结婚的功能
   private Marry target;   //首先要创建一个帮助的对象

   public Company(Marry target) {   //然后针对这个对象写一个构造方法 alt+enter ---constructor
       this.target = target;   //传递参数,获取谁结婚
  }

   @Override
   public void HappyMarry() {
       before();   //alt+enter 创建方法 create method before in company
       this.target.HappyMarry();  //在main方法中创建的是new You()对象,赋值给Marry target的也是这个对象,正常调用的是接口中的方法,但真实角色中有重写的方法,所以调用真实角色中的方法
       after();
  }

   private void after() {
       System.out.println("结婚后");
  }

   private void before() {
       System.out.println("结婚前");
  }
}

真实对象和代理对象都要实现同一个接口,代理对象要代理真实角色。

代理对象可以做很多真实对象做不了的事情(代理类中写额外的方法)。真实对象可以专注做自己的事情。

Lambda表达式

  1. 任何接口如果只包含唯一一个抽象方法,那么他就叫一个函数式接口,我们可以用lambda表达式来创建该接口的对象。

package thread;

public class TestLambda {
   //3、静态内部类
   static class Like2 implements ILike{
       @Override
       public void like() {
           System.out.println("i love lambda2");
      }
  }

   public static void main(String[] args) {
       ILike like = new Like();  //创建一个接口对象,调用了一个实现类     这句话是最重要的,必须要定义like对象的类型
       like.like();    //i love lambda

       like = new Like2();   //调用静态内部类
       like.like();    //i love lambda2

       //4、局部内部类
       class Like3 implements ILike{   //这些普遍都要写一个类继承这个接口,再new一个类的对象
           @Override
           public void like() {
               System.out.println("i love lambda3");
          }
      }
       like = new Like3();   //调用局部内部类
       like.like();      //i love lambda3

       //5、匿名内部类,没有类的名称,必须借助接口或父类
       like = new ILike() {     //直接创建一个没有类名,只有接口名的对象,直接带大括号中重写方法
           @Override
           public void like() {
               System.out.println("i love lambda4");
          }
      };
       like.like();   //i love lambda4

       //6、用lambda简化
       like = ()->{    //跟匿名内部类相比,把接口和到方法名中间的内容都可以省略(只需要知道继承是哪个接口即可,只包含一个方法并且只需要重写一个方法)
           System.out.println("i love lambda5");     //ILike like = ()->{}这句话可以知道继承的是哪个接口
      };
       like.like();   //i love lambda5
  }
}
//1、定义一个函数式接口
interface ILike{
   void like();  //自动修饰为abstract
   //void love(int a);   含参数的,可以简化为 ILove love = a->System.out.println("i love lambda6");(只有一行代码,可以扔掉大括号,否则写成代码块,带大括号)   love.love(521);   或者提前定义ILove love = null,一定要定义这个对象的接口类型。     多个变量:ILove love = (a,b)->System.out.println("i love lambda6");
}
//2、实现类
class Like implements ILike{
   @Override
   public void like() {
       System.out.println("i love lambda");
  }
}

线程停止

new创建状态,调用start进入就绪状态,CPU开始调度进入运行状态,sleep,wait,同步锁进入阻塞状态,线程结束进入死亡状态。

  1. 推荐线程自己停止下来

  2. 或者建议使用一个标志位进行终止变量,当flag=false,终止线程运行。

package thread;

public class TestStop implements Runnable{
    private Boolean flag = true;  //设置一个标识位

    @Override
    public void run() {      //继承接口,必然要重写它的方法
        int i = 0;
        while(flag){
            System.out.println("线程正在运行" + i++);
        }
    }

    public static void main(String[] args) {
        TestStop testStop = new TestStop();

        new Thread(testStop).start();   //线程就绪

        for (int i = 0; i < 10000; i++) {
            System.out.println("main"+ i);
            if(i == 5000){
                testStop.stop();  //调用stop方法将标识位切换为false,用来停止线程
                System.out.println("线程该停止了");
            }
        }
    }

    //设置一个公开的方法停止线程
    public void stop(){
        this.flag = false;
    }

}

线程休眠

sleep指当前线程阻塞的毫秒数,存在异常,需要try catch抛出,也可以直接抛出到方法外面。时间达到后进入就绪状态,每一个对象都有一个锁,sleep不会释放锁。

模拟网络延时:放大事情的发生性,多个线程操作一个对象,不安全

模拟倒计时:

package thread;

public class TestSleep {

    public static void main(String[] args) {
        try {
            tenDown();     //try catch抛出我们写的方法的异常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //模拟倒计时的方法
    public static void tenDown() throws InterruptedException {  //sleep异常在方法上抛出
        int i = 10;
        while (true) {   //不断循环
            Thread.sleep(1000);   //延时一秒
            System.out.println(i--);   
            if (i <= 0) {     //当等于0秒时,跳出循环
                break;
            }
        }
    }

}

获取当前系统时间:

package thread;

import java.text.SimpleDateFormat;
import java.util.Date;

public class TestSleep01 {
    public static void main(String[] args) {
        Date startTime = new Date(System.currentTimeMillis());   //获取系统当前时间
        while(true) {
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("hh:mm:ss").format(startTime));   //new SimpleDateFormat时间格式化工厂,打印系统时间
                startTime = new Date(System.currentTimeMillis());   //重新给对象赋值,相当于刷新
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程礼让

当前正在进行的线程从CPU中出来,和其他线程重新一起竞争,看谁先进入CPU,礼让不一定一直成功,可能刚在再进行的线程,再一次被CPU调度,进入CPU中。

package thread;

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{    //1、都是先写一个Runnable接口的实现类

    @Override
    public void run() {     //2、在重写的run方法中,写要运行的代码,重写的方法在main程序中创建对象后,会自动运行。
        System.out.println(Thread.currentThread().getName()+"正在运行");   //获取当前线程名字
        Thread.yield();   //线程礼让
        System.out.println(Thread.currentThread().getName()+"运行结束");
    }
}

线程强制执行

Join合并线程,待此线程执行完,才能执行其他线程,会造成线程阻塞。可以想象成插队。

package thread;

public class TestJoin implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("vip线程来了"+i);   //重写run方法,创建TestJoin类的对象的时候回自动运行这段方法
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestJoin testJoin = new TestJoin();    //创建一个TestJoin的对象
        Thread thread = new Thread(testJoin);   //创建一个静态代理,方便开启线程,把testJoin放进去
        thread.start();   //线程就绪,开始准备接受CPU调度

        for (int i = 0; i < 500; i++) {
            System.out.println("main"+i);
            if(i==200){
                thread.join();    //线程插队,强制执行,上面run方法中的100个数执行完,才执行main方法中的循环。
            }
        }
    }
}

观测线程状态

线程一旦进入死亡状态,就不能再次启动了。调用start()方法后线程立刻进入就绪状态,可能不会立刻被CPU调用,看CPU心情,随时可以运行。调用sleep,wait,同步锁,进入阻塞状态,线程不往下执行了,阻塞状态解除后,重新进入就绪状态,随时接受CPU的调度。new Thread()线程一旦被创建,进入新生状态。

package thread;

public class TestState {

//    @Override
//    public void run() {
//        for (int i = 0; i < 5; i++) {
//            try {
//                Thread.sleep(1000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        }
//        System.out.println("线程完成");
//    }

    public static void main(String[] args) throws InterruptedException {
//        TestState testState = new TestState();
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("////////");
        });

        Thread.State state = thread.getState();    //获取线程状态,alt+enter创建本地变量
        System.out.println(state);     //观察状态  new

        thread.start();
        state = thread.getState();
        System.out.println(state);     //启动线程

        while (state != Thread.State.TERMINATED){   //如果状态不等于状态终止
            Thread.sleep(100);
            state = thread.getState();    //重新赋值,相当于更新线程状态
            System.out.println(state);
        }


    }
}

线程的优先级

package thread;

public class TestPriority {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());//获取当前线程的优先级
        MyPriority myPriority = new MyPriority();    //只需要new一个对象作为线程体

        Thread t1 = new Thread(myPriority);   //把线程体放到代理中,几个代理就是几条线程
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);   //设置优先级不可以简写

        t1.start();

        t2.setPriority(4);  //先设置线程优先级,再启动
        t2.start();

        t3.setPriority(Thread.MAX_PRIORITY);  //优先级等级在1—10之间,0和11和-1这些都不行,会报错
        t3.start();

    }
}

class MyPriority implements Runnable{   //建一个线程的类,继承Runnable接口,(然后new一个这个类的对象,创建线程)创建线程方式很多。这只是其中一个

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
    }
}

守护线程

线程分为用户线程和守护线程,虚拟机必须确保用户线程执行完毕,不用等待守护线程(如垃圾回收gc线程,等待机制)执行完毕。

package thread;

public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();    //设置守护线程,就需要分开一步一步写,不能像用户线程一样一句简写

        Thread thread = new Thread(god);   //把线程体的对象god放在Thread类的thread代理中
        thread.setDaemon(true);    //将setDaemon模式调为true,说明是守护线程。正常默认是false,都为用户线程。
        new Thread(new You01()).start();    //用户线程启动。用户线程完成,程序停止,不必等待守护线程。
        thread.start();    //上帝守护线程启动   程序结束后,守护线程还会运行一段时间,因为虚拟机停止需要一点时间。
    }
}

class God implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("佛祖保佑你");
        }
    }
}

class You01 implements Runnable{     //创建线程就是要新建一个类继承Runnable
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("开心的活着");
        }
        System.out.println("离开了世界");
    }
}

线程同步机制

并发:同一个对象被多个线程同时操作。 多个线程访问同一个对象,并且某些线程还想修改这个对象,这时我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用。(类似食堂打饭,只有一个窗口给打饭,所以大家需要排队一个一个去打饭)。

线程同步不光需要队列,还需要锁(对象拥有这锁)。(锁类似于只有一个厕所,一堆人排队上厕所,使用厕所(对象)的人在使用过程中需要把厕所门(对象的锁)锁上,保障线程安全性。)为保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得 对象的排它锁 ,独占资源,其他线程必须等待,使用后释放锁即可。

缺点会降低性能,优先级低的有可能会先拿到锁,造成性能倒置。

三大不安全案例

代码一:

package thread;
//不安全的买票
//线程不安全,有负数   三个人同时把一张票拿到自己的内存空间中
public class UnsafeBuyTickets {
    public static void main(String[] args) {    //1、main主程序是运行线程用
        BuyTickets buyTickets = new BuyTickets();   //创建一个买票的对象,线程体

        new Thread(buyTickets,"a").start();   //票被三个人买,三个人同时操作票这个对象
        new Thread(buyTickets,"b").start();   //代理线程,启动
        new Thread(buyTickets,"c").start();
    }
}

class BuyTickets implements Runnable{    //2、因为是买票的程序,需要写一个买票的线程体
    private int tickets = 10;
    boolean flag = true;    //定义一个程序循环停止的标志位,外部停止方式
    @Override
    public void run() {     //3、因为重写的方法是自动运行的,所以一般这里面写循环语句,买票的方法单独写,不写在这个run方法里。
        while (flag){
            try {
                buy();   // 在run中调用真正的方法buy()。
            } catch (InterruptedException e) {   //在重写方法中抛出sleep异常,一般直接用try catch。
                e.printStackTrace();
            }
        }
    }

    private void buy() throws InterruptedException {     //4、这里是买票真正需要的方法,三中创建这个线程体,只是调用这个方法
        if(tickets<=0){   //判断是否有票
            flag = false;
            return;
        }
        Thread.sleep(100);    //自己写的buy()方法,抛出sleep异常,一般直接抛出到buy()方法后面
        System.out.println(Thread.currentThread().getName()+"拿到了"+tickets--);
    }
}

每个线程都在自己的工作内存交互,内存控制不当会造成数据不一致。

代码二:银行取钱

package thread;
//不安全的取钱
//两个人去取钱需要银行,账户
public class UnsafeBank {
    public static void main(String[] args) {    //1、线程运行的主程序
        Account account = new Account(100,"结婚基金");   //新建一个账户
        Drawing you = new Drawing(account,50,"you");  //新建一个线程,说明哪个账户,取多少钱,线程的名字
        Drawing girl = new Drawing(account,100,"girl");   //代表是girl取的钱,线程的名字

        you.start();
        girl.start();
    }
}

class Account{    //账户   2、取钱需要先设置账户
    int money;
    String name;   //  卡的名字

    public Account(int money, String name) {   //3、alt+enter  constructor 创建一个构造方法
        this.money = money;      //引入变量获得用户输入的值,在其他方法中调用
        this.name = name;
    }
}

class Drawing extends Thread{    //  3、模拟取款
    Account account;   //创建取钱需要的账户
    int draw;     //取了多少钱
    String name01;    //线程的名字(代表谁取了钱)

    public Drawing(Account account, int draw, String name01) {    //4、写这三个变量的构造方法
        super(name01);    //获取线程名字
        this.account = account;
        this.draw = draw;
    }

    //取钱的操作  在run方法中写
    @Override
    public void run() {
        if(draw>account.money){
            System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
            return;
        }

        try {
            Thread.sleep(1000);    //sleep可以放大问题的发生性
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        account.money = account.money - draw;   //卡内余额等于账户余额-取的钱
        System.out.println(account.name+"余额是"+account.money);
        System.out.println(this.getName()+"手里的钱");   //意思是:线程的名字+手里的钱,谁取了钱,this.getName()等价于Thread.currentThread().getName(),因为继承Thread类,也包含getName()方法,都是指代当前这个类的对象。简写的一种。
    }
}

代码三:添加数组

package thread;

import java.util.ArrayList;

public class UnsafeList {
    public static void main(String[] args) {
        ArrayList<String> objects = new ArrayList<String>();  //new一个泛型的数组类型的对象
        for (int i = 0; i < 10000; i++) {
            new Thread(()-> {             //每一次循环,就创建一个没名字Runnable接口的对象,并用Thread代理一个线程,并开启
                objects.add(Thread.currentThread().getName());
            }).start();    //两个线程同时添加到一个位置,值可能为9999
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(objects.size());
    }
}

同步方法和同步块

同步方法:

package thread;
//不安全的买票
//线程不安全,有负数
public class UnsafeBuyTickets {
    public static void main(String[] args) {    //1、main主程序是运行线程用
        BuyTickets buyTickets = new BuyTickets();   //创建一个买票的对象,线程体

        new Thread(buyTickets,"a").start();   //票被三个人买,三个人同时操作票这个对象
        new Thread(buyTickets,"b").start();   //代理线程,启动
        new Thread(buyTickets,"c").start();
    }
}

class BuyTickets implements Runnable{    //2、因为是买票的程序,需要写一个买票的线程体
    private int tickets = 10;
    boolean flag = true;    //定义一个程序循环停止的标志位,外部停止方式
    @Override
    public void run() {     //3、因为重写的方法是自动运行的,所以一般这里面写循环语句,买票的方法单独写,不写在这个run方法里。
        while (flag){
            try {
                buy();   // 在run中调用真正的方法buy()。
            } catch (InterruptedException e) {   //在重写方法中抛出sleep异常,一般直接用try catch。
                e.printStackTrace();
            }
        }
    }
    //  synchronized 这个修饰词就是同步方法,锁的是调用这个buy()方法的类所创建的对象。这里锁的是this,BuyTickets。不写这个同步方法,三个人买票所看到的就都是10张票,开始抢。写上之后,大家就会排队,第一个人买完,释放锁,第二个人只会看到9张票,以此类推。
    private synchronized void buy() throws InterruptedException {     //4、这里是买票真正需要的方法,三中创建这个线程体,只是调用这个方法
        if(tickets<=0){   //判断是否有票
            flag = false;
            return;
        }
        Thread.sleep(100);    //自己写的buy()方法,抛出sleep异常,一般直接抛出到buy()方法后面
        System.out.println(Thread.currentThread().getName()+"拿到了"+tickets--);
    }
}

同步块:同步块可以锁任何东西(需要判断要锁的对象(变化的量,需要增删改的量(对象)),这里要锁的就是账户account,防止取钱取多,出现负数。),同步方法默认只能锁这个类this(调用方法的类所创建的对象)默认锁银行,没有用。

package thread;
//不安全的取钱
//两个人去取钱需要银行,账户
public class UnsafeBank {
    public static void main(String[] args) {    //1、线程运行的主程序
        Account account = new Account(100,"结婚基金");   //新建一个账户
        Drawing you = new Drawing(account,50,"you");  //新建一个线程,说明哪个账户,取多少钱,线程的名字
        Drawing girl = new Drawing(account,100,"girl");   //代表是girl取的钱,线程的名字

        you.start();
        girl.start();
    }
}

class Account{    //账户   2、取钱需要先设置账户
    int money;
    String name;   //  卡的名字

    public Account(int money, String name) {   //3、alt+enter  constructor 创建一个构造方法
        this.money = money;      //引入变量获得用户输入的值,在其他方法中调用
        this.name = name;
    }
}

class Drawing extends Thread{    //  3、模拟取款
    Account account;   //创建取钱需要的账户
    int draw;     //取了多少钱
    String name01;    //线程的名字(代表谁取了钱)

    public Drawing(Account account, int draw, String name01) {    //4、写这三个变量的构造方法
        super(name01);    //获取线程名字
        this.account = account;
        this.draw = draw;
    }

    //取钱的操作  在run方法中写
    @Override
    public void run() {
        synchronized (account){     //意思是线程拿到了account这个对象的锁
            if(draw>account.money){
                System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
                return;
            }

            try {
                Thread.sleep(1000);    //sleep可以放大问题的发生性
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money = account.money - draw;   //卡内余额等于账户余额-取的钱
            System.out.println(account.name+"余额是"+account.money);
            System.out.println(this.getName()+"手里的钱");   //意思是:线程的名字+手里的钱,谁取了钱,this.getName()等价于Thread.currentThread().getName(),因为继承Thread类,也包含getName()方法,都是指代当前这个类的对象。简写的一种。
        }
    }
}

代码三:

package thread;

import java.util.ArrayList;

public class UnsafeList {
    public static void main(String[] args) {
        ArrayList<String> objects = new ArrayList<String>();  //new一个泛型的数组类型的对象  ,集合加上泛型<>,中间内容起到一个约束的作用
        for (int i = 0; i < 10000; i++) {
            new Thread(()-> {             //每一次循环,就创建一个没名字Runnable接口的对象,并用Thread代理一个线程,并开启
                synchronized (objects){    //锁住这个变化的量,让线程一个一个添加字符
                    objects.add(Thread.currentThread().getName());
                }
            }).start();    //两个线程同时添加到一个位置,值可能为9999
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(objects.size());
    }
}

CopyOnWriteArrayList

package thread;

import java.util.concurrent.CopyOnWriteArrayList;

public class TestJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();   //<>是泛型的意思,意思是数组里面只能是字符串,起到约束的作用。   对返回值的约束
        for (int i = 0; i < 10000; i++) {     //new CopyOnWriteArrayList<String>();是官方定义好的保障线程安全的并发领域的集合。直接能保证线程排队,不用写线程块。
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

死锁

多个线程互相抱着对方需要的资源,然后形成僵持(都想让对方先释放资源,但都不释放)

代码一:a获得了镜子的锁 b获得了口红的锁 :出现了死锁异常

package thread;

public class DeadLock {    //1、线程运行主程序
    public static void main(String[] args) {
        MakeUp makeUp01 = new MakeUp(1,"a");
        MakeUp makeUp02 = new MakeUp(0,"b");

        makeUp01.start();
        makeUp02.start();
    }
}

class Mirror{     //2、化妆需要镜子
}

class Mouth{      //3、化妆需要口红

}

class MakeUp extends Thread{  //4、化妆的线程体,继承Thread类,在重写的run方法中调用新写的化妆方法,run方法会在创建对象后自动运行。

    static Mirror mirror = new Mirror();   //化妆方法make()中,需要用到这些对象,提前创建,定义
    static Mouth mouth = new Mouth();      //需要的资源只有一份,用static来保证只有一份

    int choice;    //方法中也需要这两个变量,提前定义,选择(化妆人本身有的东西)和化妆人的名,提前想好化妆方法中要用到哪些变量,提前定义
    String name;

    public MakeUp(int choice, String name) {    //创建这几个变量的构造方法,从用户输入中获取这些值,方便在方法中调用
        this.choice = choice;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            make();   //调用这个化妆方法,在重写的run方法中用try catch抛出异常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void make() throws InterruptedException {   //5、定义化妆的方法,需要在run方法中调用
        if(choice == 1)    //如果对象是a,选择的标识位,用来区分哪个人
        synchronized (mirror){   //意思是a现在拥有mirror这个对象锁
            System.out.println(this.name+"获得了镜子的锁");
            Thread.sleep(1000);   //普通自己写的方法可以直接抛出异常到方法名void make()的后面
            synchronized (mouth){  //a还想拥有(获得)口红这个对象的锁  1秒钟后
                System.out.println(this.name+"获得口红的锁");
            }
        }else {
            synchronized (mouth) {   //意思是b现在拥有mouth这个对象锁
                System.out.println(this.name + "获得了口红的锁");
                Thread.sleep(2000);
                synchronized (mirror) {
                    System.out.println(this.name + "获得镜子的锁");
                }
            }
        }
    }
}

解决办法:

b获得了口红的锁 a获得了镜子的锁 b获得镜子的锁 a获得口红的锁

package thread;

public class DeadLock {    //1、线程运行主程序
    public static void main(String[] args) {
        MakeUp makeUp01 = new MakeUp(1,"a");
        MakeUp makeUp02 = new MakeUp(0,"b");

        makeUp01.start();
        makeUp02.start();
    }
}

class Mirror{     //2、化妆需要镜子
}

class Mouth{      //3、化妆需要口红

}

class MakeUp extends Thread{  //4、化妆的线程体,继承Thread类,在重写的run方法中调用新写的化妆方法,run方法会在创建对象后自动运行。

    static Mirror mirror = new Mirror();   //化妆方法make()中,需要用到这些对象,提前创建,定义
    static Mouth mouth = new Mouth();

    int choice;    //方法中也需要这两个变量,提前定义,选择(化妆人本身有的东西)和化妆人的名,提前想好化妆方法中要用到哪些变量,提前定义
    String name;

    public MakeUp(int choice, String name) {    //创建这几个变量的构造方法,从用户输入中获取这些值,方便在方法中调用
        this.choice = choice;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            make();   //调用这个化妆方法,在重写的run方法中用try catch抛出异常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void make() throws InterruptedException {   //5、定义化妆的方法,需要在run方法中调用
        if(choice == 1)   { //如果对象是a,选择的标识位,用来区分哪个人
        synchronized (mirror){   //意思是a现在拥有mirror这个对象锁
            System.out.println(this.name+"获得了镜子的锁");
            Thread.sleep(1000);   //普通自己写的方法可以直接抛出异常到方法名void make()的后面
        }
        synchronized (mouth){  //a还想拥有口红这个对象的锁
            System.out.println(this.name+"获得口红的锁");
        }}else {
            synchronized (mouth) {   //意思是b现在拥有mouth这个对象锁
                System.out.println(this.name + "获得了口红的锁");
                Thread.sleep(2000);
            }
            synchronized (mirror) {
                System.out.println(this.name + "获得镜子的锁");
            }
        }
    }
}

比如b中把 synchronized (mirror) {System.out.println(this.name + "获得镜子的锁");}拿出来,不放在synchronized (mouth)中就好了。

死锁的发生的必要条件:一个资源每次只能被一个进程使用(上面那个程序两个线程a和b同时都想拿镜子这个资源,并且自身现在战友的资源也不释放,还想要其他资源,拿不到的时候,并且都不释放它们自身拥有的资源,造成僵持,死锁);一个进程因请求资源而阻塞时,对已获得的资源保持不放;进程已获得的资源,在未使用完之前,不能强行剥夺;若干线程之间形成一种头尾相接的循环等待资源关系(类似我想要你的口红,我想要你的镜子)。(一条线程获得两个资源是不可以的)。

LOCK锁

package thread;

import java.util.concurrent.locks.ReentrantLock;

public class TestGaoji {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(ticket).start();
        new Thread(ticket).start();
        new Thread(ticket).start();
    }
}

class Ticket implements Runnable{
    int ticket = 10;
    //定义lock锁:手动开启关闭,性能更好ReentrantLock(可重入锁)
    private final ReentrantLock reentrantLock = new ReentrantLock();

    @Override
    public void run() {
        while (true){

            try {
                reentrantLock.lock();   //加锁,一般在try中加锁,把原先要循环运行的程序放进去,是一个线程块锁。
                if(ticket>0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticket--);
                }else {
                    break;
                }
            } finally {
                reentrantLock.unlock();  //如果同步代码有异常(例如sleep需要try catch),要将unlock写入finally语句块
            }
        }
    }
}

生产者消费者问题:线程协作

并发协作模型:生产者消费者模式:管程法 线程通信

生产者:负责生产数据的模块(可能是方法,对象,线程,进程);

消费者:负责处理数据的模块(可能是方法,对象,线程,进程);

缓冲区:消费者不能直接使用生产者的数据,他们之间有个缓冲区。(生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据)

管程法:

package gaoji;

public class TestPC {
    public static void main(String[] args) {
        SynContainer synContainer = new SynContainer();
        new Productor(synContainer).start();   //生产者和消费者的线程开启
        new Costumor(synContainer).start();
    }
}

//生产者
class Productor extends Thread{
    SynContainer container;   //生产者和消费者都需要一个容器

    public Productor(SynContainer container) {   //创建完这个对象,变量,一定要写它的构造方法
        this.container = container;
    }
    // 生产鸡
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("生产了"+i+"只鸡");
            container.push(new Chicken(i));
        }
    }
}

//消费者
class Costumor extends Thread{
    SynContainer container;   //生产者和消费者都需要一个容器

    public Costumor(SynContainer container) {   //创建完这个对象,变量,一定要写它的构造方法
        this.container = container;
    }

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

//产品
class Chicken{
    int id;

    public Chicken(int id) {   //在类中定义一个属性(变量),都要给这个变量写一个构造器
        this.id = id;
    }
}

//缓冲区
class SynContainer {

    int nums = 0;   //容器计数器
    Chicken[] chickens = new Chicken[10];   //需要一个容器的大小
    //生产者放入产品
    public synchronized void push(Chicken chicken){   //存在并发,需要加锁,同步
        //如果容器满了,需要等待消费者消费,生产者需等待
        if(nums == chickens.length){
            //通知消费者消费,等待生产
            try {
                this.wait();   //生产者需等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没有满,我们需要丢入产品
        chickens[nums]=chicken;
        nums++;

        //可以通知消费者消费了
        this.notifyAll();
    }
    //消费者消费产品
    public synchronized Chicken pop(){
        if(nums == 0){
            //如果容器空了,消费者需等待,通知生产者生产
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消费
        nums--;
        Chicken chicken = chickens[nums];  //把这只鸡取出来,保存着,起来方便后面调用是哪只鸡

        //通知生产者生产
        this.notifyAll();
        return chicken;   //返回就是为了知道在class Costumor中取得是第几只鸡
    }
}

信号灯法:

package gaoji;

public class TestPC2 {
   public static void main(String[] args) {  //1、线程运行的主程序
       TV tv = new TV();
       new Player(tv).start();
       new Watcher(tv).start();
  }
}
class  Player extends Thread{   //演员在表演,观众等待 flag=true
   TV tv = new TV();   //需要创建TV的对象,后面才能调用这个对象的方法

   public Player(TV tv) {  //创建完对象后,一定要写它的构造方法,这样才能获取主程序中用户输入的值,才能在这些类里使用
       this.tv = tv;
  }

   @Override
   public void run() {
       for (int i = 0; i < 20; i++) {
           if(i%2==0){tv.play("快乐大本营");}
           else{
               tv.play("抖音");
          }
      }
  }
}

class Watcher extends Thread{   //观众在观看,演员等待 flag=false
   TV tv = new TV();

   public Watcher(TV tv) {
       this.tv = tv;
  }

   @Override        //自动运行的方法只需要调用tv的watch方法
   public void run() {
       for (int i = 0; i < 20; i++) {
           tv.watch();
      }
  }
}

class TV{
   Boolean flag = true;   //判断唤醒进程的标志位,判断哪个进程需要等待。
   String jiemu;
   public synchronized void play(String jiemu){
       if (!flag){
           try {
               this.wait();
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
       System.out.println("演员在表演"+jiemu);
       this.notifyAll();    //通知观众去看
       this.jiemu = jiemu;  //节目更新
       this.flag = !this.flag;  //标识位取反,演员线程开始等待
  }
   public synchronized void watch(){
       if(flag){
           try {
               this.wait();
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
       System.out.println("观众在观看"+jiemu);    //演员在干什么,观众都能看到,形成了线程之间的通信(实质就是两个线程类调用了同一个中间类的不同方法,这些方法可以共用这个中间类的一些属性,实现这两个线程之间的通信)
       this.notifyAll();    //通知演员表演
       this.flag = !this.flag;  //标识位取反,观众线程开始等待
  }
}

线程池

提高性能。像共享单车一样,线程都是先创造好,大家可以重复利用,用完还给线程池就可以了

package gaoji;

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

public class TestPool {
   public static void main(String[] args) {
       //创建服务,创建线程池,newFixedThreadPool意思是线程池大小为10,可以存放10个线程
       //ExecutorService是真正的线程池接口。Executors是工具类,线程池的工厂类,用于创建并返回不同类型的线程池。
       ExecutorService service = Executors.newFixedThreadPool(10);
       //执行线程
       service.execute(new MyThread());     //execute方法,执行任务,没有返回值,一般执行Runnable。
       service.execute(new MyThread());     //有返回值的,callable的一般用<T>future<T> submit(Callable<T> task)方法执行任务。 FutureTask是callable接口的实现类
       service.execute(new MyThread());
       service.execute(new MyThread());
       //关闭连接池
       service.shutdown();
  }
}
class MyThread implements Runnable{
   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
  }
}
posted @ 2021-03-07 19:05  爱罗翔的张三  阅读(69)  评论(0编辑  收藏  举报