10,7学习记录(多线程)

多线程

  1. 创建多线程的方式一:继承Thread类

    1. 自定义线程类继承Thread类

    2. 重写run()方法,编写线程执行体

    3. 创建线程对象,调用start()开启线程

  2. 创建多线程的方式二:实现Runable接口

    1. 创建一个实现了Runnable接口的类

    2. 实现类去实现Runnable中的抽象方法:run()

    3. 创建实现类的对象

    4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象

    5. 通过Thread类的对象调用start()

  3. 创建多线程的方式三:实现Callable接口

    1. 创建一个实现Callable的实现类

    2. 实现call方法,将此线程需要执行的操作声明在call()方法中

    3. 创建Callable接口实现类的对象

    4. 将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象

    5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()

    6. 获取Callable中call方s法的返回值

  4. 创建多线程的方式四:实现Callable接口

    1. 提供指定线程数量的线程池

    2. 执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象

    3. 关闭连接池

 

 

 

  • 注意:线程开启不一定立即执行,由CPU调度执行

  • 注意:若要再启动一个线程,不可以还让已经start()的线程去执行。(会报ILLeaglThreadStateException异常) 我们需要重新创建一个线程的对象调用start()

 

  • 创建多线程的方式一:继承Thread类

public class ThreadTeat 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线程

       //创建一个线程对象
       ThreadTeat threadTeat = new ThreadTeat();
       //调用start()方法开启线程
       threadTeat.start();
       for (int i = 0; i < 200; i++) {
           System.out.println("我在学习多线程*****"+i);
      }
  }
}
  • 创建多线程的方式二:实现Runable接口

public class ThreadTest {
   public static void main(String[] args) {
       //创建实现类的对象
       MyThread myThread = new MyThread();
       //将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
       Thread t1 = new Thread(myThread);
       //通过Thread类的对象调用start() 1、启动线程 2、调用当前线程的run()--> 调用了Runnable类型的target的run()
       t1.start();
       //再启动一个线程,遍历100以内的偶数
       Thread t2=new Thread(myThread);
       t2.start();
  }

}
//创建一个实现了Runnable接口的类
class MyThread implements Runnable{
   //实现类去实现Runnable中的抽象方法:run()
   @Override
   public void run() {
       for (int i = 0; i <=100; i++) {
           if(i%2==0){
              System.out.println(Thread.currentThread().getName()+":"+i);
          }
      }
  }
}
  • 实例1:创建三个窗口卖票,总票数为100张,使用继承类方法实现

//创建三个窗口卖票,总票数为100张,使用继承类方法实现
//目前存在线程的安全问题 (待解决)
public class WindowsTest {

   public static void main(String[] args) {
       Ticket ticket = new Ticket();
       Ticket ticket1 = new Ticket();
       Ticket ticket2 = new Ticket();
       ticket.setName("窗口1");
       ticket1.setName("窗口2");
       ticket2.setName("窗口3");
       ticket.start();
       ticket1.start();
       ticket2.start();
  }
}

class Ticket extends Thread{
   private static int ticket=100;
   @Override
   public void run() {
      while (true){
          if(ticket>0)
          {
              System.out.println(getName()+": 卖票,票号为:"+ticket);
              ticket--;
          }
          else{
              break;
          }
      }
  }
}

 

  • 实例2:创建三个窗口卖票,总票数为100张,使用实现Runnable接口的方式

//创建三个窗口卖票,总票数为100张,使用实现Runnable接口的方式

//目前存在线程的安全问题 (待解决)
public class WindowsTestRunnable {
   public static void main(String[] args) {
       windows w1 = new windows();
       Thread t1 = new Thread(w1);
       Thread t2=new Thread(w1);
       Thread t3=new Thread(w1);
       t1.setName("窗口1");
       t2.setName("窗口2");
       t3.setName("窗口3");
       t1.start();
       t2.start();
       t3.start();

  }
}

class windows implements Runnable{
  private int ticket=100;
   @Override
   public void run() {
       while (true){
           if(ticket>0)
          {
               System.out.println(Thread.currentThread().getName()+": 卖票,票号为:"+ticket);
               ticket--;
          }
           else{
               break;
          }
      }
  }
}

 

  • 练习一:创建两个分线程,其中一个线程遍历100以内的偶数,另外一个遍历100以内的奇数

public class ThreadDemo1 {

   public static void main(String[] args) {
       Mythread1 mythread1 = new Mythread1();
       Mythread2 mythread2 = new Mythread2();
       mythread1.start();
       mythread2.start();

  }
}
   class Mythread1 extends Thread{
       @Override
       public void run() {
           for (int i = 0; i <=100; i++) {
               if(i%2==0)
                   System.out.println(Thread.currentThread().getName()+":"+i);
          }
      }
  }

     class Mythread2 extends Thread{
       @Override
       public void run() {
           for (int i = 0; i <= 100; i++) {
               if(i%2==1)
                   System.out.println(Thread.currentThread().getName()+":"+i);
          }
      }
  }
  • 练习二:创建两个分线程,其中一个线程遍历100以内的偶数,另外一个遍历100以内的奇数(利用Thread类的匿名子类的方式)

public class ThreadDemo2 {
   public static void main(String[] args) {
       new Thread(){
           @Override
           public void run() {
               for (int i = 0; i <=100; i++) {
                   if(i%2==0)
                       System.out.println(Thread.currentThread().getName()+":"+i);
              }
          }
      }.start();

       new Thread(){
           @Override
           public void run() {
               for (int i = 0; i <= 100; i++) {
                   if(i%2==1)
                       System.out.println(Thread.currentThread().getName()+":"+i);
              }
          }
      }.start();
  }
}

比较创建线程的两种方式

开发中:优先选择:实现Runnable接口的方式

原因:1.实现的方式没有类的单继承性的局限性

2.实现的方式更适合来处理多个线程有共享数据的情况

联系:public class Thread implements Runnable

相同点:两种方式都需要重写run(),将线程要执行的逻辑声明再run()中。

目前两种方式,要想启动线程,都是调用的Thread类中的start()。

 

 

 

测试Thread中的常用方法:

  1. start():启动当前线程;调用当前线程的run();

  2. run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中

  3. currentThread():静态方法,返回执行当前代码的线程

  4. getName():获取当前线程的名字

  5. setName():设置当前线程的名字

  6. yield():释放当前cpu的执行权

  7. join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程完全执行完以后,线程a才结束阻塞状态。

  8. stop():已过时。当执行此方法时,强制结束当前线程。

  9. sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。

  10. isAlive():判断当前线程是否存活

线程的优先级

  • MAX_PRIORITY:10

  • MIN_PRIORITY:1

  • NORM_PRIORITY:5

如何获取和设置当前线程的优先级

  • getPriority():获取线程的优先级

  • setPriority(int p):设置线程的优先级

线程的生命周期

image-20201009095723001

 

 

处理实现Runnable的线程安全问题(以卖车票的例子举例)

  1. 问题:卖票的过程中,出现了重票、错票 -->出现了线程的安全问题

  2. 问题出现的原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也操作车票

  3. 如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,线程才可以开始操作ticket。这种情况即使a出现了阻塞,也不能被改变。

  4. 在Java中,我们通过同步机制,来解决线程的安全问题。

  5. 同步的方式,解决了线程的安全问题。------优点

    操作同步代码时,只能有一个线程参与,其他线程等待。相当于时一个单线程的过程,效率低。-------局限性

方法一:同步代码块

 synchronized(同步监视器){
       //需要被同步的代码
  }
public class WindowsTestRunnable {
   public static void main(String[] args) {
       windows w1 = new windows();
       Thread t1 = new Thread(w1);
       Thread t2=new Thread(w1);
       Thread t3=new Thread(w1);
       t1.setName("窗口1");
       t2.setName("窗口2");
       t3.setName("窗口3");
       t1.start();
       t2.start();
       t3.start();

  }
}

class windows implements Runnable{
  private int ticket=100;
  Object oj=new Object();
   @Override
   public void run() {
       while (true){
           synchronized(oj) {
               if (ticket > 0) {
                   try {
                       Thread.sleep(50);
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }
                   System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
                   ticket--;
              } else {
                   break;
              }
          }
      }
  }
}

 

说明:

  1. 操作共享数据的代码,即为需要被同步的代码 ----->不能包多也不能包少

  2. 共享数据:多个线程共同操作的变量。比如ticket就是共享数据。

  3. 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。

    要求:多个线程必须要共用一把锁

    补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器

方法二:同步方法

public class WindowsTestRunnablemethod {
    public static void main(String[] args) {
        Windows2 w2 = new Windows2();

        Thread t1 = new Thread(w2);
        Thread t2 = new Thread(w2);
        Thread t3 = new Thread(w2);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }

}
class Windows2 implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while(true){
            show();
            if (ticket==0)
                break;
        }
    }
    private synchronized void show(){
        if(ticket>0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+": 卖票,票号为:"+ticket);
            ticket--;
        }
    }
}

关于同步方法的总结:

  1. 同步方法仍然涉及到同步监视器,只要是不需要我们显示的声明。

  2. 非静态的同步方法,同步监视器是:this

    静态的同步方法,同步监视器是:当前类本身

 

 

解决继承Thread类的方式的线程安全问题(以卖车票的例子举例)

例子:创建三个窗口卖票,总票数为100张,使用继承Thread类的方式

方法一:同步代码块

public class WindowsTestExtends {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Ticket ticket1 = new Ticket();
        Ticket ticket2 = new Ticket();
        ticket.setName("窗口1");
        ticket1.setName("窗口2");
        ticket2.setName("窗口3");
        ticket.start();
        ticket1.start();
        ticket2.start();
    }
}

class Ticket extends Thread{
    private static int ticket=100;
    private static Object obj=new Object();
    @Override
    public void run() {
       while (true){
          // synchronized(Ticket.class) {
               synchronized(obj) {
               if (ticket > 0) {
                   System.out.println(getName() + ": 卖票,票号为:" + ticket);
                   ticket--;
               } else {
                   break;
               }
           }
       }
    }
}

 

说明:

  1. 操作共享数据的代码,即为需要被同步的代码----->不能包多也不能包少

  2. 共享数据:多个线程共同操作的变量。比如ticket就是共享数据。

  3. 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。

    要求:多个线程必须要共用一把锁

    补充:在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器

方法二:同步方法

public class WindowsTestExtendsmethod {
    public static void main(String[] args) {
        Windows1 w1 = new Windows1();
        Windows1 w2 = new Windows1();
        Windows1 w3 = new Windows1();

        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        w1.start();
        w2.start();
        w3.start();
    }
}


class Windows1 extends Thread{
    private static int ticket=100;
    @Override
    public void run() {
        while(true){
             show();
             if (ticket==0)
                 break;
        }
    }

    private static synchronized void show(){//同步监视器:Windows1.class
        if(ticket>0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+": 卖票,票号为:"+ticket);
            ticket--;
        }
    }
}

 

解决线程安全问题的方式三:Lock锁 --- JDK5.0新增

步骤:

  1. 实例化ReentrantLock

  2. 调用锁定方法lock()

  3. 调用解锁方法unlock()

public class WindowsTestLockmethod {
    public static void main(String[] args) {
        window w1 = new window();

        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class window implements Runnable{
    private int ticket=100;
    //1.实例化ReentrantLock
    private ReentrantLock lock=new ReentrantLock(true);
    @Override
    public void run() {
        while(true){
            try{
                //2.调用锁定方法Lock()
                lock.lock();
                if (ticket>0)
                {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":卖票! 票号为"+ticket);
                    ticket--;
                }
                else {
                    break;
                }
            }finally {
//                3.调用解锁方法:unlock()
                lock.unlock();
            }

        }
    }
}

 

synchronized与lock的异同

相同:二者都可以解决线程的安全问题

不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器。

lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())

优先使用顺序:

lock -->同步代码块(已经进入了方法体,分配了相应资源)-->同步方法(方法体之外)

 

线程通信的三个方法

  1. wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器

  2. notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个

  3. notifyAll():一旦执行此方法,就会唤醒所有被wait的线程

说明:

  1. wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。

  2. wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。 否则会出现IllegalMonitorStateException异常

  3. wait(),notify(),notifyAll()三个方法是定义在java.long.Object类中。

 

***sleep()和wait()的异同

  1. 相同的:一旦执行方法,都可以使得当前的线程进入阻塞状态。

  2. 不同点:1).两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()

    2).调用的要求不同:sleep()可以在任何需要的场景下调用。wait()必须使用在同步代码块或同步方法中

    3).关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。

 

线程通信的应用经典例题:生产者/消费者问题

//线程通信的应用:经典例题:生产者/消费者问题
/**
 * 生产者(Productor)将产品交给店员(clerk),而消费者(Customer)从店员处取走产品,
 * 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员
 * 会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,
 * 店员会告诉消费者等一下,如果店中有了产品了再通知消费者来取走产品
 */
public class ProductTest {
    public static void main(String[] args) {
        clerk clerk=new clerk();
        Productor p1 = new Productor(clerk);
        Consumer c1 = new Consumer(clerk);

        p1.setName("生产者1");
        c1.setName("消费者1");

        p1.start();
        c1.start();

    }
}


class clerk{
     private int produceCount=0;
    public synchronized void productormethod() {//生产产品
        if (produceCount<20)
        {
            produceCount++;
            System.out.println(Thread.currentThread().getName()+"开始生产第"+produceCount+"个产品");

            notify();
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public synchronized void consumermethod() {//消费产品
        if (produceCount>0)
        {
            System.out.println(Thread.currentThread().getName()+"开始消费第"+produceCount+"个产品");
            produceCount--;
            notify();

        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Productor extends Thread{//生产者
    private clerk cle;

    public Productor(clerk cle) {
        this.cle = cle;
    }
    @Override
    public void run() {
        System.out.println(getName()+":开始生产产品。。。。");
        while(true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cle.productormethod();
        }
    }
}

class Consumer extends Thread{//消费者
    private clerk cle;

    public Consumer(clerk cle) {
        this.cle = cle;
    }
    @Override
    public void run() {
        System.out.println(getName()+":开始消费产品。。。。");
        while(true){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cle.consumermethod();
        }
    }
}

 

JDK5.0新增线程创建方式一:实现Callable接口

  • 与使用Runnable相比,Callable功能更加强大些

    • 相比run()方法,可以有返回值

    • 方法可以抛出异常

    • 支持泛型的返回值

    • 需要借助FutureTask类,比如获取返回结果

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/*
创建线程的方式三:实现Callable接口。------JDK  5.0新增

如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
1、call()方法可以有返回值
2、call()方法可以抛出异常,被外面的操作捕获,获取异常的信息
3、Callable是支持泛型
 */
//1、创建一个实现Callable的实现类
class NumThread implements Callable{
    //2、实现call方法,将此线程需要执行的操作声明在call()方法中
    @Override
    public Object call() throws Exception {
        int sum=0;
        for (int i = 0; i <=100 ; i+=2) {
            System.out.println(i);
            sum+=i;
        }
        return sum;
    }
}

public class ThreadNew {
    public static void main(String[] args) {
        //3、创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //4、将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);
        //5、将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();

        try {
            //6、获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
            Object sum= futureTask.get();
            System.out.println("总和为:"+sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

 

JDK5.0新增线程创建方式一:使用线程池

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

  • 好处:

    • 提高了响应速度(减少了创建新线程的实践)

    • 降低资源消耗(重复利用线程池线程,不需要每次都创建)

    • 便于线程的管理

      • corePoolSize:核心池的大小

      • maximumPoolSize:最大线程数

      • KeepAliveTIme:线程没任务时最多保持多长时间会终止

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


class NumberThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <=100; i++) {
            if(i%2==0)
                System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

class NumberThread1 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <=100; i++) {
            if(i%2!=0)
                System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
public class ThreadPool {
    public static void main(String[] args) {
        //1.提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());   //适用于Runnable
        service.execute(new NumberThread1());   //适用于Runnable
//        service.submit();   //适用于Callable
        // 3.关闭连接池
        service.shutdown();

    }
}

 

posted @ 2020-10-18 16:42  A1varo  阅读(249)  评论(0)    收藏  举报