一.进程与线程
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);
}
}
}
四.线程状态
- 新建状态:即初始状态,新建一个线程对象,只在堆中开辟内存,该对象不能占用CPU,不能运行,与常规对象无异
- 就绪状态:又称可运行状态,调用start()之后,进入就绪状态。 等待OS选中,获取CPU的使用权,并分配时间片。
- 运行状态:线程调度器使某个处于就绪状态的线程获得CPU使用权(即获得时间片),进入运行状态(执行run方法()),如果时间片到期,则回到就绪状态
- 终止状态:主线程main()或者独立线程执行完或因异常退出run(),进入终止状态,并释放持有时间片。
五.线程常用方法
线程应用程序中的所有方法主要来自Thread类和Object类,下面来自是Thread类的方法
返回类型 | 方法名 | 操作 |
---|---|---|
static void | sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) |
static void | yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void | join() | 等待该线程终止 |
vpid | start() | 使该线程可运行,Java 虚拟机调用该线程的 run 方法 |
void | run() | 如果该线程是使用独立的 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()+"退出了同步代码块");
}
}
}