Java学习(第六周)---多线程

一.进程与线程

1. 进程
1)运行时的程序,称为进程。每个进程都有自己独立的一块内存空间。
2)单核CPU在任一时间点上,只能运行一个进程。
3)宏观并行、微观串行
4)Windows+R ---->MVIC----> cpu get NumberOfCores // 获得核心数

2. 线程
1)轻量级进程 (Light Weight Process)
2)程序中的一个顺序控制流程,也是CPU的基本调度单位。
3)进程可以由单个或多个线程组成,彼此间完成不同的工作,交替执行,称为多线程。多线程共享进程的地址空间,而进程有自己独立的地址空间
4)操作系统以进程为单位分配资源,同一个进程内的线程共享进程的资源
举例:
Windows系统中,一个运行的exe文件就是一个进程。
1)迅雷是一个进程,当中的多个下载任务即是多个线程。
2)JVM虚拟机是一个进程,默认包含主线程(Main函数),可以通过代码创建多个独立线程,与Main线程并发执行。

二.线程的组成

任何一个线程都具有基本的组成部分:
1)CPU时间片:操作系统(OS)会为每个线程分配执行时间,无需人工干预。
2) 运行数据: (堆空间共享,栈空间独立)
• 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。
• 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。
3)线程的逻辑代码。

三.线程的创建

Java 虚拟机允许应用程序并发地运行多个执行线程,
1)直接生成Thread类的对象来表示线程。
• Thread类中有个run()方法:public void run(),此方法又称线程体,是线程的核心,但该方法不执行任何操作并返回。一个运行的线程实际上是调用线程的run()方法(即run()实际上是线程任务),所以线程的操作要在run()方法中进行定义,要覆盖run()方法。
• Thread类中有个start()方法:public void start() ,使该线程开始执行,Java 虚拟机调用该线程的 run 方法

public class TestCreatThread{
	public static void main(String[] args){//主线程
	MyThread t1=new MyThread();//3.创建对象
	t1.start();//4.调用start()方法
	}
}
class MyThread extends Thread{//1.继承Thread类
	public void run(){//2.覆盖run()方法
		for(int i=1;i<=50;i++){
			System.out.println("MyThread:"+i);
		}	
	}
}

2)可以通过实现接口Runnable来创建线程类
•由于java 不支持多继承,可以通过实现接口Runnable来创建线程类。Runnable接口只声明了一个方法run(),所以实现改接口的类必须重新定义该方法。
•要启动线程,必须调用线程类Thread类中的方法start(),所以使用Runnable接口实现线程,也必须有Thread类的对象,并且该对象的run ()方法(线程体)是由实现Runnable接口的类的对象提供。

public class TestCreatThread{
	public static void main(String[] args){//主线程
	MyRunnable mr=new MyRunnable();//3.创建实现类对象
	Thread t2=new Thread(mr);//4.创建线程对象(Thread线程类的有参构造方法)
	t1.start();//5.调用start()方法
	}
}
//实现接口,只是将当前类编程线程任务类。本身不是个线程
//任务是可以多个线程对象共享。
//更灵活!提供了能力,不影响继承。
class MyRunnable implements Runnable{//1.实现Runnale接口
	public void run(){//2.覆盖run()方法(线程任务)
		for(int i=1;i<=50;i++){
			System.out.println("MyRunnable:"+i);
		}	
	}
}

四.线程状态

  1. 新建状态:即初始状态,新建一个线程对象,只在堆中开辟内存,该对象不能占用CPU,不能运行,与常规对象无异
  2. 就绪状态:又称可运行状态,调用start()之后,进入就绪状态。 等待OS选中,获取CPU的使用权,并分配时间片。
  3. 运行状态:线程调度器使某个处于就绪状态的线程获得CPU使用权(即获得时间片),进入运行状态(执行run方法()),如果时间片到期,则回到就绪状态
  4. 终止状态:主线程main()或者独立线程执行完或因异常退出run(),进入终止状态,并释放持有时间片。
    在这里插入图片描述

五.线程常用方法

线程应用程序中的所有方法主要来自Thread类和Object类,下面来自是Thread类的方法

返回类型方法名操作
static voidsleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
static voidyield()暂停当前正在执行的线程对象,并执行其他线程
voidjoin()等待该线程终止
vpidstart()使该线程可运行,Java 虚拟机调用该线程的 run 方法
voidrun()如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回
线程休眠

当前线程主动休眠 millis 毫秒
通过 Thread.sleep(millis)实现
如果写在run方法里,那么只能通过tryCatch处理异常。遵循异常方法的覆盖原则

package com.qf.day28.t3.methods;

public class TestSleep {
	public static void main(String[] args) throws InterruptedException {
		MyThread t1 = new MyThread();
		t1.start();
		
		MyRunnable task = new MyRunnable();
		Thread t2 = new Thread(task);
		t2.start();
		
		
		
		for(int i =1;i<=50;i++) {
			System.out.println(Thread.currentThread().getName()+" - "+i);
			if(i == 30) {//特定条件下休眠
				//通知完t1后,main线程休眠2秒!
				Thread.sleep(2000);//有限期等待。等待时间由参数的毫秒值决定
			}
		}
	}
}
//线程类
class MyThread extends Thread{
	public void run() {
		for(int i = 1;i<=50;i++) {
			//获得当前线程的线程名称
			System.out.println(Thread.currentThread().getName()+" - "+i);
			
		}
	}
}
class MyRunnable implements Runnable{
	public void run(){
		for(int i = 1;i<=50;i++) {
			//获得当前线程的线程名称
			if(i % 2 ==0) {
				System.out.println("线程2得到了偶数!休眠啦!");
				try {
					Thread.sleep(1000);//如果写在run方法里,那么只能通过tryCatch处理异常。遵循异常方法的覆盖原则
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+" - "+i);		
		}
	}
}
线程让步

当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。
通过Thread.yield()实现。

public class TestYield {
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(new Task());
		t1.start();		
		for(int i = 1;i<=50;i++) {
			System.out.println(Thread.currentThread().getName()+" - "+i);
			if(i % 10 ==0) {
				System.out.println("main主动放弃了!");
				Thread.yield();//放弃!主动放弃当前持有的时间片,进入下一次的竞争!
			}
		}
	}
}

class Task implements Runnable{
	public void run() {
		for(int i = 1;i<=50;i++) {
			System.out.println(Thread.currentThread().getName()+" - "+i);
		}
	}
}

线程联合

允许其他线程加入到当前线程中,加入的程序执行完毕后才能执行当前程序
通过 对象.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 = 1;i<=50;i++) {
			System.out.println(Thread.currentThread().getName()+" - "+i);
			if(i==20) {
				System.out.println("main执行到20了!执行t1");
				t1.join();//将t1加入到main线程执行流程中,等待t1线程执行结束后!main再进行竞争时间片!
				//无限期等待!等待条件为调用join方法的线程执行完毕后!再进入就绪状态,竞争时间片
			}
		}
	}
}
class Task2 implements Runnable{
	public void run() {
		for(int i = 1;i<=50;i++) {
			System.out.println(Thread.currentThread().getName()+" - "+i);
		}
	}
}

六.线程同步与锁

线程安全

在这里插入图片描述
线程不安全:
• 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
• 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
• 原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。

线程同步

在程序应用中,可以通过线程同步来保证线程的安全性。
1)同步方式一:
• 同步代码块:
synchronized(临界资源对象)
{ //对临界资源对象加锁 //代码(原子操作)
}
注:
每个对象都有一个互斥锁标记,用来分配给线程的。
只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。
线程退出同步代码块时,会释放相应的互斥锁标记。
2)同步方式二:
• 同步方法:
synchronized 返回值类型 方法名称(形参列表0)
{ //对当前对象(this)加锁 // 代码(原子操作)
}
注:
只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。
线程退出同步方法时,会释放相应的互斥锁标记。

package com.qf.day29.t1.synchronizeds;

import java.util.Vector;

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 A extends Thread{
	
}
class Husband implements Runnable{
	Account acc;
	public Husband(Account acc) {
		this.acc = acc;
	}
	//线程任务:取款!
	public void run() {
//		synchronized(acc) {//对临界资源对象加锁
			this.acc.withdrawal("6002","1234",1200);//原子操作!
//		}
		
	}
}
class Wife implements Runnable{
	Account acc;
	public Wife(Account acc) {
		this.acc = acc;
	}
	//线程任务:取款!
	public void run() {
//		synchronized(acc) {//如果丈夫先拿到了锁,进行原子操作!那么妻子会等!死等!
			this.acc.withdrawal("6002","1234",1200);
//		}
		
	}
}

//银行账户 银行卡
//this   === 当前类的实例对象
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 synchronized 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) {
					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()+"当前卡内余额不足!");
				}
			}else {
				System.out.println(Thread.currentThread().getName()+"卡号或密码错误!");
			}
//		}
	}
}

3)同步规则:
• 注意:
• 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
• 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。
• 已知JDK中线程安全的类:
• StringBuffer
• Vector
• Hashtable
• 以上类中的公开方法,均为synchonized修饰的同步方法
StringBuffer的每一个方法都加了synchronized锁,每次只能运行一个线程;
Vector加了synchronized锁,ArrayList没加;
HashTable也加了synchronized锁;
•之前学习过得内容,但凡线程安全、效率低的 都加了synchronized 同步锁 ,同一时间只允许一个线程进行操作
•效率高、线程不安全 、没有加synchronized 同步锁 , 可以允许多个线程同时对一个内容做操作

•什么场景下加锁?什么场景下不加锁?

	•写(增、删、改) 操作---> 加锁!
	•读操作 不加锁
线程死锁

死锁:
• 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁 标记,并等待A对象锁标记时,产生死锁。
• 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标 记,由此可能造成死锁

public class TestDeadLock {
	public static void main(String[] args) {
		LeftChopstick left = new LeftChopstick();
		RightChopstick right = new RightChopstick();
		
		Thread boy = new Thread(new Boy(left,right));
		Thread girl = new Thread(new Girl(left,right));
		
		boy.start();
		girl.start();
	}
}
class LeftChopstick{
	String name = "左筷子";
}
class RightChopstick{
	String name = "右筷子";
}
class Boy implements Runnable{
	LeftChopstick left;
	RightChopstick right;
	public Boy(LeftChopstick left,RightChopstick right) {
		this.left =left;
		this.right = right;
	}
	public void run() {
		System.out.println("男孩要拿筷子!");
		synchronized(left) {//拿到左筷子资源,加锁!
			try {
				left.wait();//高风亮节!把筷子资源让出去!
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("男孩拿到了左筷子,开始拿右筷子");
			synchronized(right){//拿到右筷子资源,加锁!
				System.out.println("男孩拿到了右筷子,开始吃饭");
			}
		}
	}
}
class Girl implements Runnable{
	LeftChopstick left;
	RightChopstick right;
	public Girl(LeftChopstick left,RightChopstick right) {
		this.left =left;
		this.right = right;
	}
	public void run() {
		System.out.println("女孩要拿筷子!");
		synchronized(right) {//拿到右筷子资源,加锁!
			System.out.println("女孩拿到了右筷子,开始拿左筷子");
			synchronized(left){//拿到左筷子资源,加锁!
				System.out.println("女孩拿到了左筷子,开始吃饭");
				left.notify();//女孩吃完后,唤醒等待左筷子锁的男孩线程
			}
			
		}
	}
}

七.线程的交互

线程之间需要一些协调来共同完成一项任务,借助wait()和notify(),可以实现线程之间交互

public class TestWaitNotify {
	public static void main(String[] args) throws InterruptedException {
		Object obj = new Object();
		MyThread t1 = new MyThread(obj);
		MyThread2 t2 = new MyThread2(obj);
		
		t1.start();
		t2.start();
		
		//主线程通知完两个线程后,休眠。
		Thread.sleep(2000);
		synchronized(obj) {
			System.out.println(Thread.currentThread().getName()+"进入到同步代码块");
			
//			obj.wait();//主线程获得到了锁,也主动释放
			//此时此刻等待队列里有两个线程
//			obj.notify();//在obj的等待队列中,随机唤醒一个拿锁执行代码
			obj.notifyAll();//将obj的等待队列所有的线程都唤醒。
			System.out.println(Thread.currentThread().getName()+"退出了同步代码块");
		}
	}
}

//复杂:一个线程持有A对象的锁,需要B对象的锁,   另一个线程持有B、想要A
//简单:一个线程持有A对象的锁,另一个线程也想要!阻塞

class MyThread extends Thread{
	Object obj;
	public MyThread(Object obj) {
		this.obj = obj;
	}
	public void run() {
		synchronized(obj) {
			System.out.println(Thread.currentThread().getName()+"进入到同步代码块");
			//Thread-0先拿到了锁。高风亮节,让给其他线程先拿锁!
			try {
				obj.wait();//主动释放当前持有的锁!并进入无限期等待!
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"退出了同步代码块");
		}
	}
}

class MyThread2 extends Thread{
	Object obj;
	public MyThread2(Object obj) {
		this.obj = obj;
	}
	public void run() {
		synchronized(obj) {
			System.out.println(Thread.currentThread().getName()+"进入到同步代码块");
			try {
				obj.wait();
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
//			obj.notify();//在obj这个共享对象的等待队列中,唤醒一个正在等待拿锁的线程!
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"退出了同步代码块");
			
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值