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;
    }
}
原文地址:https://www.cnblogs.com/mantishell/p/14427739.html