线程(thread)
比进程更小的运行单位,是程序中单个顺序的流控制。一个进程可以由一个或者多个线程组成。简单来讲,线程是一个独立的执行流,是进程内部的一个独立执行单元,相当于一个子程序。一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片,在某一时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行。
多线程:在同一个应用程序中,有多个顺序流同时执行
创建线程:
每个Java程序启动后,虚拟机将自动创建一个主线程
可以通过以下两种方式自定义线程类:
-
创建 java.lang.Thread 类的子类,重写该类的 run方 法
-
创建 java.lang.Runnable接 口的实现类,实现接口中的 run 方法
案例1:使用Thread子类创建线程
public class MyDemo extends Thread{
public MyDemo(String name){
super(name);
}
@Override
public void run() {
for(int i=1;i<50;i++){
System.out.println(getName()+":"+i);
}
}
public static void main(String[] args) {
Thread t1=new MyDemo("线程1");
Thread t2=new MyDemo("线程2");
t1.start();
t2.start();
}
}
案例2:使用Runnable接口创建线程
//自定义线程类 实现Runable接口
public class MyThread implements Runnable{
//实现接口的run方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程:"+Thread.currentThread().getName()
+"正在打印:"+i);
}
}
public static void main(String[] args) {
//创建线程1对象
Thread thread1=new Thread(new MyThread());
//设置线程1对象的线程名
thread1.setName("1");
//创建线程2对象并设置线程名
Thread thread2=new Thread(new MyThread());
thread2.setName("2");
//启动线程
thread1.start();
thread2.start();
}
}
线程的生命周期
线程的生命周期:指线程从创建到启动,直至运行结束
线程的状态有创建、可运行、运行中、阻塞、死亡五个状态,通过线程的控制与调度可以使线程在这几种状态间转化。
1、创建状态(new Thread) Thread myThread = new MyThreadClass(); 当一个线程处于创建状态时,它仅仅是一个空的线程,系统不为它分配资源。
2、可运行状态(Runnable) myThread.start();分配了它所需的系统资源,这时线程并不是运行中状态
3、运行中状态(Running)java运行系统通过调度选中一个Runnable的线程,使其占有CPU并转为运行中状态。此时,系统才真正执行线程的run的方法
4、阻塞状态(Blocked) 进入阻塞状态的原因有(1)调用了sleep()方法;(2)调用了suspend()方法(3)为等候一个条件变量,线程调用wait()方法;(4)输入输出流中发生了线程阻塞
5、死亡状态(Dead) 自然撤消(线程执行完)或被停止(调用stop()方法(目前不推荐))
线程调度
线程调度的实现方式:
- 分时调度模型:让所有线程轮流获得CPU的控制权,并且为每个线程平均分配CPU时间片段
- 抢占式调度模型:选择优先级相对较高的线程执行,如果所有线程的优先级相同,则随机选择一个线程执行 。Java虚拟机采用此种调度模型。
线程安全
线程应用程序同时访问共享对象时,由于线程间相互抢占CPU的控制权,造成一个线程夹在另一个线程的执行过程中运行,所以可能导致错误的执行结果。
synchronized关键字:
为了防止共享对象在并发访问时出现错误,Java中提供了“synchronized”关键字。确保共享对象在同一时刻只能被一个线程访问,这种处理机制称为“线程同步”或“线程互斥”。Java中的“线程同步”基于“对象锁”的概念
① 同步方法:
•当一个线程访问对象的同步方法时,被访问对象就处于“锁定”状态,访问该方法的其他线程只能等待,对象中的其他同步方法也不能访问,但非同步方法则可以访问
//定义同步方法
public synchronized void methd(){
//方法实现
}
② 同步块
修饰部分代码,如果只希望同步部分代码行,可以使用“同步块”,同步块的作用与同步方法一样,只是控制范围有所区别
//同步块
synchronized(obj){
//被同步的代码块
}
银行转账案例:
//Account 账号类
public class Account {
private String name;
private int money;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public Account(String name, int money) {
super();
this.name = name;
this.money = money;
}
public Account() {
}
}
//取款类
public class PutMoney extends Thread {
Account account;
int money;
public PutMoney(Account account,int money){
this.account=account;
this.money=money;
}
public void put(){
synchronized (this.account) {
int beforMoney=account.getMoney();
account.setMoney(beforMoney-money);
System.out.println(account.getName()+"账号:取款前:"
+beforMoney+"存款:"+money+" 取款后:"+account.getMoney());
}
}
@Override
public void run() {
put();
}
}
//存款类
public class TackMoney extends Thread {
Account account;
int money;
public TackMoney(Account account,int money){
this.account=account;
this.money=money;
}
public void tack(){
synchronized (this.account) {
int beforMoney=account.getMoney();
account.setMoney(beforMoney+money);
System.out.println(account.getName()+"账号:存款前:"
+beforMoney+"存款:"+money+" 存款后:"+account.getMoney());
}
}
@Override
public void run() {
tack();
}
}
//测试结果类
public class TestMoney {
public static void main(String[] args) {
Account account =new Account("老王",10000);
Thread t1=new TackMoney(account,2000);
Thread t2=new PutMoney(account,5000);
t1.start();
t2.start();
}
}