线程的概念、组成、创建方法、状态、同步安全
14.多线程
14.1 什么是线程
14.1.1 什么是进程
- 程序是静止的,只有真正运行时的程序,才被称为进程;
- 单核CPU在任何时间点上,只能运行一个进程;
- 宏观并行、微观串行;
14.1.2 线程的概念
- 线程,又称轻量级进程(Light Weight Process);
- 程序中的一个顺序控制流程,同时也是CPU的基本调度单位;
- 进程由多个线程组成,彼此间完成不同的工作,交替执行,称为多线程;
- 如:迅雷是一个进程,当中的多个下载任务即是多个线程;
- Java虚拟机是一个进程,当中默认包含主线程(Main),可通过代码创建多个独立线程,与Main并发执行;
14.1.3 线程的组成
任何一个线程都具有基本的组成部分:
-
CPU时间片:操作系统(OS)会为每个线程分配执行时间;
-
运行数据:
堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象;
栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈; -
线程的逻辑代码;
14.1.4 创建线程
- 创建线程的第一种方式:
1.继承Thread类;
2.覆盖run()方法;
3.创建子类对象;
4.调用start()方法;
public class TestExtendsThread {
public static void main(String[] args) {
MyThread t1 = new MyThread();//创建线程对象
MyThread2 t2 = new MyThread2();
// t1.run();//直接调用run方法,普通对象调用方法一样
t1.start();//由JVM来调用run方法
t2.start();
for(int i = 0 ; i <= 10 ; i++) {
System.out.println("main - "+i);
}
}
}
class MyThread extends Thread{//自定义线程
public void run() {//线程的任务
for(int i = 0 ; i <= 10 ; i++) {
System.out.println("MyThread - "+i);
}
}
}
class MyThread2 extends Thread{//自定义线程
public void run() {//线程的任务
for(int i = 0 ; i <= 10 ; i++) {
System.out.println("MyThread2 - "+i);
}
}
}
- 创建线程的第二种方式:
1.实现Runnable接口;
2.覆盖run()方法;
3.创建实现类对象;
4.创建线程对象;
5.调用start()方法;
public class TestImplementsRunnable {
public static void main(String[] args) {
MyRunnable mr1 = new MyRunnable();//1.创建线程类对象
Thread t1 = new Thread(mr1);//2.Thread线程类的有参构造方法
Thread t2 = new Thread(mr1);//不同的线程,执行的是相同的任务
t1.start();//启动线程
t2.start();
for(int i = 0 ; i < 10 ; i++) {
System.out.println("Main - "+i);
}
}
}
//实现接口,只是将当前类编程线程任务类,本身不是个线程
//任务可以是多个线程共享的
//更灵活:提供了能力,不影响继承
class MyRunnable implements Runnable{
public void run() {
for(int i = 0 ; i < 10 ; i++) {
System.out.println(Thread.currentThread().getName()+" - "+i);
}
}
}
14.2 线程的状态
14.2.1 线程的状态(基本)
14.2.2 常见方法
- 休眠:
public static void sleep(long millis)
当前线程主动休眠millis毫秒;
public class TestSleep {
public static void main(String[] args) throws Exception{
MyThread3 t1 = new MyThread3();
t1.start();
//通知完t1之后,main线程休眠2秒
// Thread.sleep(2000);
MyRunnable2 task = new MyRunnable2();
Thread t2 = new Thread(task);
t2.start();
for(int i = 1 ; i < 10 ; i++) {
System.out.println(Thread.currentThread().getName()+" - "+i);
if(i == 6) {//特定条件下休眠
//通知完t1后,main线程休眠2秒
Thread.sleep(2000);
}
}
}
}
//线程类
class MyThread3 extends Thread{
public void run() {
for(int i = 0 ; i < 10 ; i++) {
//获得当前线程的线程名称
System.out.println(Thread.currentThread().getName()+" - "+i);
}
}
}
class MyRunnable2 implements Runnable{
public void run() {
for(int i = 0 ; i < 10 ; i++) {
System.out.println(Thread.currentThread().getName() +" - "+i);
try {
Thread.sleep(1000);
}catch(Exception e) {
}
}
}
}
- 放弃:
public static void yield()
当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片;
public class TestYield {
public static void main(String[] args) {
Thread t1 = new Thread(new Task());
t1.start();
for(int i = 0 ; i < 10 ; i++) {
System.out.println(Thread.currentThread().getName()+" - "+i);
if(i % 2 == 0) {
System.out.println("main主动放弃了");
Thread.yield();//放弃:主动放弃当前持有的时间片,进入下一次竞争
}
}
}
}
class Task implements Runnable{
public void run() {
for(int i = 0 ; i < 10 ; i++) {
System.out.println(Thread.currentThread().getName() + " - " +i);
}
}
}
- 结合:
public final void join()
允许其它线程加入到当前线程中;
public class TestJoin {
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(new Task2());
Thread t2 = new Thread(new Task2());
t1.start();
t2.start();
for(int i = 0 ; i < 10 ; i++) {
System.out.println(Thread.currentThread().getName()+" - "+1);
if(i == 6) {
System.out.println("main执行到了20!执行t1");
t1.join();//将t1加人到main线程执行流程中,等待t1线程执行流程结束后,main再
//无限期等待:等待条件为调用join方法的线程执行完毕后,再进入就绪状态,竞争时间片
}
}
}
}
class Task2 implements Runnable{
public void run() {
for(int i = 0 ; i < 10 ; i++) {
System.out.println(Thread.currentThread().getName()+" - "+i);
}
}
}
14.2.3 线程的状态(等待)
14.3 线程安全
14.3.1 线程安全问题
- 需求:A线程将“Hello”存入数组的第一个空位;B线程将“World”存入数组的第一个空位;
- 线程不安全: 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致;
- 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性;
- 原子操作:不可分割的多步操作,被视作一个整体,其操作和步骤不可打乱或缺省;
14.3.2 同步方式(1)
- 同步代码块:
synchronized(临界资源对象){//对临界资源对象加锁
//代码(原子操作)
} - 注:
每个对象都有一个互斥锁标记,用来分配给线程的;
只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步代码块;
线程退出同步代码块时,会释放相应的互斥锁标记;
public class TestSynchronized {
public static void main(String[] args) {
//临界资源对象
//临界资源对象只有一把锁
Account acc = new Account("6002" , "1234" , 2000);
//两个线程对象,共享同一银行卡资源对象
Thread husband = new Thread(new Husband(acc) , "丈夫");
Thread wife = new Thread(new Wife(acc) , "妻子");
husband.start();
wife.start();
}
}
class Husband implements Runnable{
Account acc;
public Husband(Account acc) {
this.acc = acc;
}
//线程任务:取款
public void run() {
// synchronized(acc) {//对临界资源对象加锁
this.acc.withdrawal("6002", "1234", 500);
// }
}
}
class Wife implements Runnable{
Account acc;
public Wife(Account acc) {
this.acc = acc;
}
//线程任务:取款
public void run() {
// synchronized(acc) {
this.acc.withdrawal("6002", "1234", 1200);//原子操作
}
// }
}
//银行账户
class Account{
String cardNo;
String password;
double balance;
public Account(String cardNo , String password , double balance) {
super();
this.cardNo = cardNo;
this.password = password;
this.balance = balance;
}
//取款(原子操作,从插卡验证,到取款成功的一系列步骤,不可缺少或打断)
public void withdrawal(String no , String pwd , double money) {
//
synchronized(this) {//对当前共享实例加同步锁
System.out.println(Thread.currentThread().getName()+"正在读卡。。。");
if(no.equals(this.cardNo) && pwd.equals(this.password)) {
System.out.println(Thread.currentThread().getName()+"验证成功。。。");
if(money > this.balance) {
System.out.println(Thread.currentThread().getName()+" 卡内余额不足");
}else{
try {
Thread.sleep(1000);//模拟ATM数钱时间
}catch(InterruptedException e){
e.printStackTrace();
}
this.balance = this.balance - money;
System.out.println(Thread.currentThread().getName()+"当前余额为:"+this.balance);
}
}else {
System.out.println(Thread.currentThread().getName()+"卡号或密码错误");
}
}
}
}
输出结果:
丈夫正在读卡。。。
丈夫验证成功。。。
丈夫当前余额为:1500.0
妻子正在读卡。。。
妻子验证成功。。。
妻子当前余额为:300.0
14.3.3 线程的状态(阻塞)
- 注:JDK5之后,就绪、运行统称Runnable