继承Thread类

继承Thread类创建并启动多线程的步骤:

①.定义一个类,继承自Thread类,并重写该类的run方法,该run方法的方法体就代表了线程需要完成的任务,因此,run方法的方法体被称为线程执行体
②.创建Thread子类的对象,即创建了子线程
③.用线程对象的start方法来启动该线程

Demo先创建一个售票线程

  1. public class SellTickets extends Thread {
  2. //共享数据
  3. static int count = 100;
  4. @Override
  5. public void run() { //执行的内容
  6. //循环售票
  7. while(count > 0) {
  8. count--;
  9. System.out.println(Thread.currentThread().getName() + "售出了一张票,剩余" + count);
  10. }
  11. }
  12. }

测试类

  1. public class TheadDemo {
  2. public static void main(String[] args) {
  3. //模拟四个售票员售票 开启四个线程 3个是thread 一个是main
  4. SellTickets s1 = new SellTickets();
  5. SellTickets s2 = new SellTickets();
  6. SellTickets s3 = new SellTickets();
  7. // System.out.println(s1.currentThread().getName()); //这个线程的名称是main
  8. s1.start();
  9. s2.start();
  10. s3.start();
  11. }
  12. }

实现Runnable接口

实现Runnable接口创建并启动多线程的步骤:

a.定义一个Runnable接口的实现类,并重写该接口中的run方法,该run方法的方法体同样是该线程的线程执行 体
b.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
c.调用线程对象的start方法来启动该线程

  1. public class fatherDemo implements Runnable{
  2. static int count = 100;
  3. @Override
  4. public void run() {
  5. while(count > 0) {
  6. count--;
  7. System.out.println(Thread.currentThread().getName() + "售出了 一张票,剩余" + count);
  8. }
  9. }
  10. public static void main(String[] args) {
  11. Thread t1 = new Thread(r);
  12. Thread t2 = new Thread(r);
  13. Thread t3 = new Thread(r);
  14. t1.start();
  15. t2.start();
  16. t3.start();
  17. }
  18. }

两种实现方式的比较

①实现Runnable接口的方式

a.线程类只是实现了Runnable接口,还可以继承其他类【一个类在实现接口的同时还可以继承另外一个类】
b.可以多个线程共享同一个target对象,所以非常适合多个线程来处理同一份资源的情况
c.弊端:编程稍微复杂,不直观,如果要访问当前线程,必须使用Thread.currentThread()

② 继承Thread类的方式

a.编写简单,如果要访问当前线程,除了可以通过Thread.currentThread()方式之外,还可以使用 super关键字
b.弊端:因为线程类已经继承了Thread类,则不能再继承其他类【单继承】 实际上大多数的多线程应用都可以采用实现Runnable接口的方式来实现【推荐使用匿名内部类】java类是单知继承的

调用start()与run()方法的区别

  1. Thread thread = new Thread();
  2. thread.start();

① - start()方法会新建一个线程,并且让这个线程执行run()方法。

  1. Thread thread = new Thread();
  2. thread.run();

② - 调用run()也能正常执行。但是,却不能新建一个线程,而是在当前线程中调用run()方法,只是作为一个普通的方法调用

synchronized修饰符

  1. public class MyThread extends Thread {
  2. private int count = 5;
  3. @Override
  4. public synchronized void run() {
  5. count--;
  6. System.out.println(this.currentThread().getName() + " count:" + count);
  7. }
  8. public static void main(String[] args) {
  9. MyThread myThread = new MyThread();
  10. Thread thread1 = new Thread(myThread, "thread1");
  11. Thread thread2 = new Thread(myThread, "thread2");
  12. Thread thread3 = new Thread(myThread, "thread3");
  13. Thread thread4 = new Thread(myThread, "thread4");
  14. Thread thread5 = new Thread(myThread, "thread5");
  15. thread1.start();
  16. thread2.start();
  17. thread3.start();
  18. thread4.start();
  19. thread5.start();
  20. }
  21. }

当多个线程访问MyThread 的run方法的时候,如果使用了synchronized修饰,那个多线程就会以排队的方式进行处理(这里排队是按照CPU分配的先后顺序而定的),一个线程想要执行synchronized修饰的方法里的代码,首先是尝试获得锁,如果拿到锁,执行synchronized代码体的内容,如果拿不到锁的话,这个线程就会不断的尝试获得这把锁,直到拿到为止,而且多个线程同时去竞争这把锁,也就是会出现锁竞争的问题。

产生原因

有多个线程在同时访问一个资源,如果一个线程在取值的过程中,时间片又被其他线程抢走了,临界资源问题就产生了

解决方案

一个线程在访问临界资源的时候,如果给这个资源“上一把锁”,这个时候如果其他线程也要访问这个资源, 就得在“锁”外面等待

对象锁:任意的对象都可以被当做锁来使用
类锁:把一个类当做锁,语法为:类名.class

同步代码块

  1. synchronized(锁) {
  2. //需要访问临界资源的代码段
  3. }

说明:

  • a.程序走到代码段中,就用锁来锁住了临界资源,这个时候,其他线程不能执行代码段中的代码,只能在锁外边等待
  • b.执行完代码段中的这段代码,会自动解锁。然后剩下的其他线程开始争抢cpu时间片
  • c.一定要保证不同的线程看到的是同一把锁,否则同步代码块没有意义.

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:

修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。

  1. public class SellTickets {
  2. static int count = 10;
  3. //任何对象都可以充当一个对象锁
  4. static Object obj = new Object();
  5. static Runnable r = new Runnable() {
  6. @Override
  7. public void run() {
  8. while(count > 0) {
  9. System.out.println(Thread.currentThread().getName()+"--");
  10. synchronized(obj){
  11. count--;
  12. if(count <= 0) {
  13. return;
  14. }
  15. System.out.println(Thread.currentThread().getName() + "售出了 一张票,剩余" + count);
  16. }
  17. }
  18. }
  19. };
  20. public static void main(String[] args) {
  21. Thread t1 = new Thread(r);
  22. Thread t2 = new Thread(r);
  23. Thread t3 = new Thread(r);
  24. // t1.setPriority(8);
  25. t1.start();
  26. t2.start();
  27. t3.start();
  28. }
  29. }

同步代码块和类锁的使用

  • 使用类锁的案例如下,参考格式 即 类.class

036线程 - 图1

同步方法

  • 没有加锁的方法

036线程 - 图2

  • 加锁的方法

036线程 - 图3

  • 参考代码如下,大家自行运行实践(ps:可以去除 synchronized关键字)
public class SellTickets {
    static int count = 100;
    //任何对象都可以充当一个对象锁
    //static Object obj = new Object();
    static Runnable r = new Runnable() {
      @Override
      public void run() {
          while(count > 0) {
           sellTickets();
          }
      }
        //同步方法,作用和同步代码块一样
        public synchronized void sellTickets() {
            if (count <= 0) {
                return;
            }
            count--;
            System.out.println("售票员" + Thread.currentThread().getName() + "售出一张票,余额 为" + count);
        }
    };
    public static void main(String[] args) {
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
       // t1.setPriority(8);
        t1.start();
        t2.start();
        t3.start();
      }
}

等待唤醒机制(消费者与生产者)

一、生产者消费者问题
生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。

该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。

036线程 - 图4

生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

生产消费者模型

生产者消费者模型具体来讲,就是在一个系统中,存在生产者和消费者两种角色,他们通过内存缓冲区进行通信,生产者生产消费者需要的资料,消费者把资料做成产品。生产消费者模式如下图。

036线程 - 图5

代码实现

案例如下

036线程 - 图6
 首先定义一个类 ,这个类创建一个长度为5的数组,然后用synchronized同步锁实现生产数据和消费数据两个方法,两个方法中分别有 wait()和notify();

public class Resources {

    private int[] arr = new int[5];
    private int count = 0;

    /**
     * 生产一个数据
     */
    synchronized public void product() throws Exception {
        if(count == arr.length) {
            wait();
        }else {
            int m =  (int)Math.floor(Math.random()*10+1) ;
            arr[count] = m;
            count ++ ;
            System.out.println("生产:"+m);
            //唤醒消费者
            notify();
        }
    }

    /**
     * 消费一个数据
     */
    synchronized public void consume() throws Exception {
        if(count == 0) {
            wait();
        }else {
            int n = arr[count-1];
            arr[count-1] = 0;
            count--;
            System.out.println("消费:"+n);
            //唤醒生产者
            notify();
        }
    }
}

创建生产者线程,引入刚刚的对象,用刚刚的对象调用生产的方法

public class ProduceThread extends Thread {
    private Resources r;
    public ProduceThread(Resources r) {
        this.r = r;
    }
    public void run() {
        try {
            while(true) {
                r.product();
                Thread.sleep(1);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

创建消费者线程,同样引入Resources对象,然后再用这个对象调用消费数据的方法

public class ConsumerThread extends Thread {
    private Resources r;
    public ConsumerThread(Resources r) {
        this.r = r;
    }
    public void run() {
        try {
            while(true) {
                r.consume();
                Thread.sleep(1);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

测试类

public class Test {

    public static void main(String[] args) {

        Resources r = new Resources();

        ProduceThread t1 = new ProduceThread(r);
        ConsumerThread t2 = new ConsumerThread(r);

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

}

java中notify 和 notifyAll区别

notify()notifyAll()的共同点:

均能唤醒正在等待的线程,并且均是最后只有一个线程获取资源对象的锁。

notify()notifyAll()的不同点:

notify() 只能唤醒一个线程,而notifyall()能够唤醒所有的线程,当线程被唤醒以后所有被唤醒的线程竞争获取资源对象的锁,其中只有一个能够得到对象锁,执行代码。注意:wait()方法并不是在等待资源的锁,而是在等待被唤醒。

当您在对象上调用 notify 方法时,它会唤醒等待该对象的线程之一。因此,如果多个线程正在等待一个对象,它将唤醒其中一个。现在你一定想知道它会唤醒哪一个。它实际上取决于操作系统的实现。

notifyAll 将唤醒等待该对象的所有线程,不像 notify 只唤醒其中一个。哪个将首先唤醒取决于线程优先级和操作系统实现。

sleep方法和wait方法区别

这个问题常问,sleep方法和wait方法都可以用来放弃CPU一定的时间,不同点在于如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器

监视器是由Per Brich Hansen和Tony Hoare提出的概念,Java以不精确的方式采用了它,也就是Java中的每个对象有一个内部的锁和内部条件。如果一个方法用synchronized关键字声明,那么,它表现的就像一个监视器方法。通过wait/notifyAll/nofify来访问条件变量

当您在对象上调用 wait 方法时,它会告诉线程放弃锁定并进入睡眠状态,除非并且直到某个其他线程进入同一监视器并在其上调用 notify 或 notifyAll 方法。

演示

package org.arpit.java2blog.thread;

public class Book {

 String title;
 boolean isCompleted;

 public Book(String title) {
  super();
  this.title = title;
 }

 public String getTitle() {
  return title;
 }
 public void setTitle(String title) {
  this.title = title;
 }
 public boolean isCompleted() {
  return isCompleted;
 }
 public void setCompleted(boolean isCompleted) {
  this.isCompleted = isCompleted;
 }

}

线程池

通过Executors类提供的方法。

1、newCachedThreadPool

创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。

private static void createCachedThreadPool() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.execute(() -> {
                // 获取线程名称,默认格式:pool-1-thread-1
                System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
                // 等待2秒
                sleep(2000);
            });
        }
    }

newFixedThreadPool

创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。

private static void createFixedThreadPool() {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.execute(() -> {
                // 获取线程名称,默认格式:pool-1-thread-1
                System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
                // 等待2秒
                sleep(2000);
            });
        }
    }

newScheduledThreadPool

创建一个周期性的线程池,支持定时及周期性执行任务。

private static void createScheduledThreadPool() {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
        System.out.println(DateUtil.now() + " 提交任务");
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.schedule(() -> {
                // 获取线程名称,默认格式:pool-1-thread-1
                System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
                // 等待2秒
                sleep(2000);
            }, 3, TimeUnit.SECONDS);
        }
    }

newSingleThreadExecutor

创建一个单线程的线程池,可保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

private static void createSingleThreadPool() {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.execute(() -> {
                // 获取线程名称,默认格式:pool-1-thread-1
                System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
                // 等待2秒
                sleep(2000);
            });
        }
    }