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方法启动线程;
单线程的程序模式:
方法的调用,永远都是基于单线程的模式来进行的。
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
都是用于控制线程同步的方法,后面进行讲解。
四、线程状态转换
图示结构:
线程创建:建立一个线程对象。
就绪状态:当一个线程对象创建后,其他线程调用它的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