多线程进阶学习管理

Thread与Runnable的关系

多线程进阶学习管理_第1张图片
在多线程的设计中,使用了代理设计模式的结构,用户自动逸的线程主体只是负责核心功能的实现,而所有的辅助实现全部交由Thread类来处理。

在进行 Thread 启动多线程的时候调用的是 start() 方法,而后找到的是 run()方法。

但通过Thread类的构造方法传递了一个Runnable接口对象的时候,那么该接口对象将被Thread类中的target属性保存,,在start()方法执行的时候会调用Thread类中的run()方法,而这个run()方法去调用Runnable接口子类被覆盖写过的run()方法

多线程开发的本质是多个线程对于同一资源的抢占,Thread主要是描述线程,资源的描述是Runnable完成的。

多线程开发

多线程进阶学习管理_第2张图片

Callable接口实现多线程

从最传统的开发来讲如果要进行多线程的实现肯定依靠的就是 Runnable。

但是 Runnable 接口有一个缺点:当线程执行完毕之后后无法获取一个返回值,所以从 JDK 1.5 之后就提出了一个新的线程实现接口: java.util.concurrent.Collable 接口。

一、接口定义

@FunctionalInterface.
public interface callable {
	public V call() throws Exception ;
}

Callable 定义的时候可以设置一个泛型,此泛型的类型就是返回数据的类型,这样的好处是可以避免向下转型所带来的安全隐患。

多线程进阶学习管理_第3张图片

二、范例:使用Callable实现多线程

class MyThread implements callable {

	@override
	public string call() throws Exception {
		for (int x = 0 ; x < 10 ; x ++) {
			system.out.println("*********线程执行、× = " +x);
		}
		return "线程执行完毕。";
	}
}

public class ThreadDemo {
	public static void main(String[] args) throws Exception {
		FutureTask task = new FutureTask<>(new MyThread()) ;
		new Thread(task).start();
		System.out.println("【线程返回数据】" + task.get());
	}
}

三、面试题:Runnable和Callable区别

  1. Runnable 是在 JDK1.0的时候提出的多线程的实现接口,而 Callable 是在 JDK 1.5之后提出的;
  2. java.lang.Runnable 接口之中只提供有一个 run()方法,并且没有返回值;
  3. java.util.concurrent.Callable 接口提供有 call()方法,可以有返回值;

多线程运行状态

对于多线程的开发而言,编写程序的过程之中总是按照:

定义线程主体类,而后通过Thread类进行线程,但是并不意味着你调用了start()方法,线程就已经开始运行了,因为整体的线程处理有自己的一套运行的状态。

多线程进阶学习管理_第4张图片

  1. 任何一个线程的对象都应该使用Thread类进行封装,所以线程的启动使用的是start(),但是启动的时候实际上若干个线程都将进入到一种就绪状态,现在并没有执行;
  2. 进入到就绪状态之后就需要等待进行资源的调度,当某一个线程调度成功之后侧进入到运行状态(run()方法),但是所有的线程不可能一直持续执行下去,中间需要产生一些暂停的状态,例如:某个线程执行一段时间之后就需要让出资源;而后这个线程就进入到阻塞状态随后重新回归到就绪状态;
  3. 当run()方法执行完毕之后,实际上该线程的主要任务也就结束了,那么此时就可以直接进入到停止状态。

线程状态:New、Runnable、Blocked、Wait、timed Waiting、Terminated

线程命名

多线程的运行状态是不确定的,那么在程序开发之中为了可以获取到一些需要使用的线程就只能依靠线程的名字来进行操作。

所以线程的名字是一个至关重要的概念,这样在Thread类之中就提供有线程名称的处理;

  • 构造方法:public Thread(Runnable target,String name);
  • 设置名字:public final void setName(String name);
  • 取得名字:public final String getName();
public class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
class ThreadDemo {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        new Thread(mt, "线程A").start();
        new Thread(mt).start();
        new Thread(mt, "线程B").start();
    }
}
// 结果:
线程A
Thread-0
线程B

当开发者为线程设置名字的时候,而如果没有设置名字,则会自动生成一个不重复的名字,这种自动的属性命名主要是依靠了static属性完成的,在Thread类里面定义了如下操作:

private static int threadInitNumber;

private static synchronized int nextThreadNum(){
  	return threadInitNumber++;
}

如果使用run方法时结果为:

public class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
class ThreadDemo {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        new Thread(mt, "线程对象").start();
        mt.run();
    }
}
// 结果:
main
线程对象

所以可以得出一个结论:主方法也是一个线程。

那么现在问题来了,所有的线程都是在进程上的划分,那么进程在哪里?

每当使用Java命令执行程序的时候就表示启动了一个JVM的进程,一台电脑上可以同时启动若干个JVM进程所以每一个JVM的进程都会有各自的线程。

image

在任何的开发之中,主线程可以创建若干个子线程,创建子线程的目的是可以将一些复杂逻辑或者比较耗时的逻辑交给子线程处理;

范例:子线程的处理

public class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
class ThreadDemo {
    public static void main(String[] args) {
        System.out.println("1.执行操作任务1.");
        new Thread(() -> {
            int temp = 0;
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                temp++;
            }
            System.out.println("n.执行操作任务3.");
        }).start();
        System.out.println("2.执行操作任务2.");
        System.out.println("n.执行操作任务N.");
    }
}
// 结果
1.执行操作任务1.
2.执行操作任务2.
n.执行操作任务N.
n.执行操作任务3.

线程休眠(sleep)

如果现在希望某一个线程可以暂缓执行,那么可以使用休眠的处理。

在 Thread 类之中定义的休眠的方法如下:

休眠1:

public static void sleep(long millis)throws InterruptedException;

休眠2:

public static void sleep(long mills,int nanos)throwsInterruptedException;

在进行休眠的时候有可能会产生中断异常“InterruptedException”,中断异常属于Exception 的子类,所以证明该异常必须进行休眠处理。

范例:观察休眠处理

class ThreadDemo {
    public static void main(String[] args) {
        System.out.println("1.执行操作任务1.");
        new Thread(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName());
        }, "线程对象").start();
        System.out.println("2.执行操作任务2.");
        System.out.println("n.执行操作任务N.");

    }
}
// 结果:
1.执行操作任务1.
2.执行操作任务2.
n.执行操作任务N.
线程对象 // 两秒之后才输出此结果

暂缓执行之后执行慢了,休眠时间一到程序马上回复继续执行。

休眠的主要特点是可以自动实现线程的唤醒,以继续进行后续的处理。但是需要注意的是,如果现在你有多个线程对象,那么休眠也是有先后顺序的。

范例:产生多个线程对象进行休眠处理

此时五个线程对象,并且这五个线程对象执行的方法体是相同的。

此时从程序执行的感觉来讲,就像是若干个线程一起进行了休眠然后一起进行了自动唤醒。

线程中断

在上一部分发现线程的休眠里面提供有一个中断异常,实际上就证明线程的休眠是可以被打断的,而这种打断肯定是由其他线程完成的,在Thread类里面提供有这种中断执行的处理方法:

  • 判断线程是否被中断:public boolean isInterrupted();
  • 中断线程执行:public void interrupt();
class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("1.执行操作任务1.");
        Thread thread = new Thread(() -> {
            System.out.println("需要休眠10秒");
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("休眠结束,霍霍别人");
        }, "线程对象");
        thread.start();
        Thread.sleep(5000);
        thread.interrupt(); // 此方法会抛出异常
    }
}
// 结果:
1.执行操作任务1.
需要休眠10Exception in thread "线程对象" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted // 5秒之后展示
	at com.example.demo.ThreadDemo.lambda$main$0(MyThread.java:18)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.example.demo.ThreadDemo.lambda$main$0(MyThread.java:16)
	... 1 more

所有正在执行的线程都是可以被中断的中断线程必须进行异常的处理。

线程的强制执行(join)

以上线程实例都是没有强制执行的程序

接下来观察一个强制执行的程序

class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread threadMain = Thread.currentThread();
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    if (i == 3) {
                        threadMain.join();
                    }
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "执行:i = " + i);
            }
        }, "还在玩转的线程");
        thread.start();
        for (int i = 0; i < 5; i++) {
            Thread.sleep(3000);
            System.out.println("【霸道总裁似的main线程】number = " + i);
        }
    }
}
// 结果:
还在玩转的线程执行:i = 0
还在玩转的线程执行:i = 1
还在玩转的线程执行:i = 2
【霸道总裁似的main线程】number = 0
【霸道总裁似的main线程】number = 1
【霸道总裁似的main线程】number = 2
【霸道总裁似的main线程】number = 3
【霸道总裁似的main线程】number = 4
还在玩转的线程执行:i = 3
还在玩转的线程执行:i = 4

在进行线程强制执行的时候一定要获取强制执行对象之后才可以执行jion()调用。

线程的礼让(yield)

线程的礼让指的是将资源让出去让别的线程先执行。

线程的礼让可以使用Thread中提供的方法:

  • 礼让:public static void yield()

范例:使用礼让操作

class ThreadDemo {
    public static void main(String[] args) {
        Runnable run = () -> {
            for (int x = 0; x < 10; x++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                if (x == 2) {
                    Thread.yield();
                }
                System.out.println(Thread.currentThread().getName() + "执行。" + x);
            }
        };
        Runnable run1 = () -> {
            for (int x = 0; x < 10; x++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "执行。" + x);
            }
        };
        Thread threadA = new Thread(run, "线程A");
        Thread threadB = new Thread(run1, "线程B");
        Thread threadC = new Thread(run1, "线程C");
        threadA.start();
        threadB.start();
        threadC.start();
    }
// 结果:
线程B执行。0
线程C执行。0
线程A执行。0
线程B执行。1
线程C执行。1
线程A执行。1
线程B执行。2
线程C执行。2
线程A执行。2
线程C执行。3
线程B执行。3
线程A执行。3
线程C执行。4
线程B执行。4
线程A执行。4
线程C执行。5
线程B执行。5
线程A执行。5
线程C执行。6
线程B执行。6
线程A执行。6
线程B执行。7
线程C执行。7
线程A执行。7
线程C执行。8
线程B执行。8
线程A执行。8
线程C执行。9
线程A执行。9
线程B执行。9

礼让执行的时候每一次调用yield()方法都只会礼让一次当前的概念。(主要是让出cpu去执行其他的)

从上面结果可以看出来A线程的每次循环基本上都是在最后执行的

线程优先级

从理论上来讲线程的优先级越高越有可能先执行(也越有可能先抢占到资源)。

在Thread类里面针对于优先级的操作提供有如下的两个处理方法:

  • 设置优先级:public finial void setPriority(int newPriority);
  • 获取优先级:public final int getPriority()

在进行优先级定义的时候都是通过int 型的数字来完成的,而对于此数字的选择在Thread类里面就定义有三个常量:

  • 最高优先级:public static final int MAX PRIORITY,10;
  • 中等优先级:public static final int NORM PRIORITY,5;
  • 最低优先级:public static final int MIN PRIORITY,1;

范例:观察优先级

class ThreadDemo {
    public static void main(String[] args) {
        System.out.println("【帅气的主线程优先级】:"+Thread.currentThread().getPriority());
        Runnable run = () -> {
            for (int x = 0; x < 10; x++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "执行。" + x);
            }
        };
        Thread threadA = new Thread(run, "线程A");
        Thread threadB = new Thread(run, "线程B");
        Thread threadC = new Thread(run, "线程C");
        threadA.setPriority(Thread.MIN_PRIORITY);
        threadB.setPriority(Thread.MIN_PRIORITY);
        threadC.setPriority(Thread.MAX_PRIORITY);
        threadA.start();
        threadB.start();
        threadC.start();
    }
}
// 结果:
【帅气的主线程优先级】:5
线程C执行。0
线程A执行。0
线程B执行。0
线程C执行。1
线程A执行。1
线程B执行。1
线程C执行。2
线程A执行。2
线程B执行。2
线程C执行。3
线程B执行。3
线程A执行。3
线程C执行。4
线程B执行。4
线程A执行。4
线程C执行。5
线程A执行。5
线程B执行。5
线程C执行。6
线程A执行。6
线程B执行。6
线程C执行。7
线程A执行。7
线程B执行。7
线程C执行。8
线程B执行。8
线程A执行。8
线程C执行。9
线程B执行。9
线程A执行。9

设置完成之后C的执行几率提高了,并不是一定先执行

主线程是中等优先级

线程同步

同步问题引出

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

上代码~

public class MyThread 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);
                ticket--;
            } else {
                System.out.println("郑州的票已经买完了。。");
                break;
            }
        }
    }
}

class ThreadDemo {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        new Thread(mt, "票贩子1").start();
        new Thread(mt, "票贩子2").start();
        new Thread(mt, "票贩子3").start();
    }
}
// 结果
票贩子2卖票,ticket = 10
票贩子3卖票,ticket = 10
票贩子3卖票,ticket = 8
票贩子3卖票,ticket = 7
票贩子1卖票,ticket = 10
票贩子3卖票,ticket = 6
票贩子3卖票,ticket = 4
票贩子3卖票,ticket = 3
票贩子3卖票,ticket = 2
票贩子3卖票,ticket = 1
票贩子2卖票,ticket = 9
郑州的票已经买完了。。
郑州的票已经买完了。。
票贩子1卖票,ticket = 5
郑州的票已经买完了。。

这个时候追加了延迟问题就暴露出来了,而实际上这个问题一直都在。

线程同步处理

线程同步:

经过分析之后已经可以确认同步问题所产生的主要原因了,那么,下面就需要进行同步问题的解决,但是解决同步问题的关键是锁,指的是当某一个线程执行操作的时候,其它线程外面等待。

问题的解决:

如果想解决这样的问题,就必须使用同步,所谓的同步就是指多个操作在同一个时间段内只能有一个线程进行,其他线程要等待此线程完成之后才可以继续执行。

多线程进阶学习管理_第5张图片

如果要想在程序之中实现这把锁的功能,就可以使用synchronized关键字来实现,利用此关键字可以定义同步方法或同步代码块,在同步代码块的操作里面的代码只允许一个线程执行。

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

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

上代码~

public class MyThread implements Runnable {
    private int ticket = 10;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (this.ticket > 0) {
                    try {
                        Thread.sleep(100); // 模拟网络延迟
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket);
                    ticket--;
                } else {
                    System.out.println("郑州的票已经买完了。。");
                    break;
                }
            }
        }
    }
}

class ThreadDemo {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        new Thread(mt, "票贩子1").start();
        new Thread(mt, "票贩子2").start();
        new Thread(mt, "票贩子3").start();
    }
}
// 结果:
票贩子1卖票,ticket = 10
票贩子3卖票,ticket = 9
票贩子2卖票,ticket = 8
票贩子2卖票,ticket = 7
票贩子2卖票,ticket = 6
票贩子2卖票,ticket = 5
票贩子3卖票,ticket = 4
票贩子1卖票,ticket = 3
票贩子3卖票,ticket = 2
票贩子2卖票,ticket = 1
郑州的票已经买完了。。
郑州的票已经买完了。。
郑州的票已经买完了。。

或者把锁加到方法上

public class MyThread implements Runnable {
    private int ticket = 10;

    private synchronized boolean sale() {
        if (this.ticket > 0) {
            try {
                Thread.sleep(100); // 模拟网络延迟
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket);
            ticket--;
            return true;
        } else {
            System.out.println("郑州的票已经买完了。。");
            return false;
        }
    }

    @Override
    public void run() {
        while (this.sale()) {
            
        }
    }
}

class ThreadDemo {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        new Thread(mt, "票贩子1").start();
        new Thread(mt, "票贩子2").start();
        new Thread(mt, "票贩子3").start();
    }
}

线程死锁

死锁

死锁是在进行多线程同步的处理之中有可能产生的一种问题,所谓的死锁指的是若干个线程彼此互相等待的状态。

上代码~

public class DeadLock implements Runnable{
    JianJian jj = new JianJian();
    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 JianJian {
    public synchronized void say (XiaoQiang xq) {
        System.out.println("贱贱说:此路是我开,此树是我栽,要想从这过,留下买路财~");
        xq.get();
    }

    public synchronized void get () {
        System.out.println("贱贱说:买路财给了,可以通过了。");
    }
}
class XiaoQiang {
    public synchronized void say (JianJian jj) {
        System.out.println("小强说:先让我过,我在给你买路财~");
        jj.get();
    }

    public synchronized void get () {
        System.out.println("逃过一劫,可以去办事了。");
    }
}
// 结果:
小强说:先让我过,我在给你买路财~
贱贱说:此路是我开,此树是我栽,要想从这过,留下买路财~ // 始终阻塞到这里,进程不会停止

现在死锁造成的主要原因是因为彼此都在互相等待着,等待着对方先让出资源。

死锁实际上是一种开发中出现的不确定的状态,有的时候代码如果处理不当则会不定期出现死锁,这是属于正常开发中的调试问题。

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

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

一、具体内容:

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

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

二、程序的基本实现

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

数据一: title=王建、content=宇宙大帅哥;

数据二: title=小高、content=猥琐第一人;

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

生产者与消费者

多线程进阶学习管理_第6张图片

上代码~

public class ThreadDemo1 {
    public static void main(String[] args) {
        Message message = new Message();
        new Thread(new Producer(message)).start();
        new Thread(new Consumer(message)).start();
    }
}

class Producer implements Runnable {
    private Message msg;
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                this.msg.setTitle("小贱贱");
                this.msg.setContent("宇宙第一大帅哥");
            } else {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                this.msg.setTitle("哈哈哈哈哈");
                this.msg.setContent("有一些些猥琐,常态猥琐");
            }
        }
    }

    public Producer (Message msg) {
        this.msg = msg;
    }
}

class Consumer implements Runnable {
    private Message msg;
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(this.msg.getTitle() + "  -  " + this.msg.getContent());
        }
    }

    public Consumer (Message msg) {
        this.msg = msg;
    }
}
@Data
class Message {
    private String title;
    private String content;
}

通过整个代码的执行你会发现此时有两个主要问题:

  • 问题一:数据不同步了;
  • 问题二:生产一个取走一个,但是发现有了重复生产和重复取出问题。

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

解决数据同步

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

class Message {
    private String title;
    private String content;

    public synchronized String get() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return this.title + "  -  " + this.content;
    }

    public synchronized void set(String title, String content) {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        this.title = title;
        this.content = content;
    }
}

在进行同步处理的时候肯定需要有一个同步的处理对象,那么此时肯定要将同步操作交由 Message 类处理是最合适的。

这个时候发现数据已经可以正常的保持一致了,但是对于重复操作的问题依然存在。

四、利用 Object 类解决重复操作

线程等待与唤醒

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

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

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

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

上代码~

class Message {
    private String title;
    private String content;
    private boolean flag; //表示生产或者消费形式
    // flag=true,允许生产,但是不允许消费
    // flag=false,允许消费,不允许生产

    public synchronized String get() {
        if (this.flag) {
            try {
                super.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        try {
            return this.title + "  -  " + this.content;
        } finally {
            this.flag = true; // 继续生产
            super.notify(); // 唤醒等待线程
        }
    }

    public synchronized void set(String title, String content) {
        if (!this.flag) {// 无法生产,应该等待被消费
            try {
                super.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        this.title = title;
        this.content = content;
        this.flag = false; // 已经生产过了
        super.notify();
    }
}

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

多线程深入话题

优雅的停止线程

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

停止多线程: public void stop()

销毁多线程: public void destroy()

挂起线程: public final void suspend() 暂停执行

恢复挂起的线程执行: public final void resume()

之所以废除掉这些方法,主要的原因是因为这些方法有可能导致线程的死锁。所以从 JDK1.2 开始就都不建议使用,如果要想实现线程的停止需要通过一种柔和的方式来进行。

上代码~

public class ThreadTest {
    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        new Thread("正在执行的线程") {
            @Override
            public void run() {
                long num = 0;
                while (flag) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName()+"-线程正在执行:num = " + num++);
                }
            }
        }.start();
        Thread.sleep(200);
        flag = false;
    }
}
// 结果:
正在执行的线程-线程正在执行:num = 0
正在执行的线程-线程正在执行:num = 1
正在执行的线程-线程正在执行:num = 2
正在执行的线程-线程正在执行:num = 3

守护线程

在多线程里面可以进行守护线程的定义,也就是说如果现在主线程的程序或者其它的线程还在执行的时候,线程将一直存在,并且运行在后台状态。如果不存在或者结束,守护线程也会结束

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

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

上代码~

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread("正在执行的线程") {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "-线程正在执行:i = " + i++);
                }
            }
        };
        Thread daemonThread = new Thread("守护线程") {
            @Override
            public void run() {
                for (int i = 0; i < Integer.MAX_VALUE; i++) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "-线程正在执行:daemon = " + i++);
                }
            }
        };
        daemonThread.setDaemon(true);
        thread1.start();
        daemonThread.start();
    }

volatile 关键字

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

在一些书上就将其错误的理解为同步属性。

1.在正常进行变量处理的时候往往会经历如下的几个步骤:

  • 获取变量原有的数据内容副本;
  • 利用副本为变量进行数学计算;
  • 将计算后的变量,保存到原始空间之中;

多线程进阶学习管理_第7张图片

2.如果一个属性上追加了 volatile 关键字,表示的就是副使用副本而是直接操作原始变量,相当于节约了拷贝副本,重新保存的步骤。

例如买票操作中:

public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        MyThread1 myThread1 = new MyThread1();
        new Thread(myThread1, "票贩子A").start();
        new Thread(myThread1, "票贩子B").start();
        new Thread(myThread1, "票贩子C").start();

    }
}

class MyThread1 implements Runnable {
    private volatile int task = 30;
    @Override
    public void run() {
        while (task > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "-票还剩:n = " + task--);
        }
    }
}

即使加入了volatile关键字依旧不能保证线程同步

3.解释 volatile 与 synchronized 的区别:

volatile 无法描述同步的处理,它只是一种直接内存的处理,避免了副本的操作,而 synchronized 是实现同步的;

ps:文章参考至阿里云培训中心

你可能感兴趣的:(学习,开发语言,java,后端,架构)