19. 线程的生命周期、同步机制、懒汉式线程安全的实现、死锁、线程的通信、线程的另外两种创建方式、集合框架的概述

1. 线程的生命周期

在这里插入图片描述

public enum State {
    /**
     * Thread state for a thread which has not yet started.
     */
    NEW,

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    RUNNABLE,

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED,

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     */
    WAITING,

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING,

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    TERMINATED;
}

2. 线程的同步机制

  • 知识概述
/**
 * 例题:开启三个窗口售票,总票数为100张。
 * <p>
 * 使用继承Thread类的方式实现
 *
 * 1. 程序执行中出现了重票、错票
 * 2. 如何解决?需要一个线程针对于ticket完全操作结束之后,其他的线程才能进入继续操作ticket
 *
 * 3. 代码层面如何解决?线程的同步机制。
 *
 * 4. 同步机制的实现方式:
 *  方式一:同步代码块
 *      synchronized(同步监视器){
 *          //需要被同步的代码
 *      }
 *     说明:① 需要被同步的代码,即为操作共享数据的代码
 *           ② 共享数据? 多个线程共同操作的数据
 *           ③ 同步监视器,即为一个对象。俗称:锁
 *             要求:① 任何一个类的对象都可以充当同步监视器。
 *                  ② 多个线程必须共享同一个同步监视器。
 *
 *
 *  方式二:同步方法
 *       如果操作共享数据的代码完整的声明在一个或多个方法中,此时可以考虑将此方法声明为同步方法。
 *
 *       对于非静态的同步方法而言,默认的同步监视器是:this
 *       对于静态的同步方法而言,默认的同步监视器是:当前类.class
 *
 *
 * 测试使用同步代码块解决继承Thread类的方式中的线程安全问题。
 *
 *  小结:针对同步监视器:实现Runnable的方式中可以考虑使用this。在继承Thread类的方式时,慎重this。
 *                      可以考虑使用的同步监视器是:当前类.class
 *        针对于操作共享数据的代码:不能“包”少了,也不能“包”多了
 *
 *  5. 同步机制:好处:解决了线程的安全问题。
 *              缺点:执行同步代码块的过程中,其实是单线程的。效率低。
 */

2.1 同步代码块解决继承Thread中的线程安全问题

class Window extends Thread {

    static int ticket = 100;//初始票数100张
    static Object obj = new Object();
    @Override
    public void run() {
            while (true) {

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

//            synchronized(obj){
            synchronized(Window.class){ //Class clazz = Window.class

            //不能使用this
//            synchronized(this){  //此时的this代表w1,w2,w3
                if (ticket > 0) {

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

                    System.out.println(Thread.currentThread().getName() + "卖票,当前票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

public class WindowTest {
    public static void main(String[] args) {

        Window t1 = new Window();
        Window t2 = new Window();
        Window t3 = new Window();

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

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

    }
}

2.2 同步代码块解决实现Runnable中的线程安全问题

class Window1 implements Runnable{
    int ticket = 100;
//    Object obj = new Object();
//    Dog dog = new Dog();
    @Override
    public void run() {
        while(true){
            synchronized (this){ //此时的this表示w,是唯一的
//            synchronized (dog){

                if(ticket > 0){
                    try {
                        Thread.sleep(30);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖票,当前票号为:" + ticket);
                    ticket--;

                }else{
                    break;
                }
            }

        }
    }
}

public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w = new Window1();

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

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

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

class Dog{

}

2.3 同步方法解决继承Thread中的线程安全问题

class Window2 implements Runnable {
    int ticket = 100;

    @Override
    public void run() {
        while (true) {
//            synchronized (this){
                show();
//            }
        }
    }

    public synchronized void show() { //默认的同步监视器是this,此题中代表:w
        if (ticket > 0) {
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "卖票,当前票号为:" + ticket);
            ticket--;

        }
    }
}

public class WindowTest2 {
    public static void main(String[] args) {
        Window2 w = new Window2();

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

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

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

2.4 同步方法解决实现Runnable中的线程安全问题

class Window3 extends Thread {

    static int ticket = 100;//初始票数100张

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

            show();

        }

    }

    public static synchronized void show(){ //此时的同步监视器是:this。此题中表示:t1,t2,t3
        if (ticket > 0) {

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

            System.out.println(Thread.currentThread().getName() + "卖票,当前票号为:" + ticket);
            ticket--;
        }
    }
}

public class WindowTest3 {
    public static void main(String[] args) {

        Window3 t1 = new Window3();
        Window3 t2 = new Window3();
        Window3 t3 = new Window3();

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

        t1.start();
        t2.start();
        t3.start();
    }
}
  • 练习
    在这里插入图片描述

3. 单例模式:懒汉式的线程安全的实现

public class SingletonTest1 {
    public static void main(String[] args) {
        Runtime r1 = Runtime.getInstance();
        Runtime r2 = Runtime.getInstance();

        System.out.println(r1 == r2);
    }
}

//懒汉式
class Runtime {
    //1. 私有化类的构造器
    private Runtime() {

    }

    //2. 声明当前类的一个变量
    private static Runtime instance = null;

    //3. 调用方法,返回当前类的一个实例
    //方式1:
//    public static synchronized Runtime getInstance(){
//
//        if(instance == null){
//
//            instance = new Runtime();
//        }
//        return instance;
//    }
    //方式2:比方式1好
//    public static Runtime getInstance() {
//
//        synchronized (Runtime.class) {
//            if (instance == null) {
//
//                instance = new Runtime();
//            }
//        }
//        return instance;
//    }

    //方式3:推荐
    public static Runtime getInstance() {

        if(instance == null){

            synchronized (Runtime.class) {
                if (instance == null) {

                    instance = new Runtime();
                }
            }
        }
        return instance;
    }
}

4. 死锁

/**
 * 死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
 *
 * 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
 *
 */
public class DeadLockTest {

    public static void main(String[] args) {

        StringBuilder s1 = new StringBuilder();
        StringBuilder s2 = new StringBuilder();

        new Thread(){
            @Override
            public void run() {
                synchronized (s1){

                    s1.append("a");
                    s2.append("1");

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

                    synchronized (s2){
                        s1.append("b");
                        s2.append("2");

                        System.out.println(s1);
                        System.out.println(s2);
                    }

                }

            }
        }.start();

        new Thread(new Runnable(){

            @Override
            public void run() {
                synchronized (s2){

                    s1.append("c");
                    s2.append("3");

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

                    synchronized (s1){
                        s1.append("d");
                        s2.append("4");

                        System.out.println(s1);
                        System.out.println(s2);
                    }

                }
            }
        }).start();
    }
}

5. 线程的通信

/**
 * 例题:使用两个线程打印 1-100。线程1, 线程2 交替打印
 *
 * 线程的通信:
 *
 * wait():一旦执行此方法,就会使得执行此方法所在的线程进行阻塞状态。同时释放同步监视器。
 * notify():一旦执行此方法,就会唤醒被wait()的线程中优先级最高的那个线程。在获得同步监视器的情况下,
 *         继续执行被wait()之后的代码。
 * notifyAll():一旦执行此方法,就会唤醒所有被wait()的线程。在获得同步监视器的情况下,继续执行被wait()之后的代码。
 *
 *
 * 说明:1. wait()/ notify() / notifyAll() :必须使用在同步代码块或同步方法中。
 *      2. wait()/ notify() / notifyAll()的调用者必须是同步代码块或同步方法中的同步监视器。
 *         否则,会报:IllegalMonitorStateException
 *      3. wait()/ notify() / notifyAll() 是定义在java.lang.Object中。
 *
 *
 * 高频面试题:sleep() 和  wait()的异同?
 *      1. 两个方法声明的位置?Thread:sleep()   / Object:wait()
 *      2. 使用的范围有没有要求? sleep():在任何想使用的地方。  wait():必须使用在同步代码块或同步方法中
 *      3. 如果都放在同步代码块或同步方法中,是否释放锁? sleep():不会  wait():会
 *      4. 都会导致线程进入阻塞状态。但是结束阻塞状态的方式不同? sleep():时间到就结束阻塞。  wait():需要被唤醒
 *
 */
class PrintNumber implements Runnable{
    int number = 1;
    Object obj = new Object();

    @Override
    public void run() {

        while(true){
            synchronized (obj) {
                //唤醒被wait()的线程
                obj.notify();
                if(number <= 100){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;
                }else{
                    break;
                }
                //当前的线程进入阻塞
                try {
                    obj.wait(); //会释放同步监视器
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //。。。。
            }
        }
    }
}

public class CommunicationTest {
    public static void main(String[] args) {
        PrintNumber num = new PrintNumber();

        Thread t1 = new Thread(num);
        Thread t2 = new Thread(num);

        t1.setName("线程1");
        t2.setName("线程2");

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

面试题:sleep() 和 wait()的异同

  • 生产者消费者题目
/**
 * 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
 * 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,
 * 店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,
 * 店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
 *
 *
 * 分析:
 * 1. 是不是多线程问题?是  生产者线程、消费者线程
 * 2. 会出现线程安全问题吗?会。有共享数据
 * 3. 共享数据是什么? 产品的数量
 * 4. 是否存在线程的通信? 有!
 *
 */
public class ProducterConsumerTest {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producter p1 = new Producter(clerk);
        Consumer c1 = new Consumer(clerk);
        Consumer c2 = new Consumer(clerk);

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

        p1.start();
        c1.start();
        c2.start();
    }
}

class Clerk{//店员
    int productNum;//产品数据

    //添加产品
    public synchronized void addProduct(){
        if(productNum < 20){
            productNum++;
            System.out.println(Thread.currentThread().getName() + "生产了第" + productNum + "个产品");

            notifyAll();

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

    //减少产品
    public synchronized void consumeProduct(){
        if(productNum > 0){
            System.out.println(Thread.currentThread().getName() + "消费了第" + productNum + "个产品");
            productNum--;

            notifyAll();

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

class Producter extends Thread{ //生产者

    Clerk clerk;

    public Producter(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() { //生产产品

        System.out.println(Thread.currentThread().getName() + "开始生产产品");

        while(true){

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

            clerk.addProduct();

        }
    }
}

class Consumer extends Thread{ //消费者
    Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }
    @Override
    public void run() { //消费产品
        System.out.println(Thread.currentThread().getName() + "开始消费产品");
        while(true){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.consumeProduct();
        }
    }
}

6. 了解:线程的另外的2种创建方式

6.1 实现Callable接口

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/*创建多线程的方式三:实现Callable (jdk5.0新增的)
*/
//1.创建一个实现Callable的实现类
class NumThread implements Callable<Integer>{
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if(i % 2 == 0){
                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()的返回值。
            Integer sum = (Integer) futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

}

面试题:使用Runnable和Callable的对比?
1.结论:实现Callable的方式好
2.体现:
① 可以在call方法中抛出异常,更灵活
② call方法,相较于run()可以有返回值
③ 可以通过泛型的方法,指定返回值的确定类型。

6.2 使用线程池

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

//创建并使用多线程的第四种方法:使用线程池
//使用线程池创建多线程的好处:
//1.降低了资源的消耗,使用完的线程可以被复用。
//2.提高了程序的响应速度。
//3.便于管理。

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);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//        //设置线程池的属性
//        System.out.println(service.getClass());//ThreadPoolExecutor
        service1.setMaximumPoolSize(20);


        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合适用于Runnable
        service.execute(new NumberThread1());//适合适用于Runnable

//        service.submit(Callable callable);//适合使用于Callable
        //3.关闭连接池
        service.shutdown();
    }

}

7. 了解:Lock的方式解决线程安全问题

import java.util.concurrent.locks.ReentrantLock;

class Window implements Runnable{
   int ticket = 100;
   private final ReentrantLock lock = new ReentrantLock();
   public void run(){
      
      while(true){
         try{
            lock.lock();
            if(ticket > 0){
               try {
                  Thread.sleep(10);
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
               System.out.println(ticket--);
            }else{
               break;
            }
         }finally{
            lock.unlock();
         }
      }
   }
}

public class ThreadLock {
   public static void main(String[] args) {
      Window t = new Window();
      Thread t1 = new Thread(t);
      Thread t2 = new Thread(t);
      
      t1.start();
      t2.start();
   }
}

8. 集合框架的概述

  • 了解
数组在内存存储方面的特点:
数组初始化以后,长度就确定了。
数组声明的类型,就决定了进行元素初始化时的类型。

数组在存储数据方面的弊端:
数组初始化以后,长度就不可变了,不便于扩展
数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。同时无法直接获取存储元素的个数
数组存储的数据是有序的、可以重复的。---->存储数据的特点单一

/* 二、集合框架
* java.util.Collection:存储一个一个的数据
*      |----java.util.List:存储有序的、可重复的数据。  ----> “动态”数组
*              |---- ArrayList\LinkedList\Vector
*      |----java.util.Set:存储无序的、不可重复的数据   ----> 高中学的"集合"
*              |---- HashSet \ LinkedHashSet \TreeSet
*
* java.util.Map:存储一对一对的数据 (key-value) ----> 高中学的"函数" y = f(x)   y = 2 * x + 1 (x1,y1),(x2,y2)
*      |---- HashMap\LinkedHashMap\TreeMap\Hashtable\Properties
*
* 三、三个层面(递进关系)
* 层次一:掌握不同接口的主要实现类的实例化,及常用方法 (ArrayList\HashSet\HashMap)
* 层次二:掌握不同实现类之间的区别
* 层次三:常用的集合类的底层实现原理--->数据结构。 (ArrayList\LinkedList\HashMap)
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值