首页 > 编程语言 >JavaSE【9】-Java多线程

JavaSE【9】-Java多线程

时间:2024-04-19 15:01:01浏览次数:32  
标签:Java Thread void start 线程 new JavaSE 多线程 public

JavaSE【9】-Java多线程

synchronized 修饰符(方法)------表示这个方法被同步了,就是基于线程安全的;

集合容器----有一些集合容器是基于线程同步的(集合的内部使用的方法是基于synchronized来修饰的);

一、线程相关概念

进程和线程的概念:

◆进程就是正在执行的程序,一个进程通常就是一个正在执行的应用程序。从Windows角度
  讲,进程是含有内存和资源并安置线程的地方。
  
◆线程是一个程序内部的执行顺序控制流(或者叫程序执行的路径)。

进程和线程的区别:

◆进程是具有一定独立功能的程序,都有独立的代码和数据空间,进程是系统进行资源分配
  和调度的一个独立单位。
  
◆线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基
  本单位,线程自己基本上不拥有系统资源,只有一些在运行中必不可少的资源(如程序计数
  器,寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
  
   多进程:在操作系统中能同时运行多个任务(程序)。
   多线程:在同一应用程序中有多个顺序流同时执行。

简洁汇总:

1、进程就是一个独立的应用程序,拥有自己独立的资源体系,一个进程下包含多个线程;

2、线程是一个应用程序内部执行的路径,一个进程中可以有多个线程并行;

3、进程的概念到大于线程的概念;

二、线程的创建和启动

在Java中想要创建出一个线程,可以有4种方式:

1、继承Thread类来实现 

2、实现Runnable接口

3、实现Callable接口

4、线程池技术

2.1、继承Thread类创建线程

package com.it.www.threadapp;

public class Demo1 {

	public static void main(String[] args) {

		// 创建一个mt的线程对象
		MyThread01 mt = new MyThread01();
		// 启动线程----使用的是start方法
		mt.start(); // -----线程的启动
	    //mt.run(); //---是方法的调用(不是多线程的用法)

		for (int i = 1; i <= 5; i++) {
			// 让当前的线程睡觉(指定一个时间)
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("====main======执行:" + i);
		}
		
		
	}

}

/**
 * 在一个Java的源文件中,可以同时并列定义多个class类,但是都不是主类。 主类:就是使用public修饰的类,必
   须要与源文件的名称保持一致。
 * 也就是说一个源文件中最多只能有一个主类,并且和源文件的名字是相同的。
 */

// 自定义一个线程类(因为Java内部提供了一个线程类 Thread )
class MyThread01 extends Thread {

	// 重写Thread类中的run方法,这个方法就是线程体
	// 线程体:就是这个线程能够做的事。
	@Override
	public void run() {

		for (int i = 1; i <= 5; i++) {

			// 让当前的线程睡觉(指定一个时间)
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			System.out.println("---MyThread01----执行:" + i);
		}

	}

}

总结!

通过继承Thread类来实现线程的步骤:

1、自定义一个类继承Thread类;

2、重写run方法,来定义线程体;

3、创建线程对象并使用start方法进行启动;

2.2、实现Runnable接口创建线程

package com.it.www.threadapp;

public class Demo2 {

	public static void main(String[] args) {
		
		//创建线程对象==============
		//1、创建Runnable接口实现类的对象
		MyThread02   mt = new  MyThread02();
		//2、构建一个Thread线程类的对象,将mt作为参数传入。--------把mt对象包装为线程类对象
		Thread   t = new  Thread(mt);
		
		//启动线程
		t.start();

		for (int i = 1; i <= 5; i++) {
			try {
				// 线程的休眠的方法,指定时间为毫秒
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			// Thread.currentThread() ---- 获取当前线程对象
			System.out.println(Thread.currentThread().getName()
					+ "----------main-------------");
		}

	}

}

// 通过实现Runnable的接口来定义线程
class MyThread02 implements Runnable {

	// 实现run方法,用于定义线程体
	@Override
	public void run() {

		for (int i = 1; i <= 5; i++) {

			try {
				// 线程的休眠的方法,指定时间为毫秒
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			// Thread.currentThread() ---- 获取当前线程对象
			System.out.println(Thread.currentThread().getName()
					+ "-----------------------");
		}

	}

}

总结!

通过实现Runnable接口实现线程的步骤:

1、自定义一个类实现Runnable接口;

2、实现类中实现run方法,来定义线程体;

3、创建一个Thread类的对象,将实现类的对象作为参数传入,定义一个线程对象;

4、使用start方法启动线程;

单线程的程序模式:

1642044937648

方法的调用,永远都是基于单线程的模式来进行的。

2.3、实现Callable接口创建线程

/**
 * 通过实现Callable接口的形式来实现自定义线程类的步骤:
 * 1、自定义的类实现Callable接口;
 * 2、实现Callable接口中的call方法(线程体);
 * 3、特点:Callable接口方式来实现线程,可以获取到线程体中的返回结果。
 */
public class MyThread03   implements Callable {

    @Override
    public Object call() throws Exception {
        for(int i=1;i<=5;i++){
            Thread.sleep(1000);
            System.out.println("MyThread03 中执行第"+i+"次!");
        }
        return 100;
    }
}


public class MyTest3 {

    public static void main(String[] args) {

        System.out.println("main方法开始执行============");

        //创建出一个新的线程,并进行启动
        MyThread03  myThread03 = new MyThread03();
        //创建一个FutureTask的计划任务
        FutureTask  futureTask = new FutureTask(myThread03);
        //FutureTask实现类-----RunnableFuture接口-----父接口Runnable
        //最后再创建Thread类的对象,作为参数进行传递
        Thread  thread = new Thread(futureTask);
        thread.start();
        
        try {
            //获取线程体中的返回结果(别的方式不具备的)
            Object result = futureTask.get();
            System.out.println(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }


        for(int i=1;i<=5;i++){
            try {
                //睡觉1秒钟
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main方法中执行第"+i+"次!");
        }
        System.out.println("main方法执行结束============");
    }
}

2.4、创建线程2种形式的比较

在没有要求和限制的情况下,我们创建一个线程,应该使用实现接口的方式:

1、因为实现接口的依赖性不强(不是强耦合);

2、继承只能是支持单继承,所以不是很灵活,依赖性很强;

三、线程操作API

isAlive :检测线程是否存活

boolean  alive = t.isAlive();
System.out.println(alive);

setPriority : 设置线程优先级

getPriority:获取线程的优先级

package com.it.www.threadapp;

public class Demo3 {

	public static void main(String[] args) {
		
		MyThread03   mt1 = new  MyThread03("T1");
		MyThread03   mt2 = new  MyThread03("T2");
		MyThread03   mt3 = new  MyThread03("T3");
		
		//设置线程的优先级
		mt1.setPriority(10);
		mt2.setPriority(Thread.NORM_PRIORITY);
		mt3.setPriority(Thread.MIN_PRIORITY);
		
		//启动三个子线程
		mt1.start();
		mt2.start();
		mt3.start();
		
		//线程是存在一个执行的优先级别,是通过一个数值来进行表示的,数值越大,执行的优先级就越高
		int   priv1 = mt1.getPriority();
		int   priv2 = mt2.getPriority();
		int   priv3 = mt3.getPriority();
		
		//每个新创建的线程,默认的优先级是 5  ,其范围是1----10。
		System.out.println(priv1+"-----"+priv2+"----"+priv3);

	}

}

//定义一个线程类
class    MyThread03  extends  Thread{
	
	//通过构造的形式给线程取个名称
	public  MyThread03(String  name){
		super(name);
	}
	
	
	@Override
	public void run() {
		
		for(int i=1;i<=5;i++){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"-----------"+i);
		}
		
	}
	
}

提示!

线程的执行与否,关联到许多的因素,其中线程的优先级也是执行机会提升的一个很重要的因素,但是并不是绝对性的,也就说不一定就是优先级高的线程首先执行完毕。

sleep:在指定时间内线程休眠

(在线程睡觉的过程中,是否拥有CPU的执行权?)

join : 线程的合并

package com.it.www.threadapp;

public class Demo4 {

	public static void main(String[] args) {

		// 创建一个子线程
		MyThread04 mt1 = new MyThread04("T1");
		// 启动三个子线程
		mt1.start();

		for (int i = 1; i <= 5; i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "-----------"
					+ i);
		}

	}

}

// 定义一个线程类
class MyThread04 extends Thread {

	// 通过构造的形式给线程取个名称
	public MyThread04(String name) {
		super(name);
	}

	@Override
	public void run() {

		for (int i = 1; i <= 5; i++) {
			if (i == 3) {
				try {
					//合并线程(当前这个子线程被合并了,就是后续的操作不再执行,回到未执行完毕的主线程上继续以单线程的形式进行执行)
					this.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "-----------"
					+ i);
		}
	}
}

yield : 让出的意思,就是将当前线程的执行权进行让出

package com.it.www.threadapp;

public class Demo5 {

	public static void main(String[] args) {

		// 创建一个子线程
		MyThread05 mt1 = new MyThread05("T1");
		// 启动三个子线程
		mt1.start();

		for (int i = 1; i <= 5; i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "-----------"
					+ i);
		}

	}

}

// 定义一个线程类
class MyThread05 extends Thread {

	// 通过构造的形式给线程取个名称
	public MyThread05(String name) {
		super(name);
	}

	@Override
	public void run() {

		for (int i = 1; i <= 5; i++) {
			if (i == 3) {
					//让出执行权,其他线程可以获取到CPU的执行权,从而进行执行,但是也有可能,当前这个线程再次获取到执行权,继续进行执行;
					this.yield();
			}
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "-----------"
					+ i);
		}

	}
}

stop : 终止线程的执行,这个方法是直接将线程杀死,比较粗暴

wait 、 notify 、 notifyAll

都是用于控制线程同步的方法,后面进行讲解。

四、线程状态转换

图示结构:

1642066405458

线程创建:建立一个线程对象。

就绪状态:当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态
处于这个状态的线程位于Java虚拟机的可运行池中,等待CPU的使用权。

运行状态:处于这个状态的线程占用CPU,执行程序代码。在并发运行环境中,如果计算机只
有一个CPU,那么任何时刻只会有一个线程处于这个状态。如果计算机有多个CPU,那么同一
时刻可以让几个线程占用不同的CPU,使它们都处于运行状态。只有处于就绪状态的线程才有
机会转到运行状态。

阻塞状态:是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才有机会转到运行状态。

阻塞状态可分为3种:
位于对象等待池中的阻塞状态:当线程处于运行状态,执行了某个对象的wait()方法。
位于对象锁池中的阻塞状态:当线程处于运行状态,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用。
其他阻塞状态:当前线程执行了sleep()方法,或者调用了其他线程的join()方法。

死亡状态:当线程执行完run()方法中的代码,或者遇到了未捕获的异常,就会退出run()方法,此时就进入死亡状态,该线程结束生命周期。 

说明:

线程共有5种状态: 创建 、 就绪状态 、 运行状态 、 阻塞状态 、 死亡状态

创建好一个线程之后,调用start方法并不是就进入了运行状态,而是进入了就绪状态(就是具备线程执行的一切条件)。再操作系统的分配中才会进入到执行的状态。由于各种情况可能会导致阻塞,解除阻塞后进入的是就绪状态,再进入到运行的状态。

五、线程同步【重难点】

5.1、对象锁定

package com.it.www.threadapp;
/**
 * 线程同步的运用前提:
 * 1、一定是在一个多线程的环境中;
 * 2、一定是针对一个共享的资源来进行操作;
 * 
 * 最终应该构成的局面 : 多个线程去争夺一个资源。
 */
public class Demo6 {

	public static void main(String[] args) {
		
		Work  work1 = new  Work();

		// 创建多个子线程----充当不同的人
		MyThread06 mt1 = new MyThread06("张三",work1);
		mt1.start();
		
		//Work  work2 = new  Work();
		MyThread06 mt2 = new MyThread06("李四",work1);
		mt2.start();

	}

}

class Work {

	// 建立一个同步的方法(此方法只能在同一个时刻允许一个线程执行,只有一个线程执行完毕后,才可以让第二个线程进入执行)
	public synchronized void work() {
		System.out.println(Thread.currentThread().getName()+"================");
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("----work.........");
	}

}

// 定义一个线程类
class MyThread06 extends Thread {

	// 定义的是Work类的对象
	private Work work;

	// 通过构造的形式给线程取个名称
	public MyThread06(String name, Work work) {
		super(name);
		this.work = work;
	}

	@Override
	public void run() {
		// 开始工作-----调用work()方法
		
		work.work();
	}

}

synchronized :同步;

多个线程针对共同的一个共享资源进行访问,通过synchronized 进行有序的控制(当一个线程执行的时候,另一个线程处理等待的状态,当第一个线程执行完毕后,第二个线程进入开始执行)。

线程同步的本质:

其实 synchronized 在修饰work这个方法的时候,表示的是锁定了Work这个类的对象。这个锁定的情况,我们称为“对象锁”。一旦一个对象被锁定,那么另外需要锁定这个对象的操作是无法实现的。

5.2、锁定类

package com.it.www.threadapp;
/**
 * 此案例说明2个问题:
 * 1、静态的方法也是可以同步的,锁定的是类的字节码;
 * 2、同步的方法锁定类的字节码和锁定类的对象是不会形成互斥的(各自独立不受影响);
 */
public class Demo7 {

	public static void main(String[] args) {

		//访问静态的方法----5秒的事件范围内访问  work方法
		MyThread07 mt1 = new MyThread07("张三");
		mt1.start();

		//访问的是非静态的方法-----5秒的事件范围内访问 test方法
		MyWork  myWork = new MyWork();
		MyThread08 mt2 = new MyThread08("李四",myWork);
		
		mt2.start();
	}
}

class MyWork {
	
	// 建立一个同步的方法(此方法是一个静态的方法,锁定的是这个类的字节码,也是会形成互斥的)
	public  static  synchronized void work() {
		System.out.println(Thread.currentThread().getName()+"================");
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("----work.........");
	}
	
	//非静态的同步方法------锁定的是对象
	public    synchronized   void   test(){
		System.out.println(Thread.currentThread().getName()+"================");
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("----test.........");
	}
	
}

// 定义一个线程类
class MyThread07 extends Thread {

	// 通过构造的形式给线程取个名称
	public MyThread07(String name) {
		super(name);
	}

	@Override
	public void run() {
		// 开始工作-----调用work()方法
		MyWork.work();
	}
	
}

//定义一个线程类
class MyThread08 extends Thread {
	
	private   MyWork  myWork;

	// 通过构造的形式给线程取个名称
	public MyThread08(String name,MyWork  myWork) {
		super(name);
		this.myWork = myWork;
	}

	@Override
	public void run() {
		//----调用test()方法
		myWork.test();
	}
	
}

注意!

锁定类 和 锁定对象之间是没有任何联系的,更不会形成互斥。

5.3、生产与消费

package com.it.www.threadapp;

public class 生产与消费 {

	public static void main(String[] args) {

		容器 x = new 容器();
		
		生产者 a1 = new 生产者(x);
		生产者 a2 = new 生产者(x);
		生产者 a3 = new 生产者(x);

		消费者 b1 = new 消费者(x);
		消费者 b2 = new 消费者(x);
		消费者 b3 = new 消费者(x);
		
		a1.start();
		a2.start();
		a3.start();
		b1.start();
		b2.start();
		b3.start();

	}

}

/**
 * 实体
 * 
 * @author Administrator
 * 
 */
class 西瓜 {

}




class 容器 {

	西瓜[] s = new 西瓜[5];

	int index = 0;

	// 放西瓜的方法
	public synchronized void set() {

		西瓜 x = new 西瓜();

		// 放满了
		while(index == s.length) {
			try {
				this.wait();  //处于等待状态的线程,只有通过notifyAll来进行唤醒;
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		this.notifyAll();//唤醒别的处于等待状态的多个线程。
		
		s[index] = x;
		System.out.println("------------生产了一个西瓜!");
		index++;
	}

	// 取西瓜的方法
	public synchronized void get() {
		
		while(index == 0) {
			try {
				this.wait();
				//通过notifyAll唤醒后,是继续从wait后开始执行。
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		index--;
		西瓜 x = s[index];
		this.notifyAll();//唤醒别的处于等待状态的多个线程。
		
		System.out.println("==============消费了一个西瓜!");
	}

}

class 生产者 extends Thread {

	容器 s;

	public 生产者(容器 s) {
		super();
		this.s = s;
	}

	@Override
	public void run() {

		while (true) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//向容器中添加东西
			s.set();
		}

	}

}

class 消费者 extends Thread {

	容器 s;

	public 消费者(容器 s) {
		super();
		this.s = s;
	}

	@Override
	public void run() {
		while (true) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//从容器中取东西
			s.get();
		}
	}
}

5.4、线程同步总结

在程序中设计线程同步的机制有如下几个好处:

1、在多线程的并发访问环境中,可以比较有效的保证数据操作的完整性和一致性;

2、可以防止数据信息的错乱或者破坏;

3、我们获得了多线程环境中数据操作的安全性和完整性,同时也牺牲了操作的效率性;

标签:Java,Thread,void,start,线程,new,JavaSE,多线程,public
From: https://www.cnblogs.com/hardrockstudy/p/18145893

相关文章

  • JavaScript技术
    JavaScript技术一、JavaScript的定义JavaScript是一种【基于对象】和【事件驱动】的【脚本语言】,在客户端执行,客户端主要实现数据的验证和页面的特效,大幅度提高网页的速度和交互的能力,在互联网中得到了广泛的运用。基于对象:js是基于面向对象的。事件驱动:使用的时候是结合......
  • day16_我的Java学习笔记 (Set、案例、Collections、Map、集合嵌套)
    1.Set系列集合1.1Set系列集系概述1.2HashSet元素无序的底层原理:哈希表JDK1.7HashSet原理解析:JDK1.8HashSet原理解析:1.3HashSet元素去重复的底层原理Set集合去重复的原因,先判断哈希值,再判断equals重写equals()和HashCode()方......
  • 记录在JavaScript中对事件循环的理解
    JavaScript事件循环通俗解释好的,用更通俗的话来说,事件循环就像是在一个大剧院里,有一个演员(JavaScript引擎)和两个重要的角色:一个是前台的表演者(调用栈),另一个是后台的候场区(事件队列)。前台表演者:这个演员在前台表演,一次只能表演一个节目(单线程执行)。当一个节目(函数)开始时,演员就上......
  • 前端如何使用Javascript实现一个简单的发布订阅模式
    在前端开发中,我们经常需要处理事件的订阅与发布,以实现组件之间的解耦和通信。本文将介绍如何使用JavaScript实现一个简单的发布订阅模式,通过分步写代码的方式,带领读者一步步完成实现过程。步骤一:定义EventEmitter类首先,我们需要定义一个名为EventEmitter的类,作为发布订阅......
  • Java项目调用 WebService
    Java项目调用WebService序言:原本接触和二开的Java项目都是使用Spring框架,并且使用的接口都是RestFul风格,今天有一个Kingdee项目是使用WSDL文件提供接口通过WebService的方式来进行接口方式的相互通讯;因为是第一次使用WebService方式,所以写下这篇文档留作参考记......
  • java 编译问题
    背景我在maven中配置了私有仓库地址后,发现一些包还是会从外网拉,耗时很长。mirror配置为:<mirrors><id>mynexus</id><name>mynexusname</name><mirrorOf>central</mirrorOf><url>http://mynexus.aaa.com/nexus/repositry/test/</url><......
  • day15_我的Java学习笔记 (Collection、数据结构、List、泛型深入)
    1.集合概述2.Collection集合的体系特点3.Collection集合常用API1.添加元素,添加成功返回true,失败返回false2.清空集合的元素3.判断集合是否为空,是空返回true,反之为false4.获取集合的大小5.判断集合中是否包含某个元素6.删除某个元素(......
  • 使用bat切换java版本环境变量
    使用bat切换java版本环境变量需求有多个项目,每个项目依赖的java版本不同,需要切换java版本。或者想试用java新版本新特性,需要切换java版本。针对以上情况,Windows情况虽然修改一下环境变量JAVA_HOME即可,但也相对繁琐,一开始在找有没有类似nvm这种工具,但是找的过程看到一篇文章......
  • Java的六种线程状态及代码示例
    Java的线程有6个状态,分别是NEW           新建状态。刚new出来的thread,还没有调用start,就是这个状态RUNNABLE     运行状态(分为运行中和就绪)。正在运行,或者等待CPU调度,调用完start,就是这个状态BLOCKED       阻塞状态。还未竞争......
  • Java开发者如何使用RunFlow内置的QLExpress
    本文是为Java开发者写的手册,如果您不是Java开发者可以阅读我们的开发者篇手册,当然如果您感兴趣也可以继续阅读。输入qe进入QLExpress专注模式。执行Java代码比如数学计算:Math.sin(9);执行结果:0.4121184852417566。比如读取系统环境变量:top.myrest.myflow.util.Jackson......