【7】Java多线程
一、基础
线程与进程的
在计算机中,我们把一个任务称为一个进程,浏览器就是一个进程,视频播放器是另一个进程,类似的,音乐播放器和Word都是进程。
某些进程内部还需要同时执行多个子任务。例如,我们在使用Word时,Word可以让我们一边打字,一边进行拼写检查,同时还可以在后台进行打印,我们把子任务称为线程。
进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少会有一个线程。
多线程
Java语言内置了多线程支持:一个Java程序实际上是一个JVM进程,JVM进程用一个主线程来执行main()
方法,在main()
方法内部,我们又可以启动多个线程。
二、创建多线程的三种方式
1、继承 Thread 类,重写run()方法,run()方法代表线程要执行的任务。
2、实现 Runnable 接口,重写 run()方法,run()方法代表线程要执行的任务。
3、实现 callable 接口,重写 call()方法,call()作为线程的执行体,具有返回值,并且可以对异常进行声明和抛出;使用start()方法来启动线程
1、第一种方法:继承Thread类
Thread是一个线程类,位于java.lang包下
1)构造方法
Thread():创建一个线程对象
Thread(String name):创建一个具有指定名称的线程对象
Thread(Rummable target):创建一个基于Runnable接口实现类的线程对象
Thread(Runnable target,String name):创建一个基于Runnable接口实现类,并且具有指定名称的线程对象。
2)Thread类的常用方法
public void run():线程相关的代码写在该方法中,一般需要重写。
public void start():启动线程的方法 public static void sleep(long m):线程休眠m毫秒的方法
public void join():优先执行调用join()方法的线程。
2、第二种方法:实现Runnable接口
只有一个方法run();
Runnable是Java中用以实现线程的接口
任何实现线程功能的类都必须实现该接口
3、第三种方法:实现callable 接口
1、创建Callable 接口的实现类,并实现 call()方法,该 call()方法将作为线程执行体,并且有返回值。
2、创建Callable 实现类的实例,使用 FutureTask 类来包装Callable 对象,该 FutureTask 对象封装了该Callable 对象的 call()方法的返回值。
3、使用FutureTask 对象作为 Thread 对象的target 创建并启动新线程。
4、调用FutureTask 对象的 get()方法来获得子线程执行结束后的返回值。
三、通过Thread 类创建线程实例
最简单的例子
class MyThread extends Thread{//创建线程类,继承Thread public void run(){ System.out.println(getName()+"该线程正在执行!"); } } public class ThreadTest { /** * 1.主方法main也是一个线程,mt方法也是一个线程,顺序是随机的 * 2.启动线程,线程启动用的是srart,启动的是上面的run方法 * 3.同一个Thread不能重复调用start方法,会抛出IllegalThreadStateException异常 */ public static void main(String[] args) { // System.out.println("主线程1"); MyThread mt=new MyThread(); mt.start();//启动线程 // mt.start(); // System.out.println("主线程2"); } }
四、通过Runnable接口创建线程实例(应用更为广泛)
实现Runnable接口创建线程:
1、创建类PrintRunnable实现接口Runnable;
2、重写run方法;
3、定义Runnable实现类的对象(例:PrintRunnable pr=new PrintRunnable();)
4、通过Thread线程类构造方法传入t1分配一个新的线程对象(例:Thread t1=new Thread(pr) ;)
5、启动线程(例:t1.start();) PS:启动线程只能通过Thread及其子类启动
//通过Runnable接口创建线程 class PrintRunnable implements Runnable { int i = 1;//两个线程共处理10次 @Override public void run() { // int i = 1;//两个线程各处理10次 while (i <= 10) //currentThread当前线程 System.out.println(Thread.currentThread().getName() + "正在运行" + (i++)); } } public class Test { public static void main(String[] args) { //启动线程有3步, //适合多个线程处理同一个资源 PrintRunnable pr = new PrintRunnable(); Thread t1 = new Thread(pr); t1.start(); Thread t2 = new Thread(pr); t2.start(); } }
五、线程的状态和声明周期(sleep&join)
1.线程的状态
新建(New):创建Thread类或者Thread子类的对象时。
可运行(Runnable):创建好的线程调用start方法后,也叫就绪状态。
正在运行(Runnig):处于可运行状态的线程获取CPU的使用权后。
阻塞(Blocked):线程遇到干扰暂停后。
终止(Dead):线程执行完毕或者异常终止。
2.线程的声明周期
stop方法已经弃用。
3、sleep方法
- Thread类的方法:public static void sleep(long millis)
- sleep方法的应用场景:-计时 -控制刷新频率
- sleep方法的作用:在指定的毫秒数内让正在执行的线程休眠(暂停执行)
- 注:在休眠相应时间后,转为可运行状态(而不是正在运行状态),在获取cpu使用权后进入运行状态;
- 这个方法可能会发生InterruptedException异常,需要try-catch捕获。
class MyThread implements Runnable{ @Override public void run() { for(int i=1;i<=30;i++){ System.out.println(Thread.currentThread().getName()+"执行第"+i+"次!"); try { Thread.sleep(1000);//等待1000毫秒,也就是1秒 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } public class SleepDemo { public static void main(String[] args) { MyThread mt=new MyThread(); Thread t=new Thread(mt); t.start(); Thread t1=new Thread(mt); t1.start(); } }
4、join方法
- Thread类的方法:public final void join(long millis)
- join方法的作用:等待该线程终止的最长时间为millis毫秒,也就是超过这个时间后,无论线程有没有执行完毕,都可以开始执行其它线程。
- 调用join( )方法可以使其他线程由正在运行状态变成阻塞状态
class MyThread extends Thread{ public void run(){ for(int i=1;i<=500;i++) System.out.println(getName()+"正在执行"+i+"次!"); } } public class JoinDemo { public static void main(String[] args) { MyThread mt=new MyThread(); mt.start(); try { // mt.join();//加入join方法后,mt方法抢先执行 mt.join(1);//mt方法抢先执行1000毫秒 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } for(int i=1;i<=20;i++){ System.out.println("主线程运行第"+i+"次!"); } System.out.println("主线程运行结束!"); } }
六、线程优先级
1、Java为线程类提供了10个优先级
2、优先级可以用整数1-10表示(数字越大优先级越高),超过范围会抛出异常
3、main方法的主线程默认优先级为5
4、优先级常量来表示线程优先级:
MAX_PRIORITY:线程的最高优先级10
MIN_PRIORITY:线程的最低优先级1
NOEM_PRIORITY:线程的默认优先级5
5、优先级相关的方法:
public int getPriority(); 获取线程优先级的方法
public void setPriority(int newPriority); 设置线程优先级的方法
七、线程同步关键字synchronized
1、多线程运行问题
1)各个线程是通过竞争CPU时间而获得运行机会的
2)各线程什么时候得到CPU时间,占用多久,是不可预测的
3)一个正在运行着的线程在什么地方被暂停是不确定的
2、线程同步关键字synchronized,使用方式
1)修饰一个代码块,被修饰的代码块称为同步代码块,作用范围是大括号{}括起来的代码;
synchronized (obj) {}
2)修饰一个方法,被修饰的方法称为同步方法,其作用范围是整个方法;
public synchronized void saveAccount() {}
3)修饰一个静态方法,作用范围是整个静态方法;
public static synchronized void saveAccount() {}
4)修饰一个类,作用范围是synchronized后面括号括起来的部分。
3、synchronized关键字作用
保证代码的执行完整性,执行过程不被打断--当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后,其他线程才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,其他线程才能执行并锁定该对象。
代码实例:
public class Bank { private String account;// 账号 private int balance;// 账户余额 public Bank(String account, int balance) { this.account = account; this.balance = balance; } public String getAccount() { return account; } public void setAccount(String account) { this.account = account; } public int getBalance() { return balance; } public void setBalance(int balance) { this.balance = balance; } @Override public String toString() { return "Bank [账号:" + account + ", 余额:" + balance + "]"; } // 存款 public synchronized void saveAccount() {//1方法中加同步关键字 // 获取当前的账号余额 int balance = getBalance(); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 修改余额,存100元 balance += 100; // 修改账户余额 setBalance(balance); // 输出存款后的账户余额 System.out.println("存款后的账户余额为:" + balance); } public void drawAccount() { synchronized (this) {//2语句块中加同步关键字 // 在不同的位置处添加sleep方法 // 获得当前的帐户余额 int balance = getBalance(); // 修改余额,取200 balance = balance - 200; try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 修改帐户余额 setBalance(balance); System.out.println("取款后的帐户余额:" + balance); } } }
//存款 public class SaveAccount implements Runnable{ Bank bank; public SaveAccount(Bank bank){ this.bank=bank; } //调用run进行存款操作 public void run(){ bank.saveAccount(); } }
//取款 public class DrawAccount implements Runnable{ Bank bank; public DrawAccount(Bank bank){ this.bank=bank; } //取款操作方法 @Override public void run() { bank.drawAccount(); } }
public class Test { public static void main(String[] args) { // 创建帐户,给定余额为1000 Bank bank=new Bank("1001",1000); //创建线程对象 SaveAccount sa=new SaveAccount(bank); DrawAccount da=new DrawAccount(bank); Thread save=new Thread(sa); Thread draw=new Thread(da); save.start(); draw.start(); try { draw.join(); save.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(bank); } }
八、