Java多线程与优先级详细解读

目录
  • 1、多线程
    • 1.1多线程的基本概念
    • 1.2多线程的实现
    • 1.3继承Thread类实现多线程
    • 1.4Runnable接口实现多线程
    • 1.5Thread类和Runnable接口实现多线程的区别
    • 1.6线程的操作状态
    • 1.7Callable实现多线程
    • 1.8线程命名和取得
    • 1.9线程的休眠
    • 1.10线程的优先级
    • 1.11线程的同步与死锁
    • 1.12死锁
  • 综合案例
    • 1.解决数据错位问题:依靠同步解决
    • 2.解决数据的重复设置和重复取出
    • 面试题:请解释sleep()和wait()的区别?

1、多线程

要使用多线程必须有一个前提,有一个线程的执行主类。从多线程开始,Java正式进入到应用部分,而对于多线程的开发,从JavaEE上表现的并不是特别多,但是在Android开发之中使用较多,并且需要提醒的是,鄙视面试的过程之中,多线程所问道的问题是最多的。

1.1 多线程的基本概念

如果要想解释多线程,那么首先应该从单线程开始讲起,最早的DOS系统有一个最大的特征:一旦电脑出现了病毒,电脑会立刻死机,因为传统DOS系统属于单进程的处理方式,即:在同一个时间段上只能有一个程序执行,后来到了windows时代,电脑即使(非致命)存在了病毒,那么也可以正常使用,只是满了一些而已,因为windows属于多进程的处理操作,但是这个时候的资源依然只有一块,所以在同一时间段上会有多个程序共同执行,而在一个时间点上只能有一个程序在执行,多线程实在一个进程基础之上的进一步划分,因为进程的启动所消耗的时间是非常长的,所以在进程之上的进一步划分就变得非常重要,而且性能也会有所提高。

所有的线程一定要依附于进程才能够存在,那么进程一旦消失了,线程也一定会消失,但反过来不一定,而Java是为数不多的支持多线程的开发语言之一。

1.2 多线程的实现

在Java之中,如果要想实现多线程的程序,就必须依靠一个线程的主体类(叫好比主类的概念一样,表示的是一个线程的主类),但是这个线程的主体类在定义的时候也需要有一些特殊的要求,这个类可以继承Thread类或实现Runnable接口来完成定义。

1.3 继承Thread类实现多线程

Java.lang.Thread是一个线程操作的核心类负责线程操作的类,任何类只需要继承了Thread类就可以成为一个线程的主类,但是既然是主类必须有它的使用方法,而线程启动的主方法是需要覆写Thread类中的run()方法才可以(相当于线程的主方法)。

package com.day12.demo;
class MyThread extends Thread{
	private String title;
	public MyThread(String title){
		this.title = title;
	}
	public void run(){
		for (int i = 0; i < 5; i++) {
			System.out.println(this.title + ",i = " + i);
		}
	}
}
public class ThreadDemo {
	@SuppressWarnings("unused")
	public static void main(String[] args) {
		MyThread thread1 = new MyThread("线程1");
		MyThread thread2 = new MyThread("线程2");
		MyThread thread3 = new MyThread("线程3");
		thread1.run();
		thread2.run();
		thread3.run();
	}
}

我们只是做了一个顺序打印操作,而和多线程没有关系。多线程的执行和进程是相似的,而是多个程序交替执行,而不是说各自执行各自的。正确启动多线程的方式应该是调用Thread类中的start()方法。

启动多线程只有一个方法:public void start(); 调用此方法会调用run()

正确启动多线程

package com.day12.demo;
class MyThread extends Thread{
	private String title;
	public MyThread(String title){
		this.title = title;
	}
	public void run(){
		for (int i = 0; i < 5; i++) {
			System.out.println(this.title + ",i = " + i);
		}
	}
}
public class ThreadDemo {
	@SuppressWarnings("unused")
	public static void main(String[] args) {
		MyThread thread1 = new MyThread("线程1");
		MyThread thread2 = new MyThread("线程2");
		MyThread thread3 = new MyThread("线程3");
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

此时再次执行代码发现所有所有的线程对象变为交替执行。所以得出结论:要想启动线程必须依靠Thread类的start()方法执行,线程启动之后会默认调用了run()方法。

问题:为什么线程启动的时候必须调用start()而不是直接调用run()?

如果想要了解必须打开Java源代码进行浏览(在JDK按照目录下)

public synchronized void start() {
    /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
    if (threadStatus != 0)
        //这个异常的产生只有在你重复启动线程的时候才会发生
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        //只声明未实现的方法,同时使用native关键字定义,native调用本机的原生系统函数
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
        }
    }
}

多线程的实现一定需要操作系统的支持,那么异常的start0()方法实际上就和抽象方法很类似没有方法体,而这个方法体交给JVM去实现,即:在windows下的JVM可能使用A方法实现了start0(),而在Linux下的JVM可能使用了B方法实现了start0(),凡是在调用的时候并不会去关心集体是何方式实现了start0()方法,只会关心最终的操作结果,交给JVM去匹配了不同的操作系统。

​ 所以多线程操作之中,使用start()方法启动多线程的操作是需要进行操作系统函数调用的。

1.4 Runnable接口实现多线程

Thread类的核心功能就是进行线程的启动,但是如果一个类直接继承Threa类就会造成单继承的局限,在Java中又提供了有另外一种实现Runable接口。

public interface Runnable{
	public void run();
}

分享:如何区分新老接口?

​ 在JDK之中个,由于其发展的时间较长,那么会出现一些新的接口和老的接口,这两者有一个最大的明显特征:所有最早提供的接口方法里面都不加上public,所有的新接口里面都有public。

通过Runnable接口实现多线程

package com.day12.demo;
class MyThread implements Runnable{
	private String title;
	public MyThread(String title){
		this.title = title;
	}
	public void run(){
		for (int i = 0; i < 5; i++) {
			System.out.println(this.title + ",i = " + i);
		}
	}
}

这个时候和之前的继承Thread类区别不大,但是唯一的好处就是避免了单继承局限,不过现在问题也就来了,刚刚解释过,如果要想启动多线程依靠Thread类的start()方法完成,之前继承Thread()类的时候可以将此方法直接继承过来使用,但现在实现的是Runnable接口,没有这个方法可以继承了,为了解决这个问题,还是需要依靠Thread类完成,在Thread类中定义一个构造方法:public Thread(Runnable target),接收Runnable接口对象。

利用Thread类启动

public class ThreadDemo {
	@SuppressWarnings("unused")
	public static void main(String[] args) {
		MyThread thread1 = new MyThread("线程1");
		MyThread thread2 = new MyThread("线程2");
		MyThread thread3 = new MyThread("线程3");
		new Thread(thread1).start();
		new Thread(thread2).start();
		new Thread(thread3).start();
	}
}

这个时候就实现了多线程的启动,而且没有了单继承局限。

1.5 Thread类和Runnable接口实现多线程的区别

现在Thread类Runnable接口都可以作为同一功能的方式来实现多线程,那么这两者如果从Java的十年开发而言,肯定使用Ruanable接口,因为可以有效的避免单继承的局限,那么除了这些之外,这两种方式是否还有其他联系呢?

为了解释这两种方式的联系,下面可以打开Thread类的定义:

Public class Thread Object implements Runnable

发现Thread类也是Runnable接口的子类,而如果真的是这样,那么之前程序的结构就变为了一下形式。所以说多线程非常类似于代理模式。

这个时候表现出来的代码模式非常类似于代理设计模式,但是它不是严格意义上的代理设计模式,因为从严格意义上来讲代理设计模式之中,代理主体所能够使用的方法依然是接口中定义的run()方法,而此处代理主题调用的是start()方法,所以只能够说形式上类似于代理设计模式,但本质上还是有差别的。

但是除了以上的联系之外,对于Runnable和Thread类还有一个不太好区分的区别:使用Runnable接口可以更加方便的表示出数据共享的概念。

买票程序

package com.day12.demo;
class MyTicket extends Thread{
	private int ticket = 10;
	public void run(){
		for (int i = 0; i < 20; i++) {
			if(ticket > 0)
			System.out.println("买票 =" + this.ticket--);
		}
	}
}
public class TicketDemo {
	public static void main(String[] args) {
		new MyTicket().start();
		new MyTicket().start();
		new MyTicket().start();
	}
}

运行后发现,数据没有共享。

package com.day12.demo;
class MyTicket extends Thread{
	private int ticket = 10;
	public void run(){
		for (int i = 0; i < 20; i++) {
			if(ticket > 0)
			System.out.println("买票 =" + this.ticket--);
		}
	}
}
public class TicketDemo {
	public static void main(String[] args) {
		MyTicket mt = new MyTicket();
		new Thread(mt).start();
		new Thread(mt).start();
		new Thread(mt).start();

	}
}

经过改进之后,发现数据进行共享,但是对于逻辑是解释是不好理解的,MyTicket类继承了Thread类,自己拥有了start()方法但是不执行自己的start()方法,而是通过匿名方法共用MyTicket()实例化对象mt调用匿名方法的start()方法。

再次经过改进之后

package com.day12.demo;
class MyTicket implements Runnable{
	private int ticket = 10;
	public void run(){
		for (int i = 0; i < 20; i++) {
			if(ticket > 0)
			System.out.println("买票 =" + this.ticket--);
		}
	}
}
public class TicketDemo {
	public static void main(String[] args) {
		MyTicket mt = new MyTicket();
		new Thread(mt).start();
		new Thread(mt).start();
		new Thread(mt).start();

	}
}

通过MyTicket类实现Runnable接口来进行改进,原因是因为Runnable接口里面只有一个自己的run()方法,而此处的start()的方法,是通过匿名类进行调用start()方法来实现线程的启动。

面试题:请解释多线程的两种实现方式区别?分别编写程序验证两种实现。

多线程的两种实现方式都需要一个线程的主类,而这个类可以实现Runnable接口或继承Thread,不管使用何种方式都必须在子类之中覆写run()方法,此方法为线程的主方法;

Thread类是Runnable接口的子类,而且使用Runnable接口可以避免单继承局限,以及更加方便的实现数据共享的概念。

Runnable 接口:

class MyThread implements Runnable{
	private int ticket=5;
	public void run(){
		for(int x=0;x<50;x++)
			if(this.ticket>0){
				System.out.println(this.ticket--);
			}
	}
}

MyThread mt = new MyThread();
new Thread(mt).start();

Thread 类:

class MyThread extends Thread{
	private int ticket=5;
	public void run(){
		for(int x=0;x<50;x++)
			if(this.ticket>0){
				System.out.println(this.ticket--);
			}
	}
}

MyThread mt = new MyThread();
mt.start();

1.6 线程的操作状态

当我们多线程调用start方法之后不会立刻执行,而是进入就绪状态,等待进行调度后执行,需要将资源分配给你运行后,才可以执行多线程的代码run()中的代码当执行一段时间之后,你需要让出资源,让其他线程来执行,这个时候run()方法可能还没有执行完成,只执行了一半,那么我么我们就需要让资源,随后重新进入就绪状态,重新等待分配新资源继续执行。当线程执行完毕后才会进入终止状态。总结就一句话:线程执行需要分配资源,资源不能独占,执行一会让出资源给其他程序执行。

1.7 Callable实现多线程

jdk增加新的工具类java.util.concurrent

@FunctionalInterface
public interface Callable<V>

Runnable中的run()方法虽然也是线程的主方法,但是其没有返回值,因为它的设计遵循了我们主方法的设计原则:线程开始就别回头。但是在很多时候需要一些返回值,例如:当某些线程执行完成后可能带来一些返回结果,这种情况下我们就只能通过Callabale来实现多线程。

通过分析源代码可以找到一些关系

Callable定义线程主方法启动并取得多线程执行的总结果

package com.day12.demo;

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

class MyTicket1 implements Callable<String>{
	@Override
	public String call() throws Exception {
		// TODO Auto-generated method stub
		for (int i = 0; i < 20; i++) {
			System.out.println("买票 ,x = " + i);
		}
		return "票卖完了,下次吧";
	}
}
public class CallableDemo {
	@SuppressWarnings({ "unused", "rawtypes", "unchecked" })
	public static void main(String[] args) throws Exception {
		FutureTask<String> task = new FutureTask<>(new MyTicket1());
		new Thread(task).start();
		System.out.println(task.get());
	}
}

这种形式主要就是返回我们的操作结果。

1.8 线程命名和取得

线程本身是属于不可见的运行状态的,即:每次操作的时候是无法预料的,所以如果要想在程序之中操作线程,唯一依靠的就是线程的名称,而要想取得和设置线程的名称可以使用以下的方法:

方法名称 类型 描述
public Thread(Runnable target,String name); 构造 声明参数
public final void setName(String name); 普通 设置线程名称
public final String getName() 普通 取得线程名称

但是由于线程的状态不确定,所以每次可以操作的线程都是正在执行run()方法的线程,那么取得当前线程对象的方法:public static Thread currentThread()。

线程名称的取得

package com.day12.demo;

class MyThread implements Runnable{
	public void run(){
		System.out.println(Thread.currentThread().getName());
			}
}
public class Test {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		MyThread mt = new MyThread();
		new Thread(mt).start();//Thread-0
		new Thread(mt).start();//Thread-1
		new Thread(mt).start();//Thread-2
		new Thread(mt,"A").start();//A
		new Thread(mt,"B").start();//B
		new Thread(mt,"C").start();//C
	}
}

如果说现在为线程设置名字的话,那么会使用用户定义的名字,而如果没有设置线程名称,会自动分配一个名称这一点操作和之前讲解的static命名类似。

package com.day12.demo;
class MyThread2 implements Runnable{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getName());
	}

}
public class RenameThreadDemo {
	public static void main(String[] args) {
		MyThread2 mt = new MyThread2();
		mt.run();//直接通过对象调用方法
		new Thread(mt).start();
	}
}

观察以上程序我们发现,线程的启动都是通过主线程创建并启动的,主方法就是一个线程。

进程在哪里?

实际上每当使用了java命令去解释程序的时候,都表示启动了一个新的JVM进程。而主方法只是这个进程上的一个线程而已。

1.9 线程的休眠

线程的休眠指的是让程序休息一会等时间到了在进行执行。方法:public static void sleep(long millis) throws InterruptedException,设置的休眠单位是毫秒。

package com.day12.demo;
class MyThread implements Runnable{
	public void run(){
		for(int x=0;x<100;x++){
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"x="+x);
		}
	}
}
public class Test {
	public static void main(String[] args) throws Exception{
		// TODO Auto-generated method stub
		MyThread mt = new MyThread();
		new Thread(mt,"线程A").start();
		new Thread(mt,"线程B").start();
	}
}

1.10 线程的优先级

从理论上讲,线程的优先级越高,越有可能先执行。如果要想操作线程的优先级有如下两个方法:

设置线程优先级:public final void setPriority(int newPriority);

取得线程优先级:public final int getPriority();

发现设置取得优先级的时候都是利用一个int型数据的操作,而这个int型数据有三种取值:

​ **最高优先级:**public static final int MAX_PRIORITY,10;

​ **中等优先级:**public static final int NORM_PRIORITY,5;

​ **最低优先级:**public static final int MIN_PRIORITY,1;

设置优先级

package com.day12.demo;
class MyThread3 implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + "i = "+ i);
		}
	}

}
public class PriorityDemo {
	public static void main(String[] args) {
		MyThread3 mt = new MyThread3();
		Thread thread1 = new Thread(mt,"线程A");
		Thread thread2 = new Thread(mt,"线程B");
		Thread thread3 = new Thread(mt,"线程C");
		thread1.setPriority(Thread.MIN_PRIORITY);
		thread2.setPriority(Thread.MAX_PRIORITY);
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

主方法只是一个中等优先级。

1.11 线程的同步与死锁

所谓的同步问题是指多个线程操作统一次元所带来的信息安全性问题,

下面模拟一个简单的卖票程序,要求有3个线程,卖10张票。

package com.day12.demo;
class MyTicket implements Runnable{
	private int ticket = 10;
	public void run(){
		for (int i = 0; i < 20; i++) {
			if(ticket > 0){
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "买票 =" + this.ticket--);
			}

		}
	}
}
public class TicketDemo {
	public static void main(String[] args) {
		MyTicket mt = new MyTicket();
		new Thread(mt,"票贩子A").start();
		new Thread(mt,"票贩子B").start();
		new Thread(mt,"票贩子C").start();

	}
}

运行上面程序发现,票数为0或者为负数,这种操作我们称为不同步操作。

不同步的唯一好处就是处理速度快(多个线程并发执行),而去银行是一个业务员对应一个客户,这个速度必然很慢。数据的不同步对于访问是不安全的操作。

同步是指所有的线程不是一起进入方法中执行,而是一个一个进来执行。

如果要写实现这把锁的功能,那么可以使用synchronized关键字进行处理,有两种处理模式:同步代码块、同步方法。

同步代码块

如果要使用这种情况必须设置一个要锁定当前对象

package com.day12.demo;
class MyTicket implements Runnable{
	private int ticket = 2000;
	public void run(){
		for (int i = 0; i < 1000; i++) {
			//在同一时刻,只允许一个线程进入并且操作,其他线程需要等待
			synchronized(this){//线程的逻辑锁
				if(ticket > 0){
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "买票 =" + this.ticket--);
				}
			}

		}
	}
}
public class TicketDemo {
	public static void main(String[] args) {
		MyTicket mt = new MyTicket();
		Thread thread1 = new Thread(mt,"票贩子A");
		thread1.setPriority(Thread.MIN_PRIORITY);
		thread1.start();
		new Thread(mt,"票贩子B").start();
		new Thread(mt,"票贩子C").start();

	}
}

同步方法

package com.day12.demo;
class MyTicket implements Runnable{
	private int ticket = 2000;
	public void run(){
		for (int i = 0; i < 1000; i++) {
			//在同一时刻,只允许一个线程进入并且操作,其他线程需要等待
			synchronized(this){//线程的逻辑锁
				this.sale();
			}
		}
	}
	public synchronized void sale(){
		if(ticket > 0){
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "买票 =" + this.ticket--);
		}
	}
}
public class TicketDemo {
	public static void main(String[] args) {
		MyTicket mt = new MyTicket();
		Thread thread1 = new Thread(mt,"票贩子A");
		thread1.setPriority(Thread.MIN_PRIORITY);
		thread1.start();
		new Thread(mt,"票贩子B").start();
		new Thread(mt,"票贩子C").start();

	}
}

同步虽然可以保证数据的完整性(线程安全操作),但是其执行的速度很慢。

1.12 死锁

一个线程执行完毕后才可以继续执行,但是如果现在相关的几个线程,彼此几个线程都在等待(同步),那么就会造成死锁。

模拟死锁程序

package com.day12.demo;
class JieFei{
	public synchronized void say(Person Person){
		System.out.println("给钱放人");
		Person.get();
	}
	public synchronized void get(){
		System.out.println("得到钱");
	}
}
class Person{
	public synchronized void say(JieFei jiefei){
		System.out.println("放人就给钱");
		jiefei.get();
	}
	public synchronized void get(){
		System.out.println("得到人");
	}
}
public class DeadLock implements Runnable {
	JieFei jie = new JieFei();
	Person person = new Person();
	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		new DeadLock();
	}
	public DeadLock(){
		new Thread(this).start();
		jie.say(person);
	}
	@Override
	public void run() {
		// TODO 自动生成的方法存根
		person.say(jie);
	}
}

死锁实在日后多线程程序开发之中经常会遇见问题,而以上的代码并没有任何实际意义,大概可以理解死锁的操作形式就可以了,不用去研究程序。记住一句话:数据要想完整操作必须使用同步,但是过多的同步会造成死锁。

面试题:请问多线程操作统一资源的时候要考虑到那些,会带来的问题?

多线程访问统一资源的时候一定要考虑同步的问题,但是过多的同步会带来死锁。

综合案例

生产者和消费者是一道最为经典的供求案例:provider、consumer。不管是之后的分布式开发还是其他开发都被大量采用。

生产者只是负责生产数据,而生产者每生产一个完整的数据,消费者就把这个数据拿走。假设生产如下数据 title = 生产,note = 出库;title=拿货, note = 消费

package com.day12.demo;

class Message{
	private String title;
	private String note;
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getNote() {
		return note;
	}
	public void setNote(String note) {
		this.note = note;
	}

}
class Productor implements Runnable{
	Message msg = null;
	public Productor(Message msg){
		this.msg=msg;
	}

	@Override
	public void run() {
		// TODO 自动生成的方法存根
		for(int x = 0;x<50;x++){
			if(x%2==0){
				try {
					msg.setTitle("生产");
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
				msg.setNote("出库");
			}else{
				msg.setNote("拿货");
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
				msg.setNote("消费");
			}
		}
	}
}
class Consumer implements Runnable{
	Message msg = null;
	public Consumer(Message msg){
		this.msg=msg;
	}
	@Override
	public void run() {
		// TODO 自动生成的方法存根
		for(int x= 0;x<50;x++){
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
			System.out.println(this.msg.getTitle()+"--->"+this.msg.getNote());
		}
	}
}
public class PCDemo {
	public static void main(String args[]){
		// TODO 自动生成的方法存根
		Message msg = new Message();
		Productor pro = new Productor(msg);
		Consumer con = new Consumer(msg);
		new Thread(pro).start();
		new Thread(con).start();
	}
}

但是异常的代码模型出现了如下的两个严重的问题:

数据错位了出现了重复取出和重复设置的问题

1.解决数据错位问题:依靠同步解决

package com.day12.demo;

class Message {
	private String title;
	private String note;

	public synchronized void set(String title, String note) {
		try {
			Thread.sleep(50);
		} catch (InterruptedException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		this.title = title;

		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		this.note = note;
	}

	public synchronized void get() {
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		System.out.println(this.title + "--->" + this.note);
	}
}

class Productor implements Runnable {
	Message msg;

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

	@Override
	public void run() {
		// TODO 自动生成的方法存根
		for (int x = 0; x < 50; x++) {
			if (x % 2 == 0) {
				msg.set("生产", "出库");
			} else {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
				msg.set("拿货", "消费");
			}
		}
	}
}

class Consumer implements Runnable {
	Message msg = null;

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

	@Override
	public void run() {
		// TODO 自动生成的方法存根
		for (int x = 0; x < 50; x++) {
			msg.get();
		}
	}
}

public class PCDemo {
	public static void main(String args[]) {
		// TODO 自动生成的方法存根
		Message msg = new Message();
		Productor pro = new Productor(msg);
		Consumer con = new Consumer(msg);
		new Thread(pro).start();
		new Thread(con).start();
	}
}

虽然解决了错位的问题,但是重复设置重复取出更加严重了。

2.解决数据的重复设置和重复取出

要想解决重复的问题需要等待及唤醒机制,而这一机制的实现只能依靠Object类完成,在Object类之中定义了以下的三个方法完成线程操作:

方法名称 类型 描述
public final void wait() throws InterruptedException 普通 等待、死等
public final void notify() 普通 唤醒第一个等待线程
public final void notifyAll() 普通 唤醒全部等待线程

通过等待与唤醒机制来解决数据的重复操作问题

package com.day12.demo;

class Message {
	private String title;
	private String note;
	//flag = true  允许生产但是不允许消费者取走
	//flag = false 生产完毕 允许消费者取走,但是不能够生产
	private boolean flag = false;
	public synchronized void set(String title, String note) {
		if(flag == true){//生产完毕 允许消费者取走,但是不能够生产
			try {
				super.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		this.title = title;
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		this.note = note;
		flag = true;
		super.notify();
	}

	public synchronized void get() {
		if(flag == false){ //允许生产但是不允许消费者取走
			try {
				super.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		try {
			Thread.sleep(50);
		} catch (InterruptedException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		System.out.println(this.title + "--->" + this.note);
		flag = false;
		super.notify();
	}
}

class Productor implements Runnable {
	Message msg;

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

	@Override
	public void run() {
		// TODO 自动生成的方法存根
		for (int x = 0; x < 50; x++) {
			if (x % 2 == 0) {
				msg.set("生产", "出库");
			} else {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
				msg.set("拿货", "消费");
			}
		}
	}
}

class Consumer implements Runnable {
	Message msg = null;

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

	@Override
	public void run() {
		// TODO 自动生成的方法存根
		for (int x = 0; x < 50; x++) {
			msg.get();
		}
	}
}

public class PCDemo {
	public static void main(String args[]) {
		// TODO 自动生成的方法存根
		Message msg = new Message();
		Productor pro = new Productor(msg);
		Consumer con = new Consumer(msg);
		new Thread(pro).start();
		new Thread(con).start();
	}
}

面试题:请解释sleep()和wait()的区别?

  • sleep()是Thread类中定义的方法,到了一定的时间后该休眠的线程自动唤醒,自动唤醒。
  • wait()是Object类中定义的方法,如果要想唤醒,必须使用notify()、notifyAll()才能唤醒,手动唤醒。

到此这篇关于Day12基础不牢地动山摇-Java基础的文章就介绍到这了,更多相关Java基础内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java多线程的调度_动力节点Java学院整理

    有多个线程,如何控制它们执行的先后次序?         方法一:设置线程优先级. java.lang.Thread 提供了 setPriority(int newPriority) 方法来设置线程的优先级,但线程的优先级是无法保障线程的执行次序的,优先级只是提高了优先级高的线程获取 CPU 资源的概率.也就是说,这个方法不靠谱.       方法二:使用线程合并. 使用 java.lang.Thread 的 join() 方法.比如有线程 a,现在当前线程想等待 a 执行完之后再往下执行,那就

  • 详解Java中的线程模型与线程调度

    JAVA线程模型 线程的实现主要有3种方式: 使用内核线程实现(1:1) 使用用户线程实现(1:N) 使用用户线程加轻量级进程实现(N:M) 使用内核线程实现(Kernel-Level Thread, KLT)(1:1) 内核线程就是直接由操作系统内核支持的线程,这种线程由内核来完成线程的切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上. 程序一般不会直接去使用内核,而是去使用线程的一种高级接口--轻量级进程(Light Weight Process,LWP),轻量级

  • Java线程的调度与优先级详解

    目录 示例: 1.定义一个线程执行体,异步执行: 2.创建10个线程,并设置不同的线程优先级,来执行线程执行体: 3.运行结果: 总结 由于CPU的计算频率非常高,每秒计算数十亿次,因此可以将CPU的时间从毫秒的维度进行分段,每一小段叫作一个CPU时间片. 目前操作系统中主流的线程调度方式是:基于CPU时间片方式进行线程调度.线程只有得到CPU时间片才能执行指令,处于执行状态,没有得到时间片的线程处于就绪状态,等待系统分配下一个CPU时间片.由于时间片非常短,在各个线程之间快速地切换,因此表现出

  • java线程优先级原理详解

    java 中的线程优先级的范围是1-10,默认的优先级是5.10最高. MIN_PRIORITY 1 MAX_PRIORITY 10 NORM_PRIORITY 5 优先级高的获得cpu的几率更大些,不是优先级高的就先执行完,线程优先级随机特性 在java中,线程的优先级具有继承性,例如A线程启动B线程,则A和B的优先级是一样的 线程创建后,可通过调用setPriority()方法改变优先级. public class Test5 { public static class TheadT ext

  • Java 线程的优先级(setPriority)案例详解

    线程可以划分优先级,优先级高的线程得到的CPU资源比较多,也就是CPU优先执行优先级高的线程对象中的任务. 设置线程优先级有助于帮助线程规划器确定下一次选中哪一个线程优先执行. java中优先级分为1-10个级别 线程优先级的继承特性 例如a线程启迪b线程,则b线程的优先级与a的一样. 代码说话:(很简单) public class MyThread1 extends Thread { @Override public void run() { System.out.println("MyThr

  • Java线程调度之线程休眠用法分析

    本文实例分析了Java线程调度之线程休眠用法.分享给大家供大家参考.具体分析如下: Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率.   这里要明确的一点,不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制.   线程休眠的目的是使线程让出CPU的最简单的做法之一,线程休眠时候,会将CPU资源交给其他线程,以便能轮换执行,当休眠一定时间后,线程会苏醒,进入准备状态等待执行.   线程休眠的方法是Thread.sleep

  • Java多线程与优先级详细解读

    目录 1.多线程 1.1多线程的基本概念 1.2多线程的实现 1.3继承Thread类实现多线程 1.4Runnable接口实现多线程 1.5Thread类和Runnable接口实现多线程的区别 1.6线程的操作状态 1.7Callable实现多线程 1.8线程命名和取得 1.9线程的休眠 1.10线程的优先级 1.11线程的同步与死锁 1.12死锁 综合案例 1.解决数据错位问题:依靠同步解决 2.解决数据的重复设置和重复取出 面试题:请解释sleep()和wait()的区别? 1.多线程 要

  • Java多线程的用法详细介绍

    Java多线程的用法详细介绍 最全面的Java多线程用法解析,如果你对Java的多线程机制并没有深入的研究,那么本文可以帮助你更透彻地理解Java多线程的原理以及使用方法. 1.创建线程 在Java中创建线程有两种方法:使用Thread类和使用Runnable接口.在使用Runnable接口时需要建立一个Thread实例.因此,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例.Thread构造函数: public Thread( ); publi

  • java的IO流详细解读

    流,就是一系列的数据. 当不同介质之间有数据交互的时候,JAVA就使用流来实现.数据源可以是文件,还可以是数据库.网络甚至其他的程序. 比如读取文件的数据到程序中,站在程序的角度来看,就叫做输入流. 字节流(以字节的形式读取和写入数据) InputStream字节输入流同时也是抽象类,只提供方法声明,不提供方法的具体实现. FileInputStream是InputStream的子类,下面以FileInputStream为例进行文件读取 package testIO; import java.i

  • Java多线程 原子操作类详细

    目录 1.What and Why 2.原子更新基本类型类 3.实现原理 4.原子更新数组 5.原子更新引用类型 6.原子更新字段类 1.What and Why 原子的本意是不能被分割的粒子,而对于一个操作来说,如果它是不可被中断的一个或者一组操作,那么他就是原子操作.显然,原子操作是安全的,因为它不会被打断. 平时我们见到的很多操作看起来是原子操作,但其实是非原子操作,例如很常见的i++操作,它背后有取值.加一.写回等操作,如果有两个线程都要对 i 进行加一操作,就有可能结果把i只变成了2,

  • 浅谈java多线程编程

    一.多线程的优缺点 多线程的优点: 1)资源利用率更好 2)程序设计在某些情况下更简单 3)程序响应更快 多线程的代价: 1)设计更复杂 虽然有一些多线程应用程序比单线程的应用程序要简单,但其他的一般都更复杂.在多线程访问共享数据的时候,这部分代码需要特别的注意.线程之间的交互往往非常复杂.不正确的线程同步产生的错误非常难以被发现,并且重现以修复. 2)上下文切换的开销 当CPU从执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据

  • 详细解读JAVA多线程实现的三种方式

    最近在做代码优化时学习和研究了下JAVA多线程的使用,看了菜鸟们的见解后做了下总结. 1.继承Thread类实现多线程 继承Thread类的方法尽管被我列为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法.start()方法是一个native方法,它将启动一个新线程,并执行run()方法.这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run(

  • Java Proxy机制详细解读

    动态代理其实就是java.lang.reflect.Proxy类动态的根据您指定的所有接口生成一个class byte,该class会继承Proxy类,并实现所有你指定的接口(您在参数中传入的接口数组):然后再利用您指定的classloader将 class byte加载进系统,最后生成这样一个类的对象,并初始化该对象的一些值,如invocationHandler,以即所有的接口对应的Method成员. 初始化之后将对象返回给调用的客户端.这样客户端拿到的就是一个实现你所有的接口的Proxy对象

  • 运用示例详细总结Java多线程

    目录 进程与线程 Java中线程实现的方式 实现 Runnable 接口 继承 Thread 类 Thread 类和 Runnable 接口 线程的状态变化 取得和设置线程的名称 线程的操作方法 线程的强制运行 线程的休眠 中断线程 后台线程 线程的优先级 线程的礼让 同步以及死锁 同步代码块 同步方法 进程与线程 进程是程序的一次动态执行过程,它需要经历从代码加载,代码执行到执行完毕的一个完整的过程,这个过程也是进程本身从产生,发展到最终消亡的过程.多进程操作系统能同时达运行多个进程(程序),

  • 一文精通Java 多线程之全方位解读

    目录 并行和并发 线程基础概念 线程和进程 多线程的好处 线程的状态 实现多线程的两种方式 继承Thread类 实现Runnable接口 线程的安全性和原子性 锁的概念和使用 生产消费者模型 生产消费者模型中的类–存储类 生产消费者模型中的类–生产者 生产消费者模型中的类–消费者 测试类 效果 volatile变量 线程池的概念和使用 并行和并发 并行:多个CPU实例或是多台机器同时执行一段处理逻辑,是真正的同时. 并发:一个CPU或一台机器,通过CPU调度算法,让用户看上去同时去执行,实际上从

  • Java 多线程优先级实例详解

    Java 多线程优先级实例详解 线程的优先级将该线程的重要性传递给调度器.尽管CPU处理现有线程集的顺序是不确定的,但是调度器将倾向于让优先权最高的线程先执行. 你可以用getPriority()来读取现有线程的优先级,并且在任何时刻都可以通过setPriority()来修改优先级. import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SimplePrio

随机推荐