Java高级编程03
本文基于 阿里云大学:Java高级编程 整理记录,仅用于个人学习/交流使用。
五、多线程深入话题
优雅的停止线程
在多线程操作之中如果要启动多线程肯定使用的是Thread类中的 start()方法,而如果对于多线程需要进行停止处理,Thread类原本提供有 stop()方法,但是对于这些方法从JDK1.2版本开始就已经将其废除了。而且一直到现在也不再建议出现在你的代码之中,而除了stop()之外还有几个方法也被禁用了:
-
停止多线程:public void stop()
-
销毁多线程:public void destroy()
-
挂起线程:public final void suspend()、暂停执行
-
恢复挂起的线程执行:public final void resume()
之所以废除掉这些方法,主要的原因是因为这些方法有可能导致线程的死锁,所以从JDK1.2开始都不建议使用
如果想要实现线程的停止需要通过一种柔和的方式来进行。
范例:实现线程柔和的停止
public class MyThread {
public static boolean flag=true;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
int num=0;
while (flag){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+",num="+(num++));
}
}).start();
Thread.sleep(200);
flag=false;//停止线程
}
}
万一现在有其它的线程去控制这个 flag 的内容,那么这个时候对于线程的停止也不是说停就立刻停止的,而是会在执行中断flag 的内容来完成。
后台守护线程
现在假设有一个人并且这个人有一个保镖,那么这个保镖一定是在这个人活着的时候进行守护,如果这个人已经死了,保镖没用了。
所以在多线程里面可以进行守护线程的定义,也就是说如果现在主线线程的程序或者其它的线程还在执行的时候,那么守护线程将一直存在,并且运行在后台状态。.
在Thread类里面提供有如下的守护线程的操作方法:
-
设置为守护线程: public final void setDaemon( boolean on);
-
判断是否为守护线程: public final boolean isDaemon();
范例:使用守护线程
public class MyThread {
public static void main(String[] args) throws InterruptedException {
Thread userThread=new Thread(()->{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i <10; i++) {
System.out.println(Thread.currentThread().getName()+",i="+i);
}
},"用户线程");//完成核心业务
Thread deamonThread=new Thread(()->{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i <Integer.MAX_VALUE; i++) {
System.out.println(Thread.currentThread().getName()+",i="+i);
}
},"守护线程");
deamonThread.setDaemon(true);
userThread.start();
deamonThread.start();
}
}
可以发现所有的守护线程都是围绕在用户线程的周围,如果程序执行完毕了,守护线程也就消失了,在整个的JVM里面最大的守护线程就是GC线程。
程序执行中GC线程会一直存在,如果程序执行完毕,GC线程也将消失。
volatile关键字
在多线程的定义之中,volatile 关键字主要是在属性定义上使用的,表示此属性为直接数据操作,而不进行副本的拷贝处理,这样的话在一些书上就将其错误的理解为同步属性了。
在正常进行变量处理的时候往往会经历如下的几个步骤:
- 获取变量原有的数据内容副本;
- 利用副本为变量进行数学计算;
- 将计算后的变量,保存到原始空间之中;
而如果一个属性上追加了volatile关键字,表示的就是使用副本,而是直接操作原始变量,相当于节约了:拷贝副本、重新保存的步骤。
public class MyThread implements Runnable {
private volatile int tickets=5;
@Override
public void run() {
synchronized (this){
while (this.tickets>0){
System.out.println(Thread.currentThread().getName()+"买票处理,tickets="+this.tickets--);
}
}
}
}
class ThreadDemo{
public static void main(String[] args) {
MyThread myThread=new MyThread();
new Thread(myThread,"票贩子A").start();
new Thread(myThread,"票贩子B").start();
new Thread(myThread,"票贩子C").start();
}
}
面试题:请解释volatile 与 synchronized 的区别?
- vblatile主要在属性上使用,而 synchronized是在代码块与方法上使用的;
- volatile无法描述同步的处理,它只是一种直接内存的处理,避免了副本的操作; synchronized能够解决同步问题
六、多线程综合案例
数字加减
设计4个线程对象,两个线程执行减操作,两个线程执行加操作。
这一题目是一个经典的多线程的开发操作,这一个程序里面一定要考虑的核心本质在于:加一个、减一个,整体的计算结果应该只在0、-1、1之间循环出现。
package com.lut.JavaPlus;
public class MyThread{
public static void main(String[] args) {
Resource resource=new Resource();
SubThread subThread=new SubThread(resource);
AddThread addThread=new AddThread(resource);
new Thread(addThread,"加法线程A").start();
new Thread(addThread,"加法线程B").start();
new Thread(subThread,"减法线程A").start();
new Thread(subThread,"减法线程B").start();
}
}
class AddThread implements Runnable{
private Resource resource;
public AddThread(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
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 i = 0; i < 50; i++) {
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==false){ //现在要进行减法操作,加法等待
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==true){ //减法需要等待
super.wait();
}
Thread.sleep(200);
this.num--;
System.out.println("【减法操作-"+Thread.currentThread().getName()+"】 num="+this.num);
this.flag=true;
super.notifyAll();
}
}
生产电脑
设计一个生产电脑和搬运电脑类,要求生产出一台电脑就搬走一台电脑,如果没有新的电脑生产出来,则搬运工要等待新电脑产出
如果生产出的电脑没有搬走,则要等待电脑搬走之后再生产,并统计出生产的电脑数量。
package com.lut.JavaPlus;
public class MyThread{
public static void main(String[] args) {
Resource resource=new Resource();
new Thread(new Producer(resource)).start();
new Thread(new Consumer(resource)).start();
}
}
class Producer implements Runnable{
private Resource resource;
public Producer(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
for (int i = 0; i <50; i++) {
try {
this.resource.make();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable{
private Resource resource;
public Consumer(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
for (int i = 0; i <50; i++) {
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++;
}
@Override
public String toString() {
return "【count="+count+
"-Computer{" +
"name='" + name + '\'' +
", price=" + price +
"}】";
}
}
class Resource{
private Computer computer;
public synchronized void make() throws Exception {
if (this.computer!=null){
super.wait();
}
this.computer=new Computer("DELL",3000);
System.out.println("生产电脑:"+computer.toString());
super.notifyAll();
}
public synchronized void get() throws Exception {
if (this.computer==null){
super.wait();
}
System.out.println("取走电脑:"+computer.toString());
this.computer=null;
super.notifyAll();
}
}
竞争抢答
实现一个竞拍抢答程序:要求设置三个抢答者(三个线程),而后同时发出抢答指令,抢答成功者给出成功提示,未抢答成功者给出失败提示。
对于这一个多线程的操作由于里面需要牵扯到数据的返回问题,那么现在最好使用Callable
public class MyThread{
public static void main(String[] args) throws Exception {
ThreadDemo threadDemo=new ThreadDemo();
FutureTask<String> taskA=new FutureTask<String>(threadDemo);
FutureTask<String> taskB=new FutureTask<String>(threadDemo);
FutureTask<String> taskC=new FutureTask<String>(threadDemo);
new Thread(taskA,"竞赛者A").start();
new Thread(taskB,"竞赛者B").start();
new Thread(taskC,"竞赛者C").start();
System.out.println(taskA.get());
System.out.println(taskB.get());
System.out.println(taskC.get());
}
}
class ThreadDemo implements Callable<String> {
private boolean flag=false;
@Override
public String call() throws Exception {
synchronized (this){
if (this.flag==false){
this.flag=true;
return Thread.currentThread().getName()+"抢答成功!";
}else {
return Thread.currentThread().getName()+"抢答失败!";
}
}
}
}