多线程编程
- 学习目标
- 一、进程与线程
- 二、Java多线程实现
-
-
- 范例:定义线程类
- 2.1Runnable接口实现多线程
- 2.2、Thread与Runnable 的区别
- 2.3、Callable接口实现多线程
- 2.4、多线程运行状态
-
- 三、多线程的常用操作方法
-
- 3.1、线程的命名和取得
- 3.2、线程休眠
- 3.3、线程中断
- 3.4、线程强制执行
- 3.5、线程礼让
- 3.6、线程的优先级
- 四、线程的同步与死锁
-
-
- 4.1、线程同步引出
- 4.2、线程同步处理
- 4.3、线程死锁
-
- volatile关键字
- 总结
学习目标
1、理解进程与线程的联系与区别。
2、Java中两种多线程的两种实现方式及区别。
3、掌握线程的基本操作方法,实现线程的休眠礼让等操作。
4、多线程同步与死锁的概念。
5、synchronized ()同步实现操作。
6、理解Object类对多线程的支持。
7、理解线程生命周期。
一、进程与线程
进程是程序的一次动态执行过程。多进程操作同时运行多个进程。
二、Java多线程实现
Java.lang.Thread 是一个负责线程操作的类,任何类只要继承Thread类就可以成为一个线程的主类。
同时线程类中需要明确覆写父类中的run()方法,当产生若干个线程类对象,这些对象就会并发执行run()方法的代码。
范例:定义线程类
public class JavaDemo2 {
public static void main(String[] args) {
new Mytheard("线程A").start(); //实例化线程并启动
new Mytheard("线程B").start(); //实例化线程并启动
new Mytheard("线程C").start(); //实例化线程并启动
}
}
class Mytheard extends Thread{ //线程的主体类
private String name; //成员属性
public Mytheard(String name){ //属性初始化
this.name = name;
}
@Override
public void run(){ //方法覆写:线程方法
for (int x = 0;x < 10;x++){
System.out.println(this.name+"运行,x="+x);
}
}
}
程序输出结果:
线程B运行,x=0
线程A运行,x=0
线程C运行,x=0
线程A运行,x=1
线程B运行,x=1
剩下的略
发现多个线程之间彼此交替执行,所以每次执行结果不一样,启动线程必须依靠Thread类的start()方法执行,线程启动后会默认调用run()方法。
2.1Runnable接口实现多线程
使用Thread类的确方便实现多线程,最大的缺点单继承局限,也可以利用Runnable接口来实现多线程,
接口定义如下:
@FunctionalInterface
public interface Runnable{
public void run();
}
范例:通过Runnable接口实现多线程
public class JavaDemo3 {
public static void main(String[] args) {
Thread threadA = new Thread(new Mythaer("线程对象A"));
Thread threadB = new Thread(new Mythaer("线程对象B"));
Thread threadC = new Thread(new Mythaer("线程对象C"));
threadA.start();
threadB.start();
threadC.start();
}
}
class Mythaer implements Runnable { //线程的主体类
public String title; //成员属性
public Mythaer(String title){ //属性初始化
this.title = title;
}
@Override
public void run() { //方法覆写:线程方法
for (int i = 0;i < 6;i++){
System.out.println(this.title+"运行,x="+i);
}
}
}
程序输出结果:
线程对象B运行,x=0
线程对象C运行,x=0
线程对象A运行,x=0
线程对象C运行,x=1
线程对象B运行,x=1
剩余略
2.2、Thread与Runnable 的区别
现在Thread类和Runnable类接口都可以以同一功能来实现多线程,从Java的实际开发而言,肯定使用Runnable接口,因为可以有效避免单继承的局限,
Thread类的定义:
public class Thread extends Object implements Runnable {}
范例:并发资源访问
public class JavaDemo4 {
public static void main(String[] args) {
Mytherd mytherd = new Mytherd();
new Thread(mytherd).start(); //线程1
new Thread(mytherd).start(); //线程2
new Thread(mytherd).start(); //线程3
}
}
class Mytherd implements Runnable{ //线程类主体
private int ticket = 5; //定义总票数
@Override
public void run() { //线程主体的方法
for (int i = 0;i < 10;i++){ //进行100次的买票处理
if (this.ticket > 0){ //判断是否有剩余
System.out.println("买票,ticket="+this.ticket--);
}
}
}
}
程序输出结果:
买票,ticket=4
买票,ticket=2
买票,ticket=5
买票,ticket=1
买票,ticket=3
2.3、Callable接口实现多线程
java.util.concurrent.Callable,接口定义:
}
@FunctionalInterface
public interface Callable<V>{
public V call() throws Exception;
}
Callable接口定义可以设置一个泛型,call()方法的返回数据类型,可以避免向下转型带来的安全隐患。
范例:定义线程主体类以及返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class JavaDemo5 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> task = new FutureTask<>(new Mythred());
new Thread(task).start();
System.out.println("县城返回数据"+task.get());
}
}
class Mythred implements Callable<String>{ //定义主体类
@Override
public String call() throws Exception { //线程执行方法
for (int i = 0; i < 10;i++){
System.out.println("线程执行,i="+i);
}
return "Java,1"; //返回结果
}
}
程序输出结果:
线程执行,i=0
线程执行,i=1
线程执行,i=2
线程执行,i=3
线程执行,i=4
线程执行,i=5
线程执行,i=6
线程执行,i=7
线程执行,i=8
线程执行,i=9
县城返回数据Java,1
2.4、多线程运行状态
想要运行多线程必须在主线程中创建新的线程对象。任意线程一般具有5中状态:创建、就绪、运行、阻塞、终止
创建:新创建了一个线程对象。准备好了一个多线程的对象(没有调用start()方法 )
就绪:调用了start()方法, 线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,
随时等待CPU调度执行(获取CPU的使用权),并不是说执行了t.start()此线程立即就会执行;
运行:执行run()方法(当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。
注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;)
阻塞:处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,
此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。
终止:线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,
则该线程结束生命周期。死亡的线程不可再次复生。
三、多线程的常用操作方法
3.1、线程的命名和取得
线程本身属于不可见的运行状态,每次操作的时间无法预料,唯一依靠的就是线程名称。
方法 | 类型 | 描述 |
---|---|---|
public Thread(Runnable target,String name) | 构造 | 实例化对象名称,接收Runnable接口子类对象 |
public final void seyName (Stirng name) | 普通 | 设置线程名字 |
public final String getName() | 普通 | 取得线程名字 |
取得当前线程对象:public static Thread currentThread()
范例:观察线程的命名操作
public class JavaDemo6 {
public static void main(String[] args) {
Mythread mythread = new Mythread();
new Thread(mythread,"线程A").start();//设置线程名字
new Thread(mythread).start(); //未设置线程名字
new Thread(mythread,"线程B").start();
}
}
class Mythread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()); //当前线程名称
}
}
程序运行结果:
线程A
线程B
Thread-0
依据运行结果发现如果没有设置名字,系统会自动分配名字,形式“Thread-xxx”的方式出现
3.2、线程休眠
某一个线程可以暂缓执行,在Thread类中中定义的休眠方法如下
线程休眠
方法 | 类型 | 描述 |
---|---|---|
public static void sleep(long millis) throws InterruptedException | 普通 | 设置线程休眠的毫秒数,时间一到自动唤醒 |
public static void sleep(long millis,int nanos) throws InterruptedException | 普通 | 设置线程休眠的毫秒数和纳秒数,时间一到自动唤醒 |
在线程休眠的时候有可能会产生中断异常InterruptedException,中断异常属于Exception的子类,
程序必须强制性进行该异常的捕获与处理。
范例:线程休眠
public class JavaDemo {
public static void main(String[] args) {
Runnable runnable = () ->{
for (int x = 0 ; x < 10; x++){ //Runnable接口实例
System.out.println(Thread.currentThread().getName()+"、x="+x);
try {
Thread.sleep(1000); //暂缓1000毫秒(1秒)
}catch (Exception e){ //强制性异常处理
e.printStackTrace();
}
}
};
for (int num = 0;num < 5;num++){
new Thread(runnable,"线程对象"+num).start(); //启动线程
}
}
}
程序输出结果:
一直运行
3.3、线程中断
线程中断
方法 | 类型 | 描述 |
---|---|---|
public boolean isInterrupted() | 普通 | 判断线程是否被中断 |
public void interrupt | 普通 | 中断线程执行 |
范例:线程中断
public class JavaDemo7 {
public static void main(String[] args) throws Exception{
Thread thread = new Thread(() -> {
System.out.println("准备睡觉10秒,不许打扰我");
try {
Thread.sleep(10000);
System.out.println("睡醒了,开始工作和学习!");
} catch (Exception e) {
System.out.println("被打扰,生气");
}
});
thread.start(); //启动线程
Thread.sleep(1000); //保证比子线程先运行一秒
if (!thread.isInterrupted()){ //该线程中断
System.out.println("打断你睡觉");
thread.interrupt(); //中断执行
}
}
}
程序输出结果:
准备睡觉10秒,不许打扰我
打断你睡觉
被打扰,生气
3.4、线程强制执行
在多线程并发执行中每一个线程对象都会交替执行,如果此时某个线程对象需要优先执行完成,
则可以设置为强制执行,等执行完毕后其他线程在继续执行。Thread类定义的强制执行方法如下:
线程强制执行:public final void join() throws InterruptedException
范例:线程强制执行
/**
* @ClassName : JavaDemo8 //类名
* @Description : 线程强制执行 //描述
* @Author : *** //作者
* @Date: 2021-07-21 09:20 //日期
*/
public class JavaDemo8 {
public static void main(String[] args) throws Exception{
Thread thread = Thread.currentThread(); //获得主线程
Thread thread1 = new Thread(()->{
for (int i = 0;i < 10;i++){
if (i==3){ //设置强制执行条件
try {
thread.join(); //强制执行任务
}catch (Exception e){
e.printStackTrace();
}
}
try {
Thread.sleep(100); //延缓执行
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行、i="+i);
}
},"玩耍的线程");
thread1.start();
for (int i = 0;i < 10;i++){ //主线程
Thread.sleep(100);
System.out.println("number="+i);
}
}
}
程序输出结果:
number=0
玩耍的线程执行、i=0
number=1
玩耍的线程执行、i=1
number=2
玩耍的线程执行、i=2
number=3
number=4
number=5
number=6
number=7
number=8
number=9
玩耍的线程执行、i=3
玩耍的线程执行、i=4
玩耍的线程执行、i=5
玩耍的线程执行、i=6
玩耍的线程执行、i=7
玩耍的线程执行、i=8
玩耍的线程执行、i=9
本程序启动了两个线程:main线程和子线程,在不满足强制执行条件时,两个线程交替执行,
而当满足了强制执行条件时在主线程执行完毕后在继续执行子线程。
3.5、线程礼让
线程礼让是指当满足某些条件,可以将当前的调度让给其他线程执行,自己在等下次调度在执行
线程礼让:public static void yield()
范例:线程礼让
/**
* @ClassName : JavaDemo9 //类名
* @Description : 线程礼让 //描述
* @Author : *** //作者
* @Date: 2021-07-21 09:53 //日期
*/
public class JavaDemo9 {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(()->{
for (int i = 0; i< 100;i++){
if (i%3 == 0){
Thread.yield(); //线程礼让
System.out.println("线程礼让"+Thread.currentThread().getName());
}
try {
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行、i="+i);
}
},"线程");
thread.start();
for (int i = 0;i < 100;i++){
Thread.sleep(100);
System.out.println("number="+i);
}
}
}
程序输出结果:
线程礼让线程
number=0
线程执行、i=0
number=1
线程执行、i=1
线程执行、i=2
线程礼让线程
number=2
number=3
剩余略
3.6、线程的优先级
在Java中优先级越高约有可能被执行
方法或常量 | 类型 | 描述 |
---|---|---|
public static final int MAX_PRIORITY | 常量 | 最高优先级,数值为10 |
public static final int MIN_PRIORITY | 常量 | 中等优先级,数值为5 |
public static final int NORM_PRIORITY | 常量 | 最低优先级,数值为1 |
public final void setPriority(int newPriority) | 普通 | 设置线程优先级 |
public final int getPriority(); | 普通 | 取得线程优先级 |
四、线程的同步与死锁
4.1、线程同步引出
线程同步是指若干个线程对象并进行资源访问实现的资源处理的保护操作。
范例:3个线程买3张票
/**
* @ClassName : JavaDemo10 //类名
* @Description : 卖票操作 //描述
* @Author : *** //作者
* @Date: 2021-07-23 09:19 //日期
*/
public class JavaDemo10 {
public static void main(String[] args) throws Exception{
Mythread1 mythread1 = new Mythread1();
new Thread(mythread1,"售票员A").start(); //开启线程
new Thread(mythread1,"售票员B").start();
new Thread(mythread1,"售票员C").start();
}
}
class Mythread1 implements Runnable{ //定义线程执行类
private int tickt = 3; //定义票数三张
@Override
public void run() {
while (true){
if (this.tickt > 0 ){
try {
Thread.sleep(100); //开启延迟
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买票,tickt"+this.tickt--);
}else {
System.out.println("******票卖完了******");
break; //跳出循环
}
}
}
}
程序输出结果:
售票员A买票,tickt1
售票员C买票,tickt2
******票卖完了******
售票员B买票,tickt3
******票卖完了******
******票卖完了******
4.2、线程同步处理
Java中提供有synchronized关键字以实现同步处理,同步的关键是要为代码加上"锁",而对于锁的操作程序有两种:同步代码块、同步方法。
同步代码块是指使用synchronized关键字定义的代码块,需要设置一个同步对象,可以选择this
范例:使用同步代码块
package JavaDemo11;
/**
* @ClassName : synchronized1 //类名
* @Description : 同步代码块 //描述
* @Author : *** //作者
* @Date: 2021-07-23 09:52 //日期
*/
public class synchronized1 {
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();
}
}
class MyThread implements Runnable{
private int ticket = 3; //总票数为3
@Override
public void run() {
while (true){
synchronized (this){ //同步代码块
if (this.ticket > 0){
try {
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买票,tickt"+this.ticket--);
} else {
System.out.println("******票卖光了******");
break;
}
}
}
}
}
程序输出结果:
售票员A买票,tickt3
售票员A买票,tickt2
售票员B买票,tickt1
******票卖光了******
******票卖光了******
******票卖光了******
范例:使用同步方法
package JavaDemo12;
/**
* @ClassName : synchronized1 //类名
* @Description : 同步方法 //描述
* @Author : *** //作者
* @Date: 2021-07-23 10:13 //日期
*/
public class synchronized1 {
public static void main(String[] args) {
Mythread1 mythread1 = new Mythread1();
new Thread(mythread1,"售票员A").start();
new Thread(mythread1,"售票员B").start();
new Thread(mythread1,"售票员C").start();
}
}
class Mythread1 implements Runnable{
private int ticket = 3;
@Override
public void run() {
while (this.sale()){ //调运同步方法
;
}
}
public synchronized boolean sale(){
if (this.ticket > 0){
try {
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买票,tickt"+this.ticket--);
return true;
}else {
System.out.println("******票买光了******");
return false;
}
}
}
程序输出结果:
售票员A买票,tickt3
售票员A买票,tickt2
售票员C买票,tickt1
******票买光了******
******票买光了******
******票买光了******
本程序将需要进行线程同步处理的操作封装在sale()方法中,当多个线程并发访问时可以保证数据操作的正确性。
4.3、线程死锁
所谓的死锁,是指两个线程都在等待对方先完成,造成了程序的停滞状态。一般程序的死锁都是在程序运行出现。
范例:观察线程锁死
package JavaDemo13;
/**
* @ClassName : Book //类名
* @Description : 线程锁死 //描述
* @Author : *** //作者
* @Date: 2021-07-23 10:40 //日期
*/
class Book {
public synchronized void tell(Painting paint){
System.out.println("Java");
paint.get();
}
public synchronized void get(){
System.out.println("not");
}
}
class Painting{
public synchronized void tell(Book book){
System.out.println("Pythen");
book.get();
}
public synchronized void get(){
System.out.println("HEllo World");
}
}
class DeabLock implements Runnable{
private Book book = new Book();
private Painting painting = new Painting();
public DeabLock(){
new Thread(this).start();
book.tell(painting);
}
@Override
public void run() {
painting.tell(book);
}
public static void main(String[] args){
new DeabLock();
}
}
程序输出结果:
Java
Pythen
***程序处于相互等待状态,后续代码不执行***、
volatile关键字
范例:使用volatile关键字定义变量
package JavaDemo14;
/**
* @ClassName : volatile2 //类名
* @Description : //描述
* @Author : *** //作者
* @Date: 2021-07-23 10:53 //日期
*/
public class volatile2 {
public static void main(String[] args) {
Mythrev mythrev = new Mythrev();
new Thread(mythrev,"售票员A").start();
new Thread(mythrev,"售票员B").start();
new Thread(mythrev,"售票员C").start();
}
}
class Mythrev implements Runnable{
private volatile int tickt = 3; //直接内存操作
@Override
public void run() {
synchronized (this){
while (this.tickt > 0){
try {
Thread.sleep(100); //延迟模拟
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买票,tickt"+this.tickt--);
}
}
}
}
程序输出结果:
售票员A买票,tickt3
售票员A买票,tickt2
售票员A买票,tickt1
总结
1、线程(Thread)是指程序的运行流程。“多线程”的机制可以同时运行多个程序块。是程序运行的效率块。
2、如果类里激活线程,必须先做好下面两项:
1.1、此类必须继承Thread类或者实现Runnable接口。
1.2、线程的处理必须覆盖run()方法
3、每个线程有五个状态:创建、就绪、运行、阻塞、终止。
4、Thread类里的sleep()方法可以来控制线程的休眠状态,休眠时间要在sleep()里的参数。
5、使用synchronized 关键字来进行资源的同步处理,在进行同步处理时需要防范死锁的产生。
6、volattile关键字并不是描述同步操作,避免了副本创建与数据同步处理。