zoukankan      html  css  js  c++  java
  • java多线程的学习

    Java多线程编程

    Thread类实现多线程

    class MyThread extends Thread{
        private String title;
    
        public MyThread(String title){
            this.title = title;
        }
    
        @Override
        public void run(){
            for (int x = 0; x < 10; x ++){
                /*try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
                System.out.println(this.title + "运行,x=" + x);
            }
        }
    }
    
    public class ThreadDemo {
        public static void main(String[] args){
            new MyThread("线程A").start();
            new MyThread("线程B").start();
            new MyThread("线程C").start();
            /*MyThread mt = new MyThread("线程A");
            mt.start();
            mt.start();*/
        }
    }
    

    Runnable接口实现多线程

    class MyRunnable implements Runnable{
        private String title;
    
        public MyRunnable(String title){
            this.title = title;
        }
    
        @Override
        public void run(){
            for (int x = 0; x < 10; x ++){
                /*try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
                System.out.println(this.title + "运行,x=" + x);
            }
        }
    }
    
    public class ThreadRunnableDemo {
        public static void main(String[] args){
            /*new MyRunnable("线程A").start();
            new MyRunnable("线程B").start();
            new MyRunnable("线程C").start();*/
            //MyRunnable mt = new MyRunnable("线程A");
            //mt.start();
            Thread thread = new Thread(new MyRunnable("线程对象"));
            thread.start();
    
           /* for (int x = 0; x < 3; x++) {
                String title = "线程对象-" + x;
                new Thread(()->{
                    for (int y = 0; y < 10; y++) {
                        System.out.println(title + "运行,y=" + y);
                    }
                }).start();
            }*/
        }
    }
    

    Thread与Runnable的关系

    卖票:

    class MyThreadTicket implements Runnable{//线程的主体类
        private int ticket = 10;
    
        @Override
        public void run() {//线程的主体方法
            for (int x = 0; x < 100; x++) {
                if(this.ticket > 0){
                    System.out.println("卖票,ticket = " + this.ticket --);
                }
            }
        }
    }
    
    public class ThreadTicket {
    
        public static void main(String[] args){
            MyThreadTicket mt = new MyThreadTicket();
            new Thread(mt).start();//第一个线程启动
            new Thread(mt).start();//第二个线程启动
            new Thread(mt).start();//第三个线程启动
        }
    }
    

    Callable接口实现多线程

    	public static void main(String[] args) throws Exception{
            Thread thread = new Thread(()->{
                System.out.println("***72小时的疯狂我需要睡觉补充精力。");
                try {
                    Thread.sleep(1000);
                    System.out.println("***睡足了,继续工作。");
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                    System.out.println("敢打扰我睡觉。");
                }
            });
            thread.start();
            Thread.sleep(1000);
            if(!thread.isInterrupted()){
                System.out.println("我偷偷的打扰一下");
                thread.interrupt();
            }
        }
    

    多线程运行状态

    线程常用操作方法

    多线程的主要操作方法都在Thread类中定义

    线程的命名和取得

    多线程的运行状态是不确定的,所以要获取线程,就需要根据线程的名称来获取。Thread类提供有线程名称的处理。

    • 构造方法:public Thread(Runnable target, String name);
    • 设置名字:public final void setName(String name);
    • 取得名字:public final String getName();
    • 获取当前线程:public static Thread currentThread();
    class MyThreadName implements Runnable{
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
    
    public class ThreadName {
    
        public static void main(String[] args) throws Exception{
            MyThreadName mt = new MyThreadName();
            new Thread(mt, "线程A").start();
            new Thread(mt).start();
            new Thread(mt, "线程C").start();
        }
    }
    

    线程休眠

    Thread.sleep(1000);
    

    线程中断

    xxxThread.isInterrupted();
    xxxThread.interrupt();
    

    线程强制执行

    xxxThread.join()
    

    线程礼让

    Thread.yield();
    

    线程优先级

    Thread.yield();
    

    线程的同步与死锁

    在多线程的处理中,可以利用Runnable描述多个线程操作的资源,而Thread秒速每一个线程对象,于是当多个线程访问同一资源的时候如果处理不当,就会产生错误的操作。

    同步问题引出

    下面编写一个简单的卖票程序,将创建若干个线程对象实现卖票的处理操作。

    范例:实现卖票操作:

    class MyThreadTicket implements Runnable{//线程的主体类
        private int ticket = 10;
    
        @Override
        public void run() {//线程的主体方法
            while (true){
                if(this.ticket > 0){
                    System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket --);
                } else {
                    System.out.println("---------票已经卖光了");
                    break;
                }
            }
        }
    }
    
    public class ThreadTicket {
        public static void main(String[] args){
            MyThreadTicket mt = new MyThreadTicket();
            new Thread(mt, "票贩子A").start();//第一个线程启动
            new Thread(mt, "票贩子B").start();//第二个线程启动
            new Thread(mt, "票贩子C").start();//第三个线程启动
        }
    }
    
    

    此时的程序将创建3个线程对象,并且这三个线程对象将进行10张票的出售,此时的程序在进行卖票处理的时候并没有任何的问题(假象),下面可以模拟一下卖票中的延迟操作。

    class MyThreadTicket implements Runnable{//线程的主体类
        private int ticket = 10;
    
        @Override
        public void run() {//线程的主体方法
            while (true){
                if(this.ticket > 0){
                    try {
                        Thread.sleep(10);//模拟网络延迟
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket --);
                } else {
                    System.out.println("---------票已经卖光了");
                    break;
                }
            }
        }
    }
    
    public class ThreadTicket {
        public static void main(String[] args){
            MyThreadTicket mt = new MyThreadTicket();
            new Thread(mt, "票贩子A").start();//第一个线程启动
            new Thread(mt, "票贩子B").start();//第二个线程启动
            new Thread(mt, "票贩子C").start();//第三个线程启动
        }
    }
    

    加了短短的延迟后,问题出现了。实际这个问题一直都在。要解决这个问题,就必须使用同步,所谓的同步就是指多个操作在同一个时间段内只能有一个线程进行,其它的线程要等待此线程完成之后才可以继续执行。

    线程同步处理

    解决同步问题的关键是锁。锁是指当某一个 线程执行操作的时候,其它线程外面等待。如果要想在程序中实现锁的功能,就可以使用synchronized关键字来实现,利用此关键字可以定义同步方法或同步代码块,在同步代码块的操作里面的代码只允许一个线程执行。

    1、利用同步代码块进行处理:

    synchronized(同步对象){
        同步代码操作;
    }
    

    一般要进行同步对象处理的时候可以采用当前对象this进行同步。

    范例:利用同步代码块解决数据访问问题

    class MyThreadTicket implements Runnable{//线程的主体类
        private int ticket = 10;
    
        @Override
        public void run() {//线程的主体方法
            while (true){
                synchronized(this){//每次执行只允许一个线程执行
                    if(this.ticket > 0){
                        try {
                            Thread.sleep(10);//模拟网络延迟
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket --);
                    } else {
                        System.out.println("---------票已经卖光了");
                        break;
                    }
                }
            }
        }
    }
    
    public class ThreadTicket {
        public static void main(String[] args){
            MyThreadTicket mt = new MyThreadTicket();
            new Thread(mt, "票贩子A").start();//第一个线程启动
            new Thread(mt, "票贩子B").start();//第二个线程启动
            new Thread(mt, "票贩子C").start();//第三个线程启动
        }
    }
    

    加入同步处理后,程序的整体性能下降了。

    2、利用同步方法解决:只需要在方法的定义上使用synchronized关键字即可。

    class MyThreadTicket implements Runnable{//线程的主体类
        private int ticket = 10;
    
        public synchronized boolean sale(){
            if(this.ticket > 0){
                try {
                    Thread.sleep(10);//模拟网络延迟
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket --);
                return true;
            } else {
                System.out.println("---------票已经卖光了");
                return false;
            }
        }
        @Override
        public void run() {//线程的主体方法
            while (this.sale()){
            }
        }
    }
    
    public class ThreadTicket {
        public static void main(String[] args){
            MyThreadTicket mt = new MyThreadTicket();
            new Thread(mt, "票贩子A").start();//第一个线程启动
            new Thread(mt, "票贩子B").start();//第二个线程启动
            new Thread(mt, "票贩子C").start();//第三个线程启动
        }
    }
    

    在日后学习java类库的时候,系统中许多的类上使用的同步处理采用的都是同步方法。

    线程死锁

    死锁是在进行多线程同步的处理中有可能产生的一种问题,所谓的死锁指的是若干个线程彼此互相等待的状态。线面通过一个简单的代码来观察一下死锁的表现形式

    范例:死锁的展示

    public class DeadLock implements Runnable {
        private Jian jj = new Jian();
        private XiaoQiang xq = new XiaoQiang();
        @Override
        public void run() {
            jj.say(xq);
        }
    
        public DeadLock() {
            new Thread(this).start();
            xq.say(jj);
        }
    
        public static void main(String[] args){
            new DeadLock();
        }
    }
    
    class Jian {
        public synchronized void say(XiaoQiang xq) {
            System.out.println("贱説:此路是我开,要想从此过,留下买路财");
            xq.get();
        }
    
        public synchronized void get() {
            System.out.println("贱説:拿到了钱,此路让开");
        }
    }
    
    class XiaoQiang {
        public synchronized void say(Jian jj){
            System.out.println("小强説:想要留下买路财,先让我过去");
            jj.get();
        }
        public synchronized void get() {
            System.out.println("小强説:逃过去了");
        }
    }
    

    现在死锁造成的主要原因是,彼此都在相互等待着,等待着对方先让出资源。死锁实际是开发中出现的不确定的状态,有的时候如果代码处理不当,则会不定期出现死锁。这是属于正常开发中的调试问题。

    若干个线程访问同一资源时一定要进行同步处理,而过多的同步会造成死锁。

    综合实战: “生产者-消费者”模型

    在多线程的开发过程中最为著名的案例就是生产者与消费者操作,该操作的主要流程如下:

    • 生产者负责内容的生产;
    • 每当生产者完成一项完整的信息之后消费者要从这里取走信息;
    • 如果生产者没有生产完成则消费者需要等待它生产完成,如果消费者还没有对信息进行消费,则生产者应该等待消费者完成消费后,再继续生产。

    生产者与消费者基本程序模型

    可以将生产者与消费者定义成两个独立的线程类对象,但是对于现在生产的数据,可以使用如下的流程:

    • 数据一:title = 张三、content = 帅气;
    • 数据二:title = 李四、content = 漂亮;

    既然生产者和消费者是两个独立的线程,那么这两个独立的线程之间就需要一个数据的保存集中点,那么可以单独定义一个Message类实现数据的保存。

    ![image-20210222210007226](Screenshot from 2021-02-22 21-02-29.png)

    范例:实现数据的基本结构

    class Producer implements Runnable {
        private Message msg;
        public Producer(Message msg) {
            this.msg = msg;
        }
        @Override
        public void run() {
            for (int x = 0; x < 100; x++) {
                if (x % 2 == 0) {
                    this.msg.setTitle("张三");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    this.msg.setContent("帅气");
                } else {
                    this.msg.setTitle("李四");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    this.msg.setContent("漂亮");
                }
            }
        }
    }
    
    class Consumer implements Runnable {
        private Message msg;
        public Consumer(Message msg) {
            this.msg = msg;
        }
        @Override
        public void run() {
            for (int x = 0; x < 100; x++) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(this.msg.getTitle() + "   -   " + this.msg.getContent());
            }
        }
    }
    class Message {
        private String title;
        private String content;
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public String getContent() {
            return content;
        }
    
        public void setContent(String content) {
            this.content = content;
        }
    }
    public class ThreadMessage {
        public static void main(String[] args) throws Exception {
            Message msg = new Message();
            new Thread(new Producer(msg)).start(); //启动生产者线程
            new Thread(new Consumer(msg)).start(); //启动消费者线程
        }
    }
    
    

    通过这个代码执行发现有两个问题:

    • 数据不同步了;
    • 出现重复生产和重复取出

    解决生产者-消费者同步问题

    首先解决数据的额同步问题,最简单的方式是使用synchronized关键字定义同步代码块或同步方法。于是这个时候对于同步的处理就可以直接在Message类中完成。

    范例: 解决同步操作

    class Producer implements Runnable {
        private Message msg;
        public Producer(Message msg) {
            this.msg = msg;
        }
        @Override
        public void run() {
            for (int x = 0; x < 100; x++) {
                if (x % 2 == 0) {
                    this.msg.set("张三", "帅气");
                } else {
                    this.msg.set("李四", "漂亮");
                }
            }
        }
    }
    
    class Consumer implements Runnable {
        private Message msg;
        public Consumer(Message msg) {
            this.msg = msg;
        }
        @Override
        public void run() {
            for (int x = 0; x < 100; x++) {
                System.out.println(this.msg.get());
            }
        }
    }
    class Message {
        private String title;
        private String content;
    
        public synchronized void set(String title, String content) {
            this.title = title;
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.content = content;
        }
        public synchronized String get() {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return this.title + "  -  " + this.content;
        }
    }
    public class ThreadMessage {
        public static void main(String[] args) throws Exception {
            Message msg = new Message();
            new Thread(new Producer(msg)).start(); //启动生产者线程
            new Thread(new Consumer(msg)).start(); //启动消费者线程
        }
    }
    

    在进行同步处理的时候可定需要有个同步的处理对象,那么此时肯定要将同步操作交由Message类处理是最合适的。这个时候数据可以保持一致了,但是重复问题依然存在。

    利用Object类解决重复操作

    如果想解决生产者和消费者的问题,那么最好的解决方案就是使用等待与唤醒机制。对于等待和唤醒机制,主要依靠的是Object类中提供的方法处理的。

    • 等待机制:
      • 死等:public final void wait() throws InterruptedException;
      • 设置等待时间:public final void wait(long timeout) throws InterruptedException;
      • 设置等待时间:public final void wait(long timeout, int nanos) throws InterruptedException;
    • 唤醒第一个等待线程: public final void notify();
    • 唤醒全部等待线程:public final void notifyAll();

    如果此时有若干个等待线程的话,notify()表示唤醒第一个等待的,而其它的线程继续等待。而notifyAll()会唤醒所有等待线程,哪个线程的优先级高,就有可能先执行。

    对于当前的问题的主要解决方案,应该通过Message类来完成处理。

    范例: 修改Message类

    class Message {
        private String title;
        private String content;
        private boolean flag = true; //表示生产或消费的形式
        // flag = true;允许生产,但不允许消费
        // flag = false;允许消费,但不允许生产
        public synchronized void set(String title, String content) {
            if(!this.flag){ //无法进行生产,应该等待被消费
                try {
                    super.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.title = title;
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.content = content;
            this.flag = false;//已经生产过了
            super.notify();//唤醒等待的线程
        }
        public synchronized String get() {
            if(this.flag){
                try {
                    super.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                return this.title + "  -  " + this.content;
            }finally {
                this.flag = true;//继续生产
                super.notify();//唤醒等待线程
            }
        }
    }
    class Producer implements Runnable {
        private Message msg;
        public Producer(Message msg) {
            this.msg = msg;
        }
        @Override
        public void run() {
            for (int x = 0; x < 100; x++) {
                if (x % 2 == 0) {
                    this.msg.set("张三", "帅气");
                } else {
                    this.msg.set("李四", "漂亮");
                }
            }
        }
    }
    
    class Consumer implements Runnable {
        private Message msg;
        public Consumer(Message msg) {
            this.msg = msg;
        }
        @Override
        public void run() {
            for (int x = 0; x < 100; x++) {
                System.out.println(this.msg.get());
            }
        }
    }
    
    public class ThreadMessage {
        public static void main(String[] args) throws Exception {
            Message msg = new Message();
            new Thread(new Producer(msg)).start(); //启动生产者线程
            new Thread(new Consumer(msg)).start(); //启动消费者线程
        }
    }
    

    这种处理形式就是进行多线程开发过程中最原始的处理方案,整个的等待、同步、唤醒机制都由开发者自行通过原生的代码实现控制。

    多线程的深入话题

    优雅的停止线程

    在多线程的操作中如果要启动多线程肯定使用的是Thread类中的start()方法,而如果对于多线程需要进行停止处理,Thread类原本提供有stop()方法。但是对于这个方法从JDK1.2版本开始,就已经将其废除了。而且一直到现在也不建议出现在你的代码中。还有几个方法也过时了:

    • 停止多线程:public void stop();
    • 销毁多线程:public void destroy();
    • 挂起多线程:public final void suspend();
    • 恢复挂起的线程执行:public final void resume();

    之所以废除这些方法,主要是因为这些方法有可能导致线程的死锁。如果想实现线程的停止,需要通过一种柔和的方式来进行

    范例:实现线程的柔和停止

    	public static boolean flag = true;
        public static void main(String[] args) throws Exception {
            new Thread(()->{
                long num = 0;
                while (flag) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在运行、num= " + num ++ );
                }
            }, "执行线程").start();
            Thread.sleep(200);
            flag  = false;
        }
    

    万一现在有其它的线程在控制这个flag的内容,那么这个时候对于线程的停止也不是立刻停止的。

    后台守护线程

    在Thread类里面提供有如下的守护线程的操作方法:

    • 设置为守护线程:public final void setDaemon(boolean on);
    • 判断是否为守护线程:public final boolean isDaemon();

    范例: 使用守护线程

    public static void main(String[] args) throws Exception {
            Thread userThread = new Thread(() -> {
                for (int x = 0; x < 10; x++) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在运行、x = " + x);
                }
            }, "用户线程");
            Thread daemonThread = new Thread(() -> {
                for (int x = 0; x < Integer.MAX_VALUE; x++) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在运行、x = " + x);
                }
            }, "守护线程");
            daemonThread.setDaemon(true); //设置守护线程
            userThread.start();
            daemonThread.start();
        }
    

    可以发现所有的守护线程都是围绕在用户线程的周围。如果程序执行完毕了,守护线程也就消失了,在JVM里最大的守护线程就是GC线程。

    程序执行中GC线程会一直存在,如果程序执行完毕,GC线程也将消失。

    volatile关键字

    在多线程的定义中,volatile关键字主要是在属性定义上使用,表示此属性为直接数据操作,而不进行副本的拷贝处理。

    ![](Screenshot from 2021-02-22 22-46-51.png)

    class MyThreadTicket implements Runnable{
        private volatile int ticket = 10;
        @Override
        public void run() {
            synchronized (this) {
                while (this.ticket > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
                }
            }
        }
    }
    
    public class ThreadTicket {
        public static void main(String[] args){
            MyThreadTicket mt = new MyThreadTicket();
            new Thread(mt, "票贩子A").start();
            new Thread(mt, "票贩子B").start();
            new Thread(mt, "票贩子C").start();
        }
    }
    

    面试题:请解释volatile与synchronized的区别?

    • volatile主要用在属性上,而synchronized用在代码块和方法上;
    • volatile无法描述同步处理,用来直接操作内存,避免的拷贝副本。而synchronized是实现同步的。

    多线程综合案例

    数字加减

    public class ThreadMath {
        public static void main(String[] args) {
            Resource res = new Resource();
            AddThread at = new AddThread(res);
            SubThread st = new SubThread(res);
            new Thread(at, "加法线程 - A").start();
            new Thread(at, "加法线程 - B").start();
            new Thread(st, "减法线程 - X").start();
            new Thread(st, "减法线程 - Y").start();
        }
    }
    
    class AddThread implements Runnable {
        private Resource resource;
        public AddThread(Resource resource){
            this.resource = resource;
        }
        @Override
        public void run() {
            for (int x = 0; x < 50; x++) {
                try {
                    this.resource.add();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    class SubThread implements Runnable {
        private Resource resource;
        public SubThread(Resource resource){
            this.resource = resource;
        }
        @Override
        public void run() {
            for (int x = 0; x < 50; x++) {
                try {
                    this.resource.sub();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    class Resource {//定义一个操作资源的类
        private int num = 0;//这个要进行加减操作的数据
        private boolean flag = true; //加减的切换
        //flag = true;表示可以进行加法操作,但是不能进行减法操作
        //flag = false;表示可以进行减法操作,但是不能进行加法操作
        public synchronized void add() throws Exception {
            if(!this.flag){
                super.wait();
            }
            Thread.sleep(100);
            this.num ++;
            System.out.println("【加法操作 - "+ Thread.currentThread().getName() +"】num=" + this.num);
            this.flag = false;
            super.notifyAll();//唤醒全部等待线程
        }
        public synchronized void sub() throws Exception {
            if(this.flag) {
                super.wait();
            }
            Thread.sleep(200);
            this.num --;
            System.out.println("【减法操作 - "+ Thread.currentThread().getName() +"】num=" + this.num);
            this.flag = true;
            super.notifyAll();
        }
    }
    

    这个是经典的多线程开发操作

    生产电脑

    设计一个生产电脑和搬运电脑的类,要求生产出一台电脑就搬走一台电脑,如果没有新的电脑生产出来,则搬运工要等待新电脑生成;如果生产出的电脑没有搬走,则要等待电脑搬走之后再生产,并统计出生产的电脑数量。

    class Producer1 implements Runnable {
        private Resource1 resource;
        public Producer1(Resource1 resource){
            this.resource = resource;
        }
    
        @Override
        public void run() {
            for (int x = 0; x < 50; x++) {
                try {
                    this.resource.make();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    class Consumer1 implements Runnable {
        private Resource1 resource;
        public Consumer1(Resource1 resource){
            this.resource = resource;
        }
    
        @Override
        public void run() {
            for (int x = 0; x < 50; x++) {
                try {
                    this.resource.get();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    class Computer {
        private static int count = 0;
        private String name;
        private double price;
        public Computer(String name, double price){
            this.name = name;
            this.price = price;
            count ++;
        }
        public String toString() {
            return "【第"+ count +"台电脑】-名字:" + this.name  + "、价格:" + this.price;
        }
    }
    class Resource1 {
        private Computer computer;
        private boolean flag = true;
        public synchronized void make() throws Exception {
            if(this.computer != null){ //已经生产了
                super.wait();
            }
            Thread.sleep(100);
            this.computer = new Computer("Dell电脑", 5000.0);
            System.out.println("【生产电脑】" + this.computer);
            super.notifyAll();
        }
        public synchronized void get() throws Exception {
            if(this.computer == null){
                super.wait();
            }
            Thread.sleep(10);
            System.out.println("【取走电脑】" + this.computer);
            this.computer = null;//已经取走了
            super.notifyAll();
        }
    }
    public class ThreadComputer {
        public static void main(String[] args){
            Resource1 res = new Resource1();
            new Thread(new Producer1(res)).start();
            new Thread(new Consumer1(res)).start();
        }
    }
    

    竞争抢答

    实现一个竞拍抢答程序:要求设置三个抢答者(三个线程),而后同时发出抢答指令,并给出抢答结果提示。

    import java.util.concurrent.Callable;
    import java.util.concurrent.FutureTask;
    
    class MyVie implements Callable<String> {
        private boolean flag = false;//抢答处理
        @Override
        public String call() throws Exception {
            synchronized (this){
                if(!this.flag){
                    this.flag = true;
                    return Thread.currentThread().getName() + "抢答成功";
                }else {
                    return Thread.currentThread().getName() + "抢答失败";
                }
            }
        }
    }
    public class ThreadVie {
        public static void main(String[] args) throws Exception {
            MyVie vie = new MyVie();
            FutureTask<String> taskA = new FutureTask<>(vie);
            FutureTask<String> taskB = new FutureTask<>(vie);
            FutureTask<String> taskC = new FutureTask<>(vie);
            new Thread(taskA, "竞赛者A").start();
            new Thread(taskB, "竞赛者B").start();
            new Thread(taskC, "竞赛者B").start();
            System.out.println(taskA.get());
            System.out.println(taskB.get());
            System.out.println(taskC.get());
        }
    }
    

    Java基础类库

    String类是我们想用的类

    • 每一个字符串的常量都属于String类的匿名对象,并且不可更改;
    • String有两个常量池:静态常量池、运行时常量池;
    • String类对象实例化建议使用直接赋值的形式完成,这样可以直接将对象保存到对象池之中,方便下次使用。

    StringBuffer类是线程安全的,而StringBuilder是非线程安全的。

    • 追加数据:public StringBuffer append(数据类型 a);

    • 插入数据:public StringBuffer insert(int offset, 数据类型 b);

    • 删除指定范围的数据:public StringBuffer delete(int start, int end);

    • 字符串内容翻转:public StringBuffer reverse();

    CharSequence

    它是描述字符串结构的接口

    • 获取指定索引字符:public char charAt(int index);
    • 获取字符串长度:public int length();
    • 截取部分字符串:public CharSequence subSequence(int start, int end);

    AutoCloseable接口

    Runtime类

    Runtime是描述运行时的状态,唯一一个与JVM运行状态有关的类,且默认提供一个该类的实例化对象。

    • 获取最大可用内存空间:public long maxMemory();默认的配置为本机系统内存的1/4
    • 获取可用内存空间:public long totalMemory();默认的配置是为本机系统内存的1/64
    • 获取空闲内存空间:public long freeMemory();
    • 手工进行GCC处理:public void gc();

    System类

    • 数组拷贝:public static void arraycopy(Object src, int srcPos,Object dest, int destPos, int length);
    • 获取当前的日期时间数值:public static long currentTimeMillis();
    • 进行垃圾回收:public static void gc();

    Cleaner 类

    import java.lang.ref.Cleaner;
    
    class Member implements Runnable {
        public Member() {
            System.out.println("【构造方法】");
        }
    
        @Override
        public void run() {//执行清除的时候执行的操作
            System.out.println("相当于C++中的【析构方法】");
        }
    }
    
    class MemberCleaning implements AutoCloseable {
        private static final Cleaner cleaner = Cleaner.create();//创建清除处理
        private Member member;
        private Cleaner.Cleanable cleanable;
    
        public MemberCleaning() {
            this.member = new Member();
            this.cleanable = this.cleaner.register(this, this.member);//注册使用的对象
        }
    
        @Override
        public void close() throws Exception {
            this.cleanable.clean();//启动多线程
        }
    }
    public class MyCleaner {
        public static void main(String[] args){
            try(MemberCleaning mc = new MemberCleaning()) {
    
            }catch (Exception e){}
        }
    }
    

    在新一代的清除回收处理的过程中,更多的情况下考虑的是多线程的使用,即:为了防止有可能造成的延迟处理,所以许多对象回收前的处理都是单独通过一个线程完成的。

    日期操作

    SimpleDateFormat

    	public static void main(String[] args) {
            Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
            String str = sdf.format(date);
            System.out.println(str);
        }
    

    2021-02-28 10:12:20.647

    字符串转date

    		String birth = "2020-02-12";
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Date date = sdf.parse(birth);
            System.out.println(date);
    

    正则

    常用正则标记

    1. 【数量:单个】字符匹配

      • 任意字符:表示由任意字符组成;

      • \:匹配“”;

      • :匹配换行;

      • :匹配制表符;

    2. 【数量:单个】字符集

      String str = "a";
      String regex = "[a-zA-Z]";
      System.out.println(str.matches(regex));
      
    3. 【数量:单个】简化字符集

      • .:表示任意一个字符
      • d:等价于0-9
      • D:等价于[^0-9]
      • s:匹配任意的一位空格,可能是空格、换行、制表符
      • S:匹配任意的非空格
      • w:匹配字母、数字、下划线,等价于[a-zA-Z_0-9]
      • W:匹配非字母、数字、下划线,等价于[[^^a-zA-Z_0-9]]
    4. 边界匹配

      • ^:匹配边界开始
      • $:匹配边界结束
    5. 数量表示,默认情况下,只有加了数量才可以匹配多位字符

      • 表达式?:可以出现0次或1次
      • 表达式*:可以出现0次、1次或多次
      • 表达式+:可以出现1次或多次
      • 表达式{n}:表达式的长度正好为n次
      • 表达式{n,}:表达式的长度为n次以上
      • 表达式{n,m}:表达式的长度为n~m次
    6. 逻辑表达式:可以连续多个正则

      • 表示式X表达式Y:X表达式之后紧跟上Y表达式
      • 表达式X|或表达式Y:有一个满足表达式即可
      • (表达式):为表达式设置一个整体的描述,可以为整体描述设置数量单位。

    String类对正则的支持

    No 方法名称 类型 描述
    1 public boolean matches(String regex); 普通 将指定字符串进行正则判断
    2 public String replaceAll(String regex, String replacement); 普通 替换全部
    3 public String replaceFirst(String regex, String replacement); 普通 替换全部
    4 public String[] split(String regex); 普通 正则拆分
    5 public String[] split(String regex, int limit); 普通 正则拆分

    java.util.regex包支持

    		String str = "INSET INTO dept(deptno,dname,loc) VALUES(#{deptno}, #{dname}, #{loc})";
            String regex = "#\{\w+\}";
            Pattern pat  = Pattern.compile(regex);
            Matcher mat = pat.matcher(str);
            while(mat.find()){
                System.out.println(mat.group(0).replaceAll("#|\{|\}",""));
            }
    

    ThreadLocal

    public class MyThreadLocal {
        public static void main(String[] args){
            new Thread(()->{
                Message msg = new Message();
                msg.setInfo("第一个线程");
                Channel.setMessage(msg);
                Channel.send();
            }, "消息发送A").start();
            new Thread(()->{
                Message msg = new Message();
                msg.setInfo("第二个线程");
                Channel.setMessage(msg);
                Channel.send();
            }, "消息发送B").start();
            new Thread(()->{
                Message msg = new Message();
                msg.setInfo("第三个线程");
                Channel.setMessage(msg);
                Channel.send();
            }, "消息发送C").start();
        }
    }
    
    class Channel {
        private static final ThreadLocal<Message> THREADLOCAL = new ThreadLocal<>();
        private Channel(){};
        public static void setMessage(Message m){
            THREADLOCAL.set(m);
        }
        public static void send(){
            System.out.println("【"+ Thread.currentThread().getName() +"、消息发送】" + THREADLOCAL.get().getInfo());
        }
    }
    class Message {
        private String info;
        public void setInfo(String info){
            this.info = info;
        }
        public String getInfo(){
            return info;
        }
    }
    
  • 相关阅读:
    Spring配置文件中关于Shiro的配置
    关于Realm的源码分析
    配置SpringMVC配置
    Shiro的登录验证及授权多Realm情况【基于SpringMVC框架下】
    关于Nginx配置说明
    Hibernate中No row with the given identifier exists问题的原因及解决
    关于Shiro的角色授权
    关于Shiro的认证策略
    关于hibernate的cache总结及并发问题
    Shiro多Realm数据交互实现过程
  • 原文地址:https://www.cnblogs.com/mantishell/p/14427739.html
Copyright © 2011-2022 走看看