初步学习多线程
基本介绍
多线程是指一个进程里,至少有2个以上的线程,而线程就是进程中的一个独立控制单元,线程控制着进程的执行,一个进程至少有一个线程。
使用多线程的目的
- 为了更好的利用cpu的资源,如果只有一个线程,则第二个任务必须等到第一个任务结束后才能进行,如果使用多线程则在主线程执行任务的同时可以执行其他任务,而不需要等待。
- 进程之间不能共享数据,线程可以
- 系统创建进程需要为该进程重新分配系统资源,创建线程代价较小
- java语言内置了多线程功能支持,简化了java多线程编程
线程的实现方式
-
继承Thread类
class MyThread extends Thread{ ...... @Override public void run(){ super.run(); System.out.println("Thread类启动多线程"); } } @Test public class TestMyThread{ public static void main(String[] args){ MyThread mt = new MyThread(); mt.start(); System.out.println("main线程"); } }
-
实现Runnable接口(推荐)
class MyThread implements Runnable{ ...... @Override public void run(){ System.out.println("Runnable类启动多线程"); } } @Test public class TestMyThread{ public static void main(String[] args){ MyThread mt = new MyThread(); Thread td = new Thread(mt); td.start(); System.out.println("main线程"); } }
Runnable方式可以避免Thread方式由于java单继承特性带来的缺陷。
Runnable的代码可以被多个线程(Thread)实例共享,适合于多个线程处理同一个资源的情况。
停止线程的方式
-
stop()方法(不推荐)
Java多线程中有一个已经封装好的方法—–stop()方法。这个方法是在早期版本的Java中就封装好的,而一旦使用stop()方法,那么线程将戛然而止,我们甚至不知道线程在停止之前完成了哪些工作。
-
interrupt()方法(不推荐)
通过Thread中的interrupt()方法来停止线程,interrupt()方法是用于中断线程,并且我们看到调用interrupt()方法后线程将会被设置interrupt status。但是,当线程因为如wait()等方法而阻塞的时候,其interrupt status将被清除。(中断和阻塞是操作系统中的概念)。
-
suspend()方法(不推荐)
这个方法容易发生死锁,调用此方法时,目标线程会停下来,但仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被挂起的线程恢复运行。对任何线程来说,如果他们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。
-
设置标志(旗帜)(推荐)
//设置标志停止线程 class MyThread implements Runnable{ ...... @Override public void run(){ int i = 0; while(i<10){ i++; ....... } System.out.println("标记停止状态"); } } @Test public class TestMyThread{ public static void main(String[] args){ MyThread mt = new MyThread(); Thread td = new Thread(mt); td.start(); System.out.println("main线程"); } }
中断线程的方式
- 调用stop()方法。该方法强制中断正在执行的线程,很可能造成临界资源的不一致,已经被弃用。
- 调用intercept()方法。该方法的线程被中断,它将尝试停止它正在做的事情而提前返回,并通过抛出 InterruptedException 表明它提前返回。
public class Run {
public static void main(String[] args) {
try {
MyThread mt = new MyThread();
mt.start();
mt.sleep(20);//modify 2000 to 20
mt.interrupt();//请求中断MyThread线程
} catch (InterruptedException e) {
System.out.println("捕获中断异常");
e.printStackTrace();
}
System.out.println("main线程执行完成");
}
}
//第一种
public class MyThread extends Thread {
@Override
public void run() {
while(true) {
if (this.interrupted()) {
System.out.println("mythreada线程被中断");
break;
}
}
System.out.println("mythread执行完成");//尽管线程被中断,但并没有结束运行。这行代码还是会被执行
}
}
//执行结果
/**
* main线程执行完成//1
* mythreada线程被中断//2
* mythread执行完成//3
*/
//第二种,当执行interrupted()方法后,抛出InterruptedException异常
public class MyThread extends Thread{
@Override
public void run() {
try {
while (true) {
if (this.interrupted()) {
System.out.println("myThread线程被中断");
throw new InterruptedException();
}
}
} catch (InterruptedException e) {
/**这样处理不好
* System.out.println("catch interrupted exception");
* e.printStackTrace();
*/
Thread.currentThread().interrupt();//这样处理比较好
}
}
}
线程的生命周期
Thread、notify、join、yield的方法简介
-
Thread:新建线程,应该调用start()方法启动线程;如果直接调用run()方法,该方法也会执行,但会被当做一个普通的方法,在当前线程中顺序执行;而如果使用start()方法,则会创建一个新的线程执行run()方法。
-
Object.wait:使当前线程进入waiting,直到另一个线程调用notify或者notifyAll方法来唤醒它,或者是指定了等待时间。
synchronized (lock) { while (!condition) { lock.wait() // 进入 waiting 状态, 这行代码之后的代码将不会被执行 } }
-
Object.notify:唤醒一个waiting态的线程
synchronized (lock) { lock.notify() }
-
Thread.join:调用threadA.join()的线程,要进入waiting状态,一直到线程threadA执行完毕.
public static void main() { Thread t1 = new Thread(…); t1.join(); // 这行代码必须要等t1全部执行完毕,才会执行 }
-
Thread.yield:暂停当前正在执行的线程对象,并执行其他线程,不会释放锁资源。
for (int i = 0; i < 1000; i++) { synchronized (object) { count++; System.out.println("t2 " + count); } Thread.yield(); // 加在这里 }
线程的异常处理
Thread异常处理:Thread 线程的有很多种实现形式,基于 Runnable 的没有返回值的线程,无法在主线程感知到异常,没有捕获的异常只会输出到控制台,如果没有捕获异常,进行处理或日志记录,会造成异常丢失。有返回值的 FutureTask ,未捕获的异常,会在获取返回值时传递给主线程。
//主程序处理Runable异常
public static void catchInRunThread() {
Thread thread = new Thread(() -> {
try {
System.out.println(1 / 0);
} catch (Exception e1) {
// 异常处理,日志记录等
System.out.println("catched exception in child thread");
}
});
thread.start();
}
//主程序处理FutureTask异常
public static void catchInFuture() {
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
return 1/0;
});
Thread thread = new Thread(futureTask);
thread.start();
try {
futureTask.get();
} catch (InterruptedException | ExecutionException e) {
System.out.println("catched exception in main thread by future");
// e.printStackTrace();
}
}
死锁的解决方法
死锁是操作系统层面的一个错误,是进程死锁的简称,最早在 1965 年由 Dijkstra 在研究银行家算法时提出的,它是计算机操作系统乃至整个并发程序设计领域最难处理的问题之一。
银行家算法:所谓银行家算法,是指在分配资源之前先看清楚,资源分配后是否会导致系统死锁。如果会死锁,则不分配,否则就分配。
/*一共有5个进程需要请求资源,有3类资源*/
public class BankDemo {
/* 每个进程所需要的最大资源数 */
public static int MAX[][] = { { 7, 5, 3 }, { 3, 2, 2 }, { 9, 0, 2 },
{ 2, 2, 2 }, { 4, 3, 3 } };
/* 系统拥有的初始资源数 */
public static int AVAILABLE[] = { 10, 5, 7 };
/* 系统已给每个进程分配的资源数 */
public static int ALLOCATION[][] = { { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 },
{ 0, 0, 0 }, { 0, 0, 0 } };
/* 每个进程还需要的资源数 */
public static int NEED[][] = { { 7, 5, 3 }, { 3, 2, 2 }, { 9, 0, 2 },
{ 2, 2, 2 }, { 4, 3, 3 } };
/* 每次申请的资源数 */
public static int Request[] = { 0, 0, 0 };
/* 进程数与资源数 */
public static int M = 5, N = 3;
int FALSE = 0;
int TRUE = 1;
public void showdata()
{
int i, j;
System.out.print( "系统可用的资源数为:/n" );
for ( j = 0; j < N; j++ )
{
System.out.print( "资源" + j + ":" + AVAILABLE[j] + " " );
}
System.out.println();
System.out.println( "各进程还需要的资源量:" );
for ( i = 0; i < M; i++ )
{
System.out.print( "进程" + i + ":" );
for ( j = 0; j < N; j++ )
{
System.out.print( "资源" + j + ":" + NEED[i][j] + " " );
}
System.out.print( "/n" );
}
System.out.print( "各进程已经得到的资源量: /n" );
for ( i = 0; i < M; i++ )
{
System.out.print( "进程" );
System.out.print( i );
for ( j = 0; j < N; j++ )
{
System.out.print( "资源" + j + ":" + ALLOCATION[i][j] + " " );
}
System.out.print( "/n" );
}
}
/* 分配资源,并重新更新各种状态 */
public void changdata( int k )
{
int j;
for ( j = 0; j < N; j++ )
{
AVAILABLE[j] = AVAILABLE[j] - Request[j];
ALLOCATION[k][j] = ALLOCATION[k][j] + Request[j];
NEED[k][j] = NEED[k][j] - Request[j];
}
};
/* 回收资源,并重新更新各种状态 */
public void rstordata( int k )
{
int j;
for ( j = 0; j < N; j++ )
{
AVAILABLE[j] = AVAILABLE[j] + Request[j];
ALLOCATION[k][j] = ALLOCATION[k][j] - Request[j];
NEED[k][j] = NEED[k][j] + Request[j];
}
};
/* 释放资源 */
public void free( int k )
{
for ( int j = 0; j < N; j++ )
{
AVAILABLE[j] = AVAILABLE[j] + ALLOCATION[k][j];
System.out.print( "释放" + k + "号进程的" + j + "资源!/n" );
}
}
public int check0( int k )
{
int j, n = 0;
for ( j = 0; j < N; j++ )
{
if ( NEED[k][j] == 0 )
n++;
}
if ( n == 3 )
return(1);
else
return(0);
}
/*
* 检查安全性函数
* 所以银行家算法其核心是:保证银行家系统的资源数至少不小于一个客户的所需要的资源数。在安全性检查函数 chkerr() 上由这个方法来实现
* 这个循环来进行核心判断,从而完成了银行家算法的安全性检查工作。
*/
public int chkerr( int s )
{
int WORK;
int FINISH[] = new int[M], temp[] = new int[M]; /* 保存临时的安全进程序列 */
int i, j, k = 0;
for ( i = 0; i < M; i++ )
FINISH[i] = FALSE;
for ( j = 0; j < N; j++ )
{
WORK = AVAILABLE[j]; /* 第 j 个资源可用数 */
i = s;
/* 判断第 i 个进程是否满足条件 */
while ( i < M )
{
if ( FINISH[i] == FALSE && NEED[i][j] <= WORK )
{
WORK = WORK + ALLOCATION[i][j];
FINISH[i] = TRUE;
temp[k] = i;
k++;
i = 0;
} else {
i++;
}
}
for ( i = 0; i < M; i++ )
if ( FINISH[i] == FALSE )
{
System.out.print( "/n 系统不安全!!! 本次资源申请不成功!/n" );
return(1);
}
}
System.out.print( "/n 经安全性检查,系统安全,本次分配成功。/n" );
System.out.print( "本次安全序列:" );
for ( i = 0; i < M - 1; i++ )
{
System.out.print( "进程" + temp[i] + "->" );
}
System.out.print( "进程" + temp[M - 1] );
System.out.println( "/n" );
return(0);
}
}