Java多线程编程大完结,从入门到提高,一点一案例(真的不能再有比这更详细了)

Java多线程开发

进程与线程

我们明白java是为数不多的支持多线程开发

因为在原来DOS采取单线程,但是每当有病毒出现程序将无法执行,因此我

们需要进行多线程开发,来避免这种程序无法执行的状态

windows开启多进程设计,在同一时间段上同时运行多个程序并且这些程序

将依次执行,但是在同一个时间点上只会有一个程序执行(单核),多核可

以执行多进程

  • 单进程在同一时间只允许一个程序执行

  • 线程是在进程基础上划分的更小程序单元单元

  • 线程依赖进程支持

  • 线程启动速度快于进程

  • 进程是在操作系统上的划分,线程是在进程上的划分

Thread类实现多线程

简单创建一个多进程
通过继承Thread类可以实现多线程

在这里插入图片描述
可以看java.lang.Thread是Object的子类

在继承Thread程序类时,需要覆写run()方法public void run()

多线程要执行的功能都应该在run()方法中定义

public class DoAnotherThing extends Thread {
    public void run(){
        // here is where you do something
        //在这里描写多线程的功能
    }
}

但是我们不应该直接调用run()方法

要想启动多线程必须使用start()方法完成

public void start()

...
DoAnotherThing doIt = new DoAnotherThing();
doIt.start();//在这里我们没有调用run()方法,而是调用start()方法
...

我们可以把多线程的调度当作赛跑,run()方法就是在调度运动员在赛道上

做准备但是真正比赛的开始还是要看start()方法,start()方法就像信号

枪,只有调度了start()方法才能够真正开始多线程

值得注意的是虽然调用了start()方法,但最终执行的是run()方法

在这里插入图片描述
在这里插入图片描述

start()方法会抛出一个IllegalTHreadStateException异常对象但整个程序并

没有try…catch处理,因此该异常一定是RuntimeException的子类,每个线程

类对象只允许启动一次,如果重复启动就会抛出异常

Thread的执行分析
以下以一张图进行总结:

在这里插入图片描述
任何情况下,只要定义了多线程,多线程的启动永远只有一种方案:Tread类中的start()方法

Runnable实现多线程

Java里面提供第二种多线程主体定义结构形式:实现Java.lang.Runnable接口

在这里插入图片描述
从jdk1.8以后引入lambda表达式以后就变为了函数式接口
在这里插入图片描述
可以看出Runnable只有一个run()方法

接下来我们看下用法

范例:

public class mythread implements Runnable {//实现Runnable
    public void run(){
        // here is where you do something
        //在这里描写多线程的功能
    }
}
public class ThreadDemo{
 public static void main(String[] args)
    {
        Thread  mt=new Thread(new mythread());
        mt.start();
    }
}

从上方代码我们可以看出由于没有继承Thread类,但是我们又必须要使用

Thread类里面的start()方法实现多线程,于是我们就需要去观察Thread的

构造方法

Thread构造方法

Thread的构造方法

public Thread(Runnable target);

也就是说我们可以传入一个实现Runnable的对象然后再去调用start()方法

public class ThreadDemo{
 public static void main(String[] args)
    {
        Thread  mt=new Thread(new mythread());//创建Thread对象,传入Runnable对象
        mt.start();//调用Thread当中的start()方法实现多线程
    }
}

补充:使用lambda表达式实现多线程

public class ThreadDemo{
 public static void main(String[] args)
    {
       for(int x=0;x<3;x++)
       {
       		String title="线程对象"+x;
       		Runnable run=()->{
				for(int y=0;y<10;y++)
				{
					System.out.println(title+"运行、+y="+y);
				}
			};
			new Thread(run).start();
       }
    }
}

Thread与Runnable的关系

我们可以看出Runnable作为接口可以实现多继承,解决了Thread只能单继承

的难题,同时也可以更好进行功能的扩充

在这里插入图片描述

Thread与Runnable的关系:
在这里插入图片描述
在这里插入图片描述

范例:

class mythread implements Runnable{
    private int ticket=5;
    private string title;
    public mythread(String title)
    {
        this.title=title;    
    }
    @Override
    public void run{
        for(int x=0;x<100;x++)
        {
            if(this.title>0)
            {
               System.out.println("卖票,title="+this.ticket--)              
            }
                   
        }    
    }
}
public class threaddemo{
    public static void main(String[] args)
    {
        mythread mt=new mythread();
        new Thread(mt).start();//第一个线程启动
        new Thread(mt).start();//第二个线程启动
        new Thread(mt).start();//第三个线程启动
        
    }
}

在这里插入图片描述

三个线程都同时对同一个对象操作

Callable接口实现多线程

Runnable接口无法在完成后获取一个返回值,所以出现Java.util.concurrent.Callable接口可以在线程完成后返回一个值

接口的定义:

@FunctionalInterface
public interface Callable<V>
{
	public v call() throws Exception;
}

由于我们返回的值并不确定,所以在这里我们需要设置泛型
在这里插入图片描述
在方法介绍上,说明又时候我们并不一定都可以正常结束返回结果,这时就需要我们记得抛出异常

范例:

import java.util.concurrent.Callabe;
import java.util.concurrent.FutureTask;
class mythread implements Callable<String>{
    @Override
    public String call() throws Exception{
          System.out.println("******线程执行********,x="+x)       
    }
    return "线程执行完毕";
}
public class ThreadDemo{
    public static void main(String[] args) throws Exception{
         FutureTask<String> task=new FutureTask<>(new mythread());
         new Thread(task).start();
         System.out.println("【线程返回数据】"+task.get());           
    }
}

在这里插入图片描述
在这里插入图片描述

多线程运行状态

在这里插入图片描述

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

线程常用操作

线程的命名和取得

线程的命名和取得都是来源于Thread类中线程名称的操作

常用有一下三种操作

  • 构造方法命名:public Thread(Runnalbe target,String name) ;
  • setName()方法命名:public final void setName(String name);
  • getName()方法取得名字:public final String getName();
    范例:在run()方法中获取线程名称
class mythread implements Runable{
    @Override
    public void run{
          System.out.println(Thrad.currentThread().getName());       
    }
}
public class ThreadDemo{
    public static void main(String[] args) throws Exception{
         mythread mt=new mythread();
         new Thread(mt,"线程A").start();//设置了线程名字
         new Thread(mt).start();//未设置线程名字
         new Thread(mt,"线程B").start();//设置了线程名字           
    }
}

在这里插入图片描述
获取线程名称时,未命名线程会自动分配名称
接下来获取主线程名称

class mythread implements Runable{
    @Override
    public void run{
          System.out.println(Thrad.currentThread().getName());       
    }
}
public class ThreadDemo{
    public static void main(String[] args) throAws Exception{
         mythread mt=new mythread();
         new Thread(mt,"线程对象").start();//设置了线程名字                       
        mt.run();//对象直接调用run()方法
    }
}

在这里插入图片描述
获取主线程名称为main(主方法也是线程)

线程休眠

我们可以希望某个线程能够暂缓执行以方便观察运行状态

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

休眠方法:

  • 休眠:public static void sleep(long millis) throws InterruptedException;
  • 休眠:public static void sleep(long mills,int nanos) throws InterruptedException;

范例:观察休眠处理(单个对象进行线程休眠处理)

public class ThreadDemo{
    public static void main(String[] args) throws Exception{
       new Thread(()->{
           for(int x=0;x<10;x++)
           {
               System.out.println(Thread.currentThrad().getName()+",x="+x); 
               try{
                   Thread.sleep(1000);暂缓执行               
               }
               catch(InterruptedException e){
                   e.printStackTrace();
               }          
           }       
       },"线程对象").start();
    }
}

在线程的启动上有线程后,当程序休眠再启动也有先后,就像接力比赛,接棒时当作休眠,休眠后每个人的运行速度是不相同的,这就造成了线程执行完毕的先后顺序也是不同的

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

public class ThreadDemo{
    public static void main(String[] args) throws Exception{
       Runnable run=()->{
           for(int x=0;x<10;x++)
           {
               System.out.println(Thread.currentThrad().getName()+",x="+x); 
               try{
                   Thread.sleep(1000);暂缓执行               
               }
               catch(InterruptedException e){
                   e.printStackTrace();
               }          
           }       
       };
       for(int num=0;num<5;num++)
       {
           new Thread(run,"执行线程"+num).start();       
       }
    }
}

线程休眠要通过运行代码自己观察

线程中断

在之前的线程休眠中发现里面提供有一个中断异常,这实际上证明线程休眠是可以被打断的,这种打断肯定是由其他线程完成的

在Thread类里面提供有线程处理方法

  • 判断线程是否被中断:public boolean isInterrupted();
  • 中断线程执行:public void interrupt();
    范例:观察线程的中断处理
public class ThreadDemo {
	public static void main(String[] args) throws Exception {
		Thread thread=new Thread(()->{
			System.out.println("*****我需要休眠*****");
			try {
				Thread.sleep(10000);//休息10秒
				System.out.println("***睡足了,可以去工作了***");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				System.out.println("休息被打断");
			}
			
		}); 
		thread.start();//开始休息
		Thread.sleep(1000);//先休息1秒钟
		if(!thread.isInterrupted()) {//询问是否中断休息	
			//没有中断
			thread.interrupt();//现在打断你的休息
		}	
	}
}

在这里插入图片描述
正在执行的线程都是可以被中断的,中断的线程必须进行异常处理

线程强制运行

当满足某些条件之后,某一个线程对象可以一直独占资源一直到线程结束

范例:观察一个没有强制执行的程序

public class ThreadDemo {
	public static void main(String[] args) throws Exception {
		Thread thread=new Thread(()->{
			for(int x=0;x<100;x++)
			{
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"执行、x="+x);
			}
			
		},"玩耍的线程"); 
		thread.start();
		for(int x=0;x<100;x++)
		{
			Thread.sleep(100);
			System.out.println("霸道的main线程执行x="+x);
		}
	
	}
}

在这里插入图片描述
我们发现在子线程会和主线程抢占资源进行输出,接下来我们看看在利用了线程的强制执行后的运行状态

在Thread类里面提供有强制执行方法,join()方法

public final void join() throws InterruptedException

利用join()方法我们可以使线程强制执行独占资源

从强制执行抛出异常看出,强制执行也可以被中断

范例:强制执行程序

public class ThreadDemo {
	public static void main(String[] args) throws Exception {
		Thread mainThread=new Thread().currentThread();//获取主线程
		Thread thread=new Thread(()->{
			for(int x=0;x<100;x++)
			{
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				if(x>3)
				{
					try {
						mainThread.join();//霸道的线程开始执行
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				System.out.println(Thread.currentThread().getName()+"执行、x="+x);
			}
			
		},"玩耍的线程"); 
		thread.start();
		for(int x=0;x<100;x++)
		{
			Thread.sleep(100);
			System.out.println("霸道的main线程执行x="+x);
		}
	
	}
}

在这里插入图片描述
可以看出当下x>3后main()开始独占资源,当main()执行完成后其他线程才开始执行

在进行强制执行的时候一定要获取强制执行线程对象才可以进行join的操作

线程礼让

在多线程中,可以先将资源让出去让别的线程先执行,线程的礼让可以用Thread中的方法

  • 礼让方法:public static void yield();

范例:线程的礼让执行

public class ThreadDemo {
	public static void main(String[] args) throws Exception {
	
		Thread thread=new Thread(()->{
			
			for(int x=0;x<100;x++)
			{
				if(x%3==0)
				{
					Thread.yield();//玩耍的线程礼让了
					System.out.println("****玩耍的线程礼让了*****");
					
				}
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				System.out.println(Thread.currentThread().getName()+"执行、x="+x);
			}
			
		},"玩耍的线程"); 
		thread.start();
		for(int x=0;x<100;x++)
		{
			Thread.sleep(100);
			System.out.println("霸道的main线程执行x="+x);
		}
	
	}
}

在这里插入图片描述
每一次调用yield()都只会礼让一次当前的资源

线程优先级

从理论上讲线程的优先级越高越有可能先执行(越有可能先抢占到资源)在Thread类里面提供有如下的两个处理方法

  • 设置优先级:public final 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(优先级的值)

范例:没有设置优先级

public class ThreadDemo {
	public static void main(String[] args) throws Exception {
		Runnable run=()->{
			for(int x=0;x<10;x++)
			{
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"执行");
			}
		};
		Thread threadA=new Thread(run,"线程A对象");
		Thread threadB=new Thread(run,"线程B对象");
		Thread threadC=new Thread(run,"线程C对象");
		threadA.start();
		threadB.start();
		threadC.start();
	}
}

在这里插入图片描述

不同电脑效果不同,本人的是B优先执行
接下来看使用优先级后电脑执行的效果
范例:观察设置优先级后的效果

public class ThreadDemo {
	public static void main(String[] args) throws Exception {
		Runnable run=()->{
			for(int x=0;x<10;x++)
			{
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"执行");
			}
		};
		Thread threadA=new Thread(run,"线程A对象");
		Thread threadB=new Thread(run,"线程B对象");
		Thread threadC=new Thread(run,"线程C对象");
		threadA.setPriority(Thread.MIN_PRIORITY);//把AB设置优先级最小
		threadB.setPriority(Thread.MIN_PRIORITY);//把AB设置优先级最小
		threadC.setPriority(Thread.MAX_PRIORITY);//把AB设置优先级最小
		threadA.start();
		threadB.start();
		threadC.start();
	}
}

在这里插入图片描述
提高有先级确实可以提高线程先执行的可能,但是并不是绝对先执行

补充:主方法是一个主线程,主线程的优先级是多少呢?

public class ThreadDemo {
	public static void main(String[] args) throws Exception {
			System.out.println(Thread.currentThread().getPriority());
	}
}

在这里插入图片描述
发现主线程的优先级是5,只是中等优先级,同时这与我们默认的方法的优先级是相同的

线程的同步与锁死

同步问题

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

同步问题的引出
下面编写一个简单的买票程序,将创建若干个线程对象实现卖票的操作;

范例:实现买票操作

class Mythread implements Runnable{
	private int ticket=10;

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true)
		{
			if(this.ticket>0)
			{
				System.out.println(Thread.currentThread().getName()+"卖票、ticket="+this.ticket--);
			}
			else {
				System.out.println("票已经抢光");
				break;
			}
		}	
	}
}
public class ThreadDemo {
	public static void main(String[] args) throws Exception {
		Mythread my=new Mythread();
		new Thread(my,"线程A").start();
		new Thread(my,"线程B").start();
		new Thread(my,"线程C").start();
	}
}

此时线程的三个对象将进行10张票的出售
在这里插入图片描述
程序执行到目前为止还算正常,但是当我们增加休眠操作用来模拟现实中的网络延迟时就会发现问题

class Mythread implements Runnable{
	private int ticket=10;

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true)
		{
			if(this.ticket>0)
			{
				try {
					Thread.sleep(100);//增加休眠操作用来模拟网络延迟效果
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"卖票、ticket="+this.ticket--);
			}
			else {
				System.out.println("票已经抢光");
				break;
			}
		}	
	}
}
public class ThreadDemo {
	public static void main(String[] args) throws Exception {
		Mythread my=new Mythread();
		new Thread(my,"线程A").start();
		new Thread(my,"线程B").start();
		new Thread(my,"线程C").start();
	}
}

在这里插入图片描述
可以发现在这个时候居然出现了票数为0,-1的情况,这就是同步问题的引出

对同步问题进行分析

在这里插入图片描述
三个票贩子(三个线程)都对同一块数据进行操作(为方便我们假设这个时候就只有最后一张票了),我们一步一部观察
第一步
在这里插入图片描述
当线程进入时先进行判段是否有票,现在就剩一张票所以第一个线程可以进入,这个时候可能有第二个线程进入但这个时候ticket还是1;

第二步
在这里插入图片描述
用sleep()对网络延迟进行模拟,当第一个线程进入后开始休眠,休眠后开始执行ticket–操作,这个时候可能第二个线程也进入,但是第一个线程可能已经进入第三步;
第三步
在这里插入图片描述
当第一个线程ticket–完成时,第二个线程的已经通过判断也就是说它也可以进入第三步,这个时候票数为0,再进行–操作就变为-1了
在这里插入图片描述

线程同步处理

经过分析我们已经发现了同步问题产生的主要原因了,那么接下来就需要进行同步问题的解决,解决同步问题的关键就是锁

锁:指的就是当某个线程执行操作的时候其他线程外面等待;

在这里插入图片描述
如果想要实现锁的功能,就可以使用synchronized关键字来实现,利用此关键字可以定义同步方法或同步代码块,再同步代码块的操作里面的代码只允许一个线程执行
1.利用同步代码块进行处理

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

一般进行同步对象处理的时候可以采取当前对象this进行同步
范例:利用同步代码块解决数据同步访问问题

class Mythread implements Runnable{
	private int ticket=10;

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true)
		{
			synchronized (this) {//每一次只允许一个线程通过
				if(this.ticket>0)
				{
					try {
						Thread.sleep(100);//增加休眠操作用来模拟网络延迟效果
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"卖票、ticket="+this.ticket--);
				}
				else {
					System.out.println("票已经抢光");
					break;
				}
			}
		}	
	}
}
public class ThreadDemo {
	public static void main(String[] args) throws Exception {
		Mythread my=new Mythread();
		new Thread(my,"线程A").start();
		new Thread(my,"线程B").start();
		new Thread(my,"线程C").start();
	}
}

在这里插入图片描述

程序可以正常执行,但是加入同步之后程序的整体执行性能下降了。同步实际上会造成性能的降低

2.利用同步方法解决:只需要在方法定义上使用sychronized关键字即可

范例:

class Mythread implements Runnable{
	private int ticket=10;
	public  synchronized boolean sale()
	{
		if(this.ticket>0)
		{
			try {
				Thread.sleep(100);//增加休眠操作用来模拟网络延迟效果
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"卖票、ticket="+this.ticket--);
			return true;
		}
		else {
			System.out.println("票已经抢光");
			return false;
		}
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(this.sale())
		{
			
		}	
	}
}
public class ThreadDemo {
	public static void main(String[] args) throws Exception {
		Mythread my=new Mythread();
		new Thread(my,"线程A").start();
		new Thread(my,"线程B").start();
		new Thread(my,"线程C").start();
	}
}

在这里插入图片描述
同样可以达到同步的效果

线程锁死

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

范例:线程死锁

public class Deadlock implements Runnable{
	A a=new A();
	B b=new B();
	@Override
	public void run() {
		// TODO Auto-generated method stub
		a.say(b);
	
	}
	public Deadlock(){
		new Thread(this).start();
		b.say(a);
	}
	public static void main(String[] args) {
		new Deadlock();
	}
}

class A{
	public synchronized void say(B b) {
		System.out.println("A:想要通过就要先给钱");
		b.get();
	}
	public synchronized void get() {
		System.out.println("A:你给我钱了,可以让你通过");
	}
}
class B{
	public synchronized void say(A a)
	{
		System.out.println("B:我必须要先过去才能给你钱");
		a.get();
	}
	public synchronized void get()
	{
		System.out.println("B:我通过了,可以给你钱");
	}
}

在这里插入图片描述
在这里插入图片描述
发现程序一直锁死在这里一直运行但没有输出get()内的语句

综合案例:生产者与消费者

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

在多线程开发过程中最为著名的案例就是生产者与消费者操作

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

程序的基本实现
可将生产者定义为两个独立的线程类对象

  • 数据1:title=小杨、content=宇宙大帅哥;
  • 数据2:title=小方、content=社会小青年;

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

在这里插入图片描述
范例:实现程序基本结构

public class ThreadDemo{
	public static void main(String[] args) throws Exception {
		Message msg=new Message();
		new Thread(new producter(msg)).start();//启动生产者线程
		new Thread(new Consumer(msg)).start();//启动消费者线程
	}
	
}
class producter implements Runnable{
	private Message msg;
	public producter(Message msg){
		this.msg=msg;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int x=0;x<100;x++)
		{
			if(x%2==0)
			{
				this.msg.setContent("社会小青年");//两个线程数据
				try {
					Thread.sleep(10);//增加休眠,模拟网络延迟
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				this.msg.setTitle("小方");//两个线程数据
			}
			else {
				this.msg.setContent("宇宙大帅哥");//两个线程数据
				try {
					Thread.sleep(10);//增加休眠,模拟网络延迟
				} catch (Exception e) {
					// TODO: handle exception
				}
				this.msg.setTitle("小杨");//两个线程数据
			}
		}
	}
}

class Consumer implements Runnable{
	private Message msg;
	public Consumer(Message msg) {
		this.msg=msg;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int x=0;x<100;x++)
		{
			try {
				Thread.sleep(10);//增加休眠,模拟网络延迟
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(this.msg.getTitle()+"  -   "+this.msg.getContent());
		}
		
	}
}
class Message{
	private String title;
	private String content;
	public void setContent(String content) {
		this.content = content;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getContent() {
		return content;
	}
	public String getTitle() {
		return title;
	}
}

初次建立框架进行执行
在这里插入图片描述
我们发现出现了问题,在这个时候本来对应的是

  • 小杨是宇宙大帅哥,小方是社会小青年
  • 但是现在出现了错误的信息(数据不同步)

现在我们只是完成了程序的基本结构而并没有考虑到同步问题,接下来就需要解决同步问题

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

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

范例:解决生产者与消费者同步问题

public class ThreadDemo{
	public static void main(String[] args) throws Exception {
		Message msg=new Message();
		new Thread(new producter(msg)).start();//启动生产者线程
		new Thread(new Consumer(msg)).start();//启动消费者线程
	}
	
}
class producter implements Runnable{
	private Message msg;
	public producter(Message msg){
		this.msg=msg;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int x=0;x<100;x++)
		{
			if(x%2==0)
			{
				try {
					Thread.sleep(10);//增加休眠,模拟网络延迟
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				this.msg.set("小方","社会小青年");//两个线程数据
			}
			else {
				try {
					Thread.sleep(100);//增加休眠,模拟网络延迟
				} catch (Exception e) {
					// TODO: handle exception
				}
				this.msg.set("小杨","宇宙大帅哥");//两个线程数据
			}
		}
	}
}

class Consumer implements Runnable{
	private Message msg;
	public Consumer(Message msg) {
		this.msg=msg;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int x=0;x<100;x++)
		{
			try {
				Thread.sleep(10);//增加休眠,模拟网络延迟
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(this.msg.get());
		}
		
	}
}
class Message{
	private String title;
	private String content;
	public synchronized void set(String title,String content)
	{
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.content=content;
		this.title=title;
	}
	public synchronized String get() {
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return this.title+"  -  "+this.content;
	}
}

在这里插入图片描述
解决完同步问题后,数据信息保持一致,但是不能避免数据重复问题

利用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类

public class ThreadDemo{
	public static void main(String[] args) throws Exception {
		Message msg=new Message();
		new Thread(new producter(msg)).start();//启动生产者线程
		new Thread(new Consumer(msg)).start();//启动消费者线程
	}
	
}
class producter implements Runnable{
	private Message msg;
	public producter(Message msg){
		this.msg=msg;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int x=0;x<100;x++)
		{
			if(x%2==0)
			{
				try {
					Thread.sleep(10);//增加休眠,模拟网络延迟
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				this.msg.set("小方","社会小青年");//两个线程数据
			}
			else {
				try {
					Thread.sleep(100);//增加休眠,模拟网络延迟
				} catch (Exception e) {
					// TODO: handle exception
				}
				this.msg.set("小杨","宇宙大帅哥");//两个线程数据
			}
		}
	}
}

class Consumer implements Runnable{
	private Message msg;
	public Consumer(Message msg) {
		this.msg=msg;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int x=0;x<100;x++)
		{
			try {
				Thread.sleep(10);//增加休眠,模拟网络延迟
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(this.msg.get());
		}
		
	}
}
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==false)
		{
			try {
				super.wait();//无法进行生产应该等待消费
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.content=content;
		this.title=title;
		this.flag=false;//已经生产过了
		super.notify();//唤醒等待的线程,有就唤醒,没有就算了
	}
	public synchronized String get() {
		if(this.flag==true)
		{
			try {
				super.wait();//还未生产需要等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			return this.title+"  -  "+this.content;
		}
		finally {//不管如何都要执行
			this.flag=true;//继续生产
			super.notify();//唤醒等待线程
			
		}
	}
}

在这里插入图片描述
到此,程序执行正常,生产消费一步一步进行,这种处理形式就是在进行多线程开发之中最原始的处理方案,整个等待、同步过程都是通过原生代码实现控制

多线程的深入话题

优雅的停止线程

在多线程启动使用的是Thread类里面的start()方法,而多线程的停止处理,Thread的stop()方法已经废除,不建议使用

还要几个方法也被禁用

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

之所以废除掉这些方法,主要的原因是因为这些方法有的可能导致线程死锁,这个时候我们需要线程停止就需要线程柔和的方式

范例:柔和的停止线程

public class Threade {
	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) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"正在运行、num="+num++);
			}
		},"执行线程").start();
		Thread.sleep(200);
		flag=false;
	}
}

在这里插入图片描述
因为现在程序执行,没有其他线程去控制flag的内容,所以现在这个时候,我们就可以通过改变flag来控制程序执行,但是万一有其他线程去控制flag的话,一旦其他线程阻塞导致flag的值无法改变,这个时候就会发现线程无法停止了

所以这个停止线程的方式是现阶段最有效的线程停止方法

后台守护线程

现在假设有一个人并且这个人有一个保镖,那么这个保镖一定是在这个人活着的时候才进行守护。所以在的线程里面可以进行守护线程的定义,也就是说如果现在主线程或者其他线程还在执行的时候,那么守护线程将一直存在,并且运行在后台状态

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

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

先看看没有使用守护线程的效果

public class Threade {
	public static void main(String[] args) throws Exception {
		 Thread userthread=new Thread(()->{
			for(int x=0;x<10;x++) {//用户线程只执行10次
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					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(50);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"正在运行、x="+x++);
			}
		},"守护线程");
		Thread.sleep(200);
		userthread.start();
		daemonthread.start();
	}
}

在这里插入图片描述
在这里,用户线程执行完成后守护线程依然在一直执行
接下来看看使用守护线程的效果

public class Threade {
	public static void main(String[] args) throws Exception {
		 Thread userthread=new Thread(()->{
			for(int x=0;x<10;x++) {//用户线程只执行10次
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					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(50);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"正在运行、x="+x++);
			}
		},"守护线程");
		daemonthread.setDaemon(true);
		userthread.start();
		daemonthread.start();
	}
}

当用户线程执行完成后守护线程同样停止执行
在这里插入图片描述
在程序执行过程中最大的守护线程就是GC线程
程序执行中GC线程会一直存在,如果程序执行完毕GC线程也将消失

volatile关键字

volatile关键字主要在属性定义上使用的,表示此属性为直接数据操作,而不进行副本的拷贝处理,所以在一些书上就将其错误的理解为同步属性

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

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

而如果一个属性追加了volatile关键字,表示的就是不使用副本,而是直接操作原始变量,相当与节约了拷贝、重新保存步骤
volatile工作过程
范例:volatile的正确使用

package java线程;

public class Mythread implements Runnable {
	private volatile int title=5;
	@Override
	public void run() {
		// TODO Auto-generated method stub
		synchronized (this) {
		while(this.title>0) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"卖票处理="+this.title--);
		}
		}	
	}
	public static void main(String[] args) throws Exception {
		Mythread mt=new Mythread();
		new Thread(mt,"票贩子A").start();
		new Thread(mt,"票贩子B").start();
		new Thread(mt,"票贩子C").start();
		
		
	}

}

在这里插入图片描述
面试题:请解释volatile与synchronized的区别

  • volatile主要在属性上使用,而synchronized是在代码1块方法上使用的
  • volatile无法描述同步处理它只是一种直接的内存处理,避免了副本的操作
  • synchronized可以处理同步问题
    本质上两者没有联系

Java多线程综合案例

数字加减

设计4个线程对象,两个线程执行减操作,两个线程执行加操作

public class ThreadDemo{
	public static void main(String[] args) throws Exception {
		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() {
		// TODO Auto-generated method stub
		for(int x=0;x<50;x++) {
			try {
				this.resource.add();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}
class SubThread implements Runnable{//减法操作
	private Resource resource;
	public SubThread(Resource resource) {
		this.resource=resource;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int x=0;x<50;x++) {
			try {
				this.resource.sub();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				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();//唤醒全部等待线程
	}
}



在这里插入图片描述
这一题目是经典的多线程开发操作,这个程序里面一定要考虑的核心本质在于:加一个、减一个,整体的计算结果应该只在0、-1、1之间循环出现

生产电脑

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

解答:在本程序之中实现的就是一个标准的生产者与消费者的处理模型

public class ThreadDemo{
	public static void main(String[] args) throws Exception {
		
		Resource res=new Resource();
		new Thread(new Producer(res)).start();
		new Thread(new Consumer(res)).start();
		
	}
}
class Producer implements Runnable{
	private Resource resource;
	public Producer(Resource resource) {
		this.resource=resource;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int x=0;x<50;x++) {
			
			this.resource.make();
		}
		
	}
	
}
class Consumer implements Runnable{
	private Resource resource;
	public Consumer(Resource resource) {
		this.resource=resource;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int x=0;x<50;x++) {
	
			this.resource.get();
		}
		
	}
	
}
class Resource{
	private Computer computer;
	private boolean flag=true;
	public synchronized void make() {
		if(this.computer!=null) {//已经生产过了
			try {
				super.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.computer=new Computer("小米电脑",1.1);
		System.out.println("生产电脑"+this.computer);
		super.notifyAll();
	}
	public synchronized void get() {
		if(this.computer==null) {//还没有生产
			try {
				super.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("取走电脑"+this.computer);
		this.computer=null;//已经取走了
		super.notifyAll();
	}
}
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;
	}
}


在这里插入图片描述

竞争抢答

实现一个竞拍抢答程序:要求设置三个抢答者(三个线程),而后发出抢答指令,抢答成功给出抢答成功提示,抢答失败给出抢答失败提示

由于需要牵扯到数据的返回所以使用Callable更简单

package java线程;

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

public class ThreadDemo{
	public static void main(String[] args) throws Exception {
		
		Mythread mt=new Mythread();
		FutureTask<String> taskA=new FutureTask<String>(mt);
		FutureTask<String> taskB=new FutureTask<String>(mt);
		FutureTask<String> taskC=new FutureTask<String>(mt);
		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 Mythread implements Callable<String>{
	private boolean flag=false;

	@Override
	public String call() throws Exception {
		// TODO Auto-generated method stub
		synchronized (this) {
			if(this.flag==false) {
				this.flag=true;
				return Thread.currentThread().getName()+"抢答成功";
			}
			else {
				return Thread.currentThread().getName()+"抢答失败";
			}
			
		}
	}
	
}

在这里插入图片描述
使用Callable的主要原因是因为Callable拥有返回值方便我们处理

  • 24
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 28
    评论
评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

样子的木偶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值