java-线程笔记

归纳下常考的知识点

一、创建线程的方式有多少种?
四种,继承Thread类,实现Runnable接口,实现Callable接口,线程池

二、如何理解实现Callable接口的方式创建线程比实现Runnable接口创建多线程方式强大?
1.call()可以有返回值
2.call()可以抛出异常,被外面的操作捕获,获取异常的信息
3. Callable是支持泛型的

三、解决线程安全的方式有多少种?
三种,同步代码块,同步方法,Lock(jdk5新增)

四、对比下synchronized和Lock?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完同步代码块之后自动释放锁,
lock需要手动的启动同步(lock()),同时结束同步也需要手动实现(unlock())

五、sleep(),wait()的异同?
相同点:一旦执行此方法,都会使得当前线程进入阻塞状态
不同点:
1.两个方法声明的位置不同,Thread类中声明sleep(),Object类中声明wait()
2.调用的要求不同,sleep()可以在任何场景下调用,而wait()只能在同步代码块或者同步方法中调用
3.关于是否释放同步监视器:如果两个方法都使用在同步代码块或者同步方法中,sleep()不会释放锁,wait()会释放锁

六、关于锁?
注意,多个线程如果要想解决线程安全问题,那么必须要共用同一把锁,任何对象都可以作为锁。
1.非静态的同步方法,同步监视器(锁)是this
2.静态的同步方法,同步监视器(锁)是当前类本身
3.同步代码块中的锁慎重选择,最好选当前类对象本身作为锁
4.使用Lock时,一定要用try,finally结构,在最后要释放锁

七、总结锁的释放与不释放
释放锁的操作
1.当前线程的同步方法,同步代码块执行结束
2.当前线程在同步代码块,同步方法中,遇到break,return终止了代码块
3.当前线程在同步代码块,同步方法中,遇到了未处理的ERROR/EXCEPTION,导致异常结束
4.当前线程在同步代码块,同步方法中执行了线程对象的wait()方法,当前线程停止,并且释放锁

不释放锁的操作
1.线程在执行同步代码块或者同步方法时,程序调用了sleep()/yield()方法暂停当前线程的执行
2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁,应尽量避免使用suspend()何resume()方法来控制线程

线程的生命周期:
在这里插入图片描述

1.线程,进程,程序概念是什么?

package com.yl.pdfdemo.day07.p1;

/**
 * @Author wfj
 * @Date 2021/4/27
 * @Description 线程,进程,程序概念
 * @Version 1.0
 */

public class Description {
    /**
     * 程序(program):是为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象
     *
     * 进程(process):是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。--声明周期
     *程序是静态的,进程是动态的,进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
     *
     * 线程(Thread):进程可以进一步细化分为线程,是一个程序内部的一条执行路径
     * 1)若一个进程同一时间并行执行多个线程,就是支持多线程的
     * 2)线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(PC),线程切换的开销小
     * 3)一个进程中的多个线程共享相同的内存单元/内存地址空间->它们从同一堆中分配对象,可以访问相同的变量和对象
     * 这使得线程之间通信更加简便,高效,但是多个线程操作共享的系统资源可能就会带来安全的隐患
     *
     *      注意:每一个线程拥有自己一份独立的栈和程序计数器
     *      一个进程可以包含多个线程,线程可以共用堆,方法区的资源
     *
     * 单核CPU,多核CPU理解
     * 单核CPU其实就是一种假的多线程,因为在同一时间内,只能执行一个线程
     * 多核的话,代表着同一时间,多个都CPU可以执行线程(现在的服务器都是多核的)
     * 一个java.exe最起码有三个线程,main线程,gc垃圾回收线程,异常处理线程
     *
     * 并行,并发
     * 并行:多个CPU同时执行多个任务,比如多个人做不同的事情
     * 并发:一个CPU(采用时间片)同时执行多个任务,比如多个人做同一件事情
     *
     * 什么时候需要创建线程?
     * 程序同时需要执行两个或多个任务
     *
     * 线程的生命周期(重点)
     * 1.新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
     * 2.就绪:处于新建的状态的线程调用start()后,将进入线程队列等待CPU时间片,此时它已经具备了运行的条件,只是没分配到CPU资源
     * 3.运行:当就绪状态的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
     * 4.阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的状态,进入阻塞状态
     * 5.死亡:线程完成了它的全部工作或者线程提前被强制性地中止或出现异常导致结束
     *
     */
}

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

package com.yl.pdfdemo.day07.p1;

/**
 * @Author wfj
 * @Date 2021/4/28
 * @Description 线程的创建方式一:继承Thread类
 * @Version 1.0
 */

public class ThreadTest1 {
    /**
     * 1.创建一个继承于Tread类的子类
     * 2.重写Thread类的run方法 ===》将此线程执行的操作声明在run()中
     * 3.创建Thread类的子类对象
     * 4.通过此对象调用start方法
     */
    public static void main(String[] args) {
//        3.创建Thread类的子类对象
//        4.通过此对象调用start方法,作用:1)启动线程,2)调用run()方法
        MyThread t1 = new MyThread();
        //此时调用start()方法,线程进入了就绪状态,线程并没有立刻执行,等到CPU调度该线程,且该线程获得CPU资源时,才真正地执行
        t1.start();

        //注意1.如果是调用run()方法的话,仅仅是调用了方法,执行里面的东西,并没有启动一个线程
        //t1.run();
        //注意2.再启动一个线程,不可以让已经start()的线程去执行,会报IllegalThreadStateException
//        t1.start();
        //我们需要重新创建一个线程的对象
        MyThread t2 = new MyThread();
        t2.start();

        //如下内容为main线程中的内容
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName()+":" + i + "main()线程");
            }
        }
    }
}

//1.创建一个继承于Tread类的子类
class MyThread extends Thread{
    //2.重写Thread类的run方法 ===》将此线程执行的操作声明在run()中
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}

3.创建线程的方式二:实现Runnable接口

package com.yl.pdfdemo.day07.p1;

/**
 * @Author wfj
 * @Date 2021/4/28
 * @Description 线程的创建方式二:实现Runnable接口
 * @Version 1.0
 */

public class ThreadTest2 {
    /**
     * 步骤:
     * 1.创建一个实现了Runnable接口的类
     * 2.实现类去实现Runnable中的抽象方法:run()
     * 3.创建实现类的对象
     * 4.将此对象作为参数传递到Thread的构造器中,创建Thread类对象
     * 5.通过Thread类对象调用start()方法
     *
     * 比较创建线程的两种方式
     * 开发中,优先选择实现Runnable接口的方式
     * 原因:1.实现的方式没有类的单继承的局限性
     *       2.实现的方式更适合多个线程共享数据的情况
     *
     * 联系:public class Thread implements Runnable
     * 相同点:两种方式都需要重写run方法,线程要执行的逻辑中声明在run()中
     *
     */
    public static void main(String[] args) {
        MThread m1 = new MThread();
        Thread t1 = new Thread(m1);

        t1.start();

        //再启动一个线程
        Thread t2 = new Thread(m1);
        t2.start();
    }
}

class MThread implements Runnable{

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

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

package com.yl.pdfdemo.day07.p1;

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

/**
 * @Author wfj
 * @Date 2021/5/8
 * @Description 创建线程的方式三:实现Callable接口 ---jdk5.0新增
 * @Version 1.0
 */

public class ThreadNew {
    public static void main(String[] args) {
        /**
         * Future接口
         * 1.可以对具体Runnable、Callable任务的执行结果进行取消,查询是否完成,获取结果等
         * 2.FutureTask是Future接口的唯一的实现类
         * 3.FutureTask同时实现了Runnable、Future接口,它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
         *
         * 详细过程步骤
         * 1.创建一个实现Callable接口的实现类
         * 2.实现类重写call()方法,将此线程需要执行的操作声明在call()中
         * 3.创建Callable接口实现类的对象
         * 4.将此Callable接口实现类的对象传递到FutureTak构造器中,创建FutureTask对象
         * 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并且start()
         *
         * 如何理解实现Callable接口的方式创建线程比实现Runnable接口创建多线程方式强大?
         * 1.call()可以有返回值
         * 2.call()可以抛出异常,被外面的操作捕获,获取异常的信息
         * 3.Callable是支持泛型的
         */
        NumThread numThread = new NumThread();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(numThread);
        new Thread(futureTask).start();
        try {
            //可以拿到返回值
            int i  = futureTask.get();
            System.out.println(i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class NumThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                sum += i;
            }
        }
        return sum;
    }
}

5.创建线程的方式四:通过线程池创建

package com.yl.pdfdemo.day07.p1;

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

/**
 * @Author wfj
 * @Date 2021/5/8
 * @Description 创建线程的方式四:使用线程池 == jdk5.0以后
 * @Version 1.0
 */

public class ThreadPool {
    public static void main(String[] args) {
        /**
         *使用线程池的好处:
         * 1.提高响应速度,减少了创建线程的时间
         * 2.降低资源消耗,重复利用线程池中的线程,不需要重复创建
         * 3.便于线程管理
         *      1)corePoolSize:核心池大小
         *      2)maximumPoolSize:最大线程数
         *      3)keepAliveTime:线程没有任务时最多保持多长时间后会终止
         */
        //1.提供指定线程数量的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        //设置线程池的属性
        ThreadPoolExecutor poolExecutor = (ThreadPoolExecutor) executorService;
        poolExecutor.setCorePoolSize(15);
        poolExecutor.setMaximumPoolSize(15);

        //2.执行指定的线程操作,需要提供实现Callable接口或者实现Runnable接口的对象
        executorService.execute(new Num1Thread());//适用于Runnable
        executorService.execute(new Num2Thread());//适用于Runnable
        //executorService.submit(Callable callable)//适用于Callable接口

        //3.关闭连接池
        executorService.shutdown();
    }
}

class Num1Thread implements Runnable{

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

class Num2Thread implements Runnable{

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

6.线程的主要方法

package com.yl.pdfdemo.day07.p1;

/**
 * @Author wfj
 * @Date 2021/4/28
 * @Description 线程相关方法
 * @Version 1.0
 */

public class ThreadMethodTest {
    /*
    * Thread中常用的方法
    * 1.start(),启动当前线程,调用当前线程的run()
    * 2.run(),通常需要重写Thread类中的此类方法,将创建的线程要执行的操作放到这里面
    * 3.currentThread(),静态方法,返回当前代码的线程
    * 4.getName(),获取当前线程的名字
    * 5.setName(),设置当前线程的名字
    * 6.yield(),释放当前cpu的执行权,有可能下一次cpu又会把执行权分配给当前对象
    * 7.join(),在线程a中调用线程b的join()方法,线程a会进入阻塞状态,等到线程b的执行完以后,线程a才会结束阻塞状态
    * 8.stop(),方法已过时,强制结束当前线程
    * 9.sleep(long millitime) 睡眠,单位为毫秒,在这个指定的单位时间内,当前线程处于阻塞状态
    * 10.isAlive():判断当前线程是否存活
    *
    * 线程的优先级:
    * 1.
    *  MAX_PRIORITY:10
    *  MIN_PRIORITY:1
    *  NORM_PRIORITY:5
    *
    * 2.如何获取和设置线程的优先级
    *   getPriority():获取线程的优先级
    *   setPriority(int p):设置线程的优先级
    *
    * 说明:高优先级的的线程要抢占低优先级的cpu的执行权,但是从概率上讲,高优先级的线程高概率的情况下被执行,
    * 并不意味着,只有当高优先级的线程执行完成之后,低优先级的才会执行
    *
    * */
    public static void main(String[] args) {
        HelloThread h1 = new HelloThread("线程ONE");
        //h1.setName("线程一");
        h1.setPriority(Thread.MAX_PRIORITY);
        h1.start();

        //给主线程命名
        Thread.currentThread().setName("主线程");
        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
        for (int i = 0; i < 100; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i );
            }
            if (i == 20) {
                try {
                    h1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println(h1.isAlive());
    }
}

class HelloThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                //睡眠1秒
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i );
            }
            if (i % 20 == 0) {
                yield();
            }
        }
    }

    //通过构造器给线程命名
    public HelloThread(String name) {
        super(name);
    }
}

7.线程间的通信

package com.yl.pdfdemo.day07.p1;

/**
 * @Author wfj
 * @Date 2021/5/6
 * @Description 线程间通信
 * @Version 1.0
 */

public class CommunicationTest {
    public static void main(String[] args) {
        /**
         * wait(),一旦执行此方法,当前线程就进入阻塞状态,并且释放同步监视器
         * notify(),一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的线程
         * notifyAll(),一旦执行此方法,就会唤醒所有被wait的线程
         *
         * 注意:wait,notify,notifyAll这三个方法只能使用在同步方法或同步代码块中
         * wait,notify,notifyAll这三个方法的调用者必须是同步方法或同步代码块中的同步监视器
         *
         * 面试题:sleep和wait的异同
         * 1.相同点:一旦执行此方法,都会使得当前线程进入阻塞状态
         * 2.不同点:1)两个方法声明的位置不同,Thread类中声明sleep(),Object类中声明wait()
         *           2)调用的要求不同,sleep()可以在任何场景下调用,而wait()只能在同步代码块或者同步方法中调用
         *           3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或者同步方法中,sleep()不会释放锁,wait()会释放锁
         *
         * 总结锁的释放与不释放
         * 释放锁的操作
         * 1.当前线程的同步方法,同步代码块执行结束
         * 2.当前线程在同步代码块,同步方法中,遇到break,return终止了代码块
         * 3.当前线程在同步代码块,同步方法中,遇到了未处理的ERROR/EXCEPTION,导致异常结束
         * 4.当前线程在同步代码块,同步方法中执行了线程对象·1的wait()方法,当前线程停止,并且释放锁
         *
         * 不释放锁的操作
         * 1.线程在执行同步代码块或者同步方法时,程序调用了sleep()/yield()方法暂停当前线程的执行
         * 2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁
         *    应尽量避免使用suspend()何resume()方法来控制线程
         */
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
    }
}

class Number implements Runnable {
    private int num = 1;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                //与wait()方法结合使用,唤醒线程
                notify();
                if (num <= 100) {
                    try {
                        //线程睡眠,但并没有释放锁
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + num);
                    num++;
                    try {
                        //线程进入阻塞状态,并且释放了锁
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }
}

8.售票练习
1)继承Thread的方式,加上使用同步代码块的方式来解决线程安全问题

package com.yl.pdfdemo.day07.p1;

/**
 * @Author wfj
 * @Date 2021/4/28
 * @Description 窗口卖票练习:继承Thread的方式
 * @Version 1.0
 */

public class WindowTest {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

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

class Window extends Thread {
    private static int ticket = 100;
    private static Object object = new Object();

    @Override
    public void run() {
        while(true) {
            //正确的写法
//            synchronized (object) {
            synchronized (Window.class){ // Class clazz = Window.class,Window.class只会加载一次,所以这个对象唯一
                //synchronized (this) { //错误的写法,此时的this代表w1,w2,w3三个对象
                if (ticket > 0) {
                    System.out.println(getName() + ":卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

2)实现Runnable接口的方式,加上使用同步代码块的方式来解决线程安全问题

package com.yl.pdfdemo.day07.p1;

/**
 * @Author wfj
 * @Date 2021/4/28
 * @Description 窗口卖票练习:实现Runnable接口的方式
 * @Version 1.0
 */

public class WindowTest1 {
    public static void main(String[] args) {
        /**
         * 以下还存在问题:在卖票过程中,还存在重票和错票===>出现了线程的安全问题
         * 问题出现的原因:当某个线程在操作车票时,操作尚未完成,其他线程也加入了进来,操作车票
         * 解决方案:当一个线程a操作车票时,其他线程不能参与进来,直到线程a操作完之后,其他线程才可以进来操作车票
         * 这种情况即使线程a出现了阻塞,也不能改变
         * java中,我们通过同步机制,来解决线程的安全问题
         *
         * 方式一:同步代码块
         *        synchronized(同步监视器) {
         *            //需要被同步的代码
         *        }
         *        说明:操作共享数据的代码,即为需要被同步的代码   注意:不能包含多了的代码,也不能包含少了的代码
         *        共享数据:多个线程共同操作的变量,比如以下的ticket
         *        同步监视器,俗称锁,任何一个类的对象都可以充当锁
         *        在实现Runnable接口创建多线程的方式中,我们可以考虑使用this来充当锁
         *
         *        要求:多个线程必须共用同一把锁
         *
         *
         * 方式二:同步方法
         *
         * 同步的方式解决了线程安全的问题--好处
         * 操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低--局限性
         */
        Window1 window1 = new Window1();
        Thread t1 = new Thread(window1);
        Thread t2 = new Thread(window1);
        Thread t3 = new Thread(window1);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

class Window1 implements Runnable {
    private int ticket = 100;
//    Object object = new Object();
//
    @Override
    public void run() {
        while (true) {
//            synchronized(object) {
            synchronized(this) { //此时的this代表Window1的唯一对象
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为"+ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}


3)使用同步方法来解决实现Runnable接口的线程问题

package com.yl.pdfdemo.day07.p1;

/**
 * @Author wfj
 * @Date 2021/4/28
 * @Description 使用同步方法处理继承Thread类的方式中线程的安全问题
 * @Version 1.0
 */

public class WindowTest3 {
    public static void main(String[] args) {
        /**
         * 同步方法总结:
         * 1.非静态的同步方法,同步监视器是this
         * 2.静态的同步方法,同步监视器是当前类本身
         */
        Window3 w1 = new Window3();
        Window3 w2 = new Window3();
        Window3 w3 = new Window3();
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

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

class Window3 extends Thread {
    private static int ticket = 100;
    private static Object object = new Object();

    @Override
    public void run() {
        while(true) {
            sale();
            }
        }

    private static synchronized void sale() { //同步监视器:Window3.class
//     private synchronized void sale() { //同步监视器:w1,w2,w3三个对象,此方式是错误的
         if (ticket > 0) {
             try {
                 Thread.sleep(100);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
             ticket--;
         }
     }
    }

4)使用同步方法处理继承Thread类的方式中线程的安全问题

package com.yl.pdfdemo.day07.p1;

/**
 * @Author wfj
 * @Date 2021/4/28
 * @Description 使用同步方法处理继承Thread类的方式中线程的安全问题
 * @Version 1.0
 */

public class WindowTest3 {
    public static void main(String[] args) {
        /**
         * 同步方法总结:
         * 1.非静态的同步方法,同步监视器是this
         * 2.静态的同步方法,同步监视器是当前类本身
         */
        Window3 w1 = new Window3();
        Window3 w2 = new Window3();
        Window3 w3 = new Window3();
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

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

class Window3 extends Thread {
    private static int ticket = 100;
    private static Object object = new Object();

    @Override
    public void run() {
        while(true) {
            sale();
            }
        }

    private static synchronized void sale() { //同步监视器:Window3.class
//     private synchronized void sale() { //同步监视器:w1,w2,w3三个对象,此方式是错误的
         if (ticket > 0) {
             try {
                 Thread.sleep(100);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
             ticket--;
         }
     }
    }

9.解决线程安全的方式,以上已经提及了两种,还有一种是jdk5.0以后出现的,Lock

package com.yl.pdfdemo.day07.p1;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author wfj
 * @Date 2021/5/6
 * @Description 解决线程安全问题的方式三:Lock锁 === jdk5.0新增
 * @Version 1.0
 */

public class LockTest {
    public static void main(String[] args) {
        /**
         * 面试题:synchronized与Lock的异同
         * 相同:二者都可以解决线程安全问题
         * 不同:synchronized机制在执行完同步代码块之后自动释放锁,
         *      lock需要手动的启动同步(lock()),同时结束同步也需要手动实现(unlock())
         *
         * 面试题:有几种方式解决线程安全问题
         *  1.synchronized
         *      1)同步代码块
         *      2)同步方法
         *
         *  2.Lock
         */
    Sale sale = new Sale();
    Thread t1 = new Thread(sale);
    Thread t2 = new Thread(sale);
   Thread t3 = new Thread(sale);
   t1.setName("窗口A");
   t2.setName("窗口B");
   t3.setName("窗口C");
   t1.start();
   t2.start();
   t3.start();
    }

}

class Sale implements Runnable{
    private int ticket = 100;
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                //调用锁定方法
                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 {
                //调用解锁方法,
                lock.unlock();
            }
        }
    }
}

10.何为死锁?

package com.yl.pdfdemo.day07.p1;

/**
 * @Author wfj
 * @Date 2021/5/6
 * @Description 线程的死锁问题
 * @Version 1.0
 */

public class DieThreaf {
    /**
     * 死锁:
     *      不同的线程分别占用对方需要的资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了死锁
     *      出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
     *
     *  解决方法:
     *      专门的算法、原则
     *      尽量减少同步资源的定义
     *      尽量避免嵌套同步
      */
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();
        //匿名对象
        new Thread(){
            @Override
            public void run() {
                synchronized (s1) {
                    s1.append("a");
                    s2.append("1");
                    try {
                        Thread.sleep(100);
                    } 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(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s1) {
                        s1.append("d");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

11.线程安全的单例模式(重点)

package com.yl.pdfdemo.day07.p1;

/**
 * @Author wfj
 * @Date 2021/4/29
 * @Description 线程安全的单例懒汉模式
 * @Version 1.0
 */

public class SafeSingleton {
    private SafeSingleton(){}

    private static SafeSingleton safeSingleton = null;

    //方式一
//    public static synchronized SafeSingleton getInstance() { //同步监视器:当前类对象
//        if (safeSingleton == null) {
//            safeSingleton = new SafeSingleton();
//        }
//        return safeSingleton;
//    }
    public static  SafeSingleton getInstance() {
        //方式二:效率稍差
//        synchronized (SafeSingleton.class) {
//            if (safeSingleton == null) {
//                safeSingleton = new SafeSingleton();
//            }
//            return safeSingleton;
//        }
        //方式三,效率较好
        if (safeSingleton == null) {
            synchronized (SafeSingleton.class) {
                if (safeSingleton == null) {
                    safeSingleton = new SafeSingleton();
                }
            }
        }
        return safeSingleton;
    }
}

12.经典的生产者消费者例题(重点)

package com.yl.pdfdemo.day07.p1;

/**
 * @Author wfj
 * @Date 2021/5/6
 * @Description 生产者消费者例题
 * @Version 1.0
 */

public class ProductTest {
    public static void main(String[] args) {
    Clerk clerk = new Clerk();
    Producer p1 = new Producer(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{
    private int count = 0;

    //生产产品
    public synchronized void produceProduct() {
        if (count < 20) {
            count++;
            System.out.println(Thread.currentThread().getName()+":开始生产第"+count+"个产品");
            //唤醒其他线程
            notify();
        } else {
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //消费产品
    public synchronized void consumeProduct() {
        if (count > 0) {
            System.out.println(Thread.currentThread().getName()+":开始消费第"+count+"个产品");
            count--;
            //唤醒其他线程
            notify();
        } else {
            try{
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//生产者
class Producer extends Thread{
    private Clerk clerk;
    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }
    public void run(){
        System.out.println(getName()+":开始生产产品.....");
        while (true) {
            clerk.produceProduct();
        }
    }
}

//消费者
class Consumer extends Thread{
    private Clerk clerk;
    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }
    public void run(){
        System.out.println(getName()+":开始消费产品.....");
        while (true) {
            clerk.consumeProduct();
        }
    }
}

13.创建Thread类的匿名子类

package com.yl.pdfdemo.day07.p1;

/**
 * @Author wfj
 * @Date 2021/4/28
 * @Description 线程练习
 * @Version 1.0
 */

public class ThreadDemo1 {
    public static void main(String[] args) {
//        MyThread1 t1 = new MyThread1();
//        t1.start();
//        MyThread2 t2 = new MyThread2();
//        t2.start();

        //创建Thread类的匿名子类
        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 != 0) {
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                }
            }
        }.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 != 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

14.银行账户练习(重点)

package com.yl.pdfdemo.day07.p1;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author wfj
 * @Date 2021/5/6
 * @Description 银行有一个账户,两个储户分别向同一个账户存3000元,每次存1000,存3000,每次存完,打印账户余额
 * @Version 1.0
 */

public class AccountTest {
    public static void main(String[] args) {
        Account account = new Account(0);
        Customer c1 = new Customer(account);
        Customer c2 = new Customer(account);
        c1.setName("小明");
        c2.setName("小红");
        c1.start();
        c2.start();
    }
}

class Account{
    private double balance;
    private ReentrantLock lock = new ReentrantLock();

    public Account(double balance) {
        this.balance = balance;
    }
    //存钱
    //方式一,同步方法
//    public synchronized void deposit(double money) {
    //方式二:Lock
    public void deposit(double money) {
        if (money > 0) {
            try {
                lock.lock();
                balance += money;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":存钱成功,账户余额为:"+balance);
            } finally {
                lock.unlock();
            }
        }
    }
}

class Customer extends Thread{
    private Account account;
    public Customer(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            account.deposit(1000);
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值