线程
概念
-
程序
程序是指令和数据的有序集合,组成成一个完成特定功能或者是一组特定功能。其本身没有任何运行的含义,是一个静态的概念。进程是包含程序的,进程的执行离不开程序,进程中的文本区域就是代码区,也就是程序。
-
进程
狭义定义:进程就是一段程序的执行过程。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
-
线程
通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。
-
进程与线程的区别
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
-
简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
-
线程的划分尺度小于进程,使得多线程程序的并发性高。
-
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
-
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
-
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
-
线程的生命周期
-
新建状态: 一个新产生的线程从新状态开始了它的生命周期。它保持这个状态直到程序start这个线程。
-
**运行状态:**当一个新状态的线程被start以后,线程就变成可运行状态,一个线程在此状态下被认为是开始执行其任务
-
**就绪状态:**当一个线程等待另外一个线程执行一个任务的时候,该线程就进入就绪状态。当另一个线程给就绪状态的线程发送信号时,该线程才重新切换到运行状态。
-
休眠状态: 由于一个线程的时间片用完了,该线程从运行状态进入休眠状态。当时间间隔到期或者等待的事件发生了,该状态的线程切换到运行状态。
-
终止状态: 一个运行状态的线程完成任务或者其他终止条件发生,该线程就切换到终止状态。
创建线程
-
通过实现Runnable接口;
1:实现Runnable
2:重写run方法 run方法中编写要执行的逻辑代码
3:创建Thread对象,将实现类对象作为参数传入Thread的构造器
4:调用start方法 启动该线程public static void main(String[] args){ //创建实现类对象 MyRun r=new MyRun(); //创建一个线程对象 Thread t=new Thread(r);//在Thread类中有一个Runnable的属性,给该属性赋值 //启动线程 t.start(); //打印输出 for(int i=0;i<10;i++){ System.out.println("线程main执行--->"+i); } } } class MyRun implements Runnable{ @Override public void run(){ for(int i=0;i<10;i++){ System.out.println("myrun执行"+i); } }
-
通过继承Thread类本身;
1:继承Thread
2:重写run方法 run方法中编写要执行的逻辑代码
3:创建子类对象
4:调用start方法 启动该线程public static void main(String[] args){ //开启自定义的线程 Myt t=new Myt(); System.out.println(new Myt().isAlive()); //执行打印输出 for(int i=0;i<100;i++){ if(i==20){ t.start(); } t.isAlive(); } } } class Myt extends Thread{ @Override public void run(){ for(int i=0;i<10;i++){ System.out.println("线程Myt执行--->"+i); } }
-
通过 Callable 和 Future 创建线程。
1:创建 Callable 接口的实现类,并实现 call() 方法,该call() 方法将作为线程执行体,并且有返回值。
2:创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了 该 Callable 对象的 call() 方法的返回值。
3:使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
4:调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
public static void main(String[] args){ CallableThread ct=new CallableThread(); FutureTask<Integer> ft=new FutureTask<>(ct); for(int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+"的循环变量i的值:"+i); if(i==20){ new Thread(ft,"有返回值的线程").start(); } } try{ System.out.println("子线程的返回值:"+ft.get()); }catch (InterruptedException e){ e.printStackTrace(); }catch (ExecutionException e){ e.printStackTrace(); } } } class CallableThread implements Callable<Integer>{ @Override public Integer call()throws Exception{ int i=0; for(;i<100;i++){ System.out.println(Thread.currentThread().getName()+""+i); } return i; }
创建三种线程的对比
1:采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
2:使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
线程的常见方法
-
public void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 -
public void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 -
public final void setName(String name)
改变线程名称,使之与参数 name 相同。 -
public final void setPriority(int priority)
更改线程的优先级。 -
public final void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。public static void main(String[] args){ DaemonThread t=new DaemonThread(); t.setDaemon(true); t.start(); for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"=="+i); } } } class DaemonThread extends Thread{ public void run(){ for(int i=0;i<1000;i++){ System.out.println(getName()+"=="+i); } }
-
public final void join(long millisec)
等待该线程终止的时间最长为 millis 毫秒。public static void main(String[] args)throws InterruptedException{ //启动子线程 new JoinThread("新线程").start(); for(int i=0;i<100;i++){ if(i==20){ JoinThread jt=new JoinThread("被Join的线程"); jt.start(); //main线程调用了jt线程的join方法,main线程 //必须等jt执行结束后才会向下执行 jt.join(); } System.out.println(Thread.currentThread().getName()+""+i); } } } class JoinThread extends Thread{ //提供一个有参构造器,用于设置该线程 public JoinThread(String name){ super(name); } //重写run方法 定义线程执行体 public void run(){ for(int i=0;i<100;i++){ System.out.println(getName()+"==="+i); } }
-
public void interrupt()
中断线程。public static void main(String[] args) { Thread t = new Thread(new Runnable() { @Override public void run() { for(int i = 0;i<10;i++) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } while(!Thread.currentThread().isInterrupted()) { System.out.println("线程正在执行--->"+i); } } } }); t.start(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } t.interrupt();//中断t线程 给当前线程盖了一个标示 中断标示 }
-
public final boolean isAlive()
测试线程是否处于活动状态。 -
public static void yield()
暂停当前正在执行的线程对象,并执行其他线程。public static void main(String[] args){ //启动两个并发进程 YieldTest y1=new YieldTest("高级"); //j将y1设置成最大优先级 y1.setPriority(Thread.MAX_PRIORITY); y1.start(); YieldTest y2=new YieldTest("低级"); //将y1设置成最低优先级 y2.setPriority(Thread.MIN_PRIORITY); y2.start(); } } class YieldTest extends Thread{ public YieldTest(String name){ super(name); } public void run(){ for (int i=0;i<50;i++){ System.out.println(getName()+" "+i); //当i等于20时,使用Yield()方法让当前进程让步 if(i==20){ Thread.yield(); } } }
-
public static void sleep(long millisec)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。public static void main(String[] args)throws Exception{ for(int i=0;i<10;i++){ System.out.println("当前时间:"+new Date()); Thread.sleep(7000); } }
-
public static boolean holdsLock(Object x)
当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。 -
public static Thread currentThread()
返回对当前正在执行的线程对象的引用。 -
public static void dumpStack()
将当前线程的堆栈跟踪打印至标准错误流。
线程的同步策略
synchronized
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
-
修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象; 一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞 。
public static void main(String[] args) { TicketRun1 tr = new TicketRun1(); for(int i = 65;i<70;i++) { new Thread(tr,"窗口"+(char)i).start(); } } } class TicketRun1 implements Runnable{ int count = 10; public void run() { for(int i = 0;i<10;i++) { synchronized (this) { if(count>0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在售票:"+(count--)); } } } }
-
修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
public static void main(String[] args){ TicketRun tr=new TicketRun(); for(int i=65;i<70;i++){ new Thread(tr,"窗口"+(char)i).start(); } } } class TicketRun implements Runnable{ int count=10; public void run(){ for(int i=0;i<10;i++){ checkTicket(); } } public synchronized void checkTicket(){ if(count>0){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在售票:"+(count--)); } }
-
修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
-
修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
-
注意:
- synchronized关键字不能继承。
虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。 - 在定义接口方法时不能使用synchronized关键字。
- 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
- 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
- synchronized关键字不能继承。
同步问题—银行取钱问题
public class Test09_BankProblem {
public static void main(String[] args){
//创建一个账户
Account acct=new Account("123456",1000);
//模拟两个线程对同一账户取钱
new DrawThread("甲",acct,800).start();
new DrawThread("乙",acct,800);
}
}
class Account{
//封装账户编号、账户余额的成员变量
private String accountNo;
private double balance;
public Account(){
}
//构造器
public Account(String accountNo,double balance){
this.accountNo=accountNo;
this.balance=balance;
}
public String getAccountNo() {
return accountNo;
}
//由于账户余额不能随意更改,所以只为balance提供getter方法
public double getBalance() {
return this.balance;
}
//提供一个线程安全的draw()方法来完成取钱操作
public synchronized void draw(double drawAmount){
//账户余额大于取钱数目
if(balance>=drawAmount){
//吐出钞票
System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票"+drawAmount);
try {
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
//修改余额
balance-=drawAmount;
System.out.println("\t余额为:"+balance);
}else {
System.out.println(Thread.currentThread().getName()+"取钱失败!余额不足!");
}
}
//根据accountNo重写hashCode()和equals()方法
public int hashCode(){
return accountNo.hashCode();
}
public boolean equals(Object object){
if(this==object){
return true;
}
if(object!=null&&object.getClass()==Account.class){
Object target=(Account) object;
return ((Account) target).getAccountNo().equals(accountNo);
}
return false;
}
}
class DrawThread extends Thread{
//模拟用户账户
private Account account;
//当前取钱线程希望取得钱数
private double drawAmount;
public DrawThread(String name,Account account,double drawAmount){
super(name);
this.account=account;
this.drawAmount=drawAmount;
}
//当多个线程修改同一数据时,将涉及数据安全问题
public void run(){
account.draw(drawAmount);
}
}
return ((Account) target).getAccountNo().equals(accountNo);
}
return false;
}
}
class DrawThread extends Thread{
//模拟用户账户
private Account account;
//当前取钱线程希望取得钱数
private double drawAmount;
public DrawThread(String name,Account account,double drawAmount){
super(name);
this.account=account;
this.drawAmount=drawAmount;
}
//当多个线程修改同一数据时,将涉及数据安全问题
public void run(){
account.draw(drawAmount);
}
}