1.进程
2.线程
线程属于进程,线程是进程中的独立执行单元 ,多个线程共享同一块内存空间和一组系统资源。
3.线程的创建方式
1.继承 java.lang.Thread 类(线程子类)
2.实现 java.lang.Runnable 接口(线程执行类)
3.实现 java.util.concurrent.Callable 接口,允许子线程返回结果、抛出异常
4.线程池创建 ExecutorService接口表示线程池
4.线程的状态
New:新建状态,新创建的线程,尚未调用start()方法,只有对象;
Runnable:运行状态,运行中的线程,已经调用start()方法,线程正在或即将执行run()方法(running 或 ready);运行状态和就绪状态 start方法后表示可运行,只有得到cpu才可运行。
Blocked:阻塞状态,运行中的线程,等待获取锁,而被挂起,暂不执行;
Waiting:等待状态,运行中的线程,因为sleep()join()等方法调用,进入等待;
Timed Waiting:计时等待状态,运行中的线程,因为执行sleep(等待毫秒值)join(等待毫秒值)等方法,进入计时等待;sleep是不会释放锁的,wait会释放锁。
Terminated:终止状态,线程已终止,因为run( )方法执行完毕或者出现了异常。
5.线程实现通信
多个线程因为在同一个进程中,所以互相通信比较容易的。
核心方法:wait(),notify(),notifyAll();
注意:线程通信一定是多个线程在操作同一个资源才需要进行通信。
线程通信必须先保证线程安全,否则毫无意义,代码也会报错!
// 三个线程存钱public synchronized void saveMoney(double money) {try{// 1.知道是谁来存钱String name = Thread.currentThread().getName();// 2.判断余额是否足够if(this.money > 0){// 5.等待自己,唤醒别人!this.notifyAll();this.wait();}else{// 3.钱没有,存钱this.money += money;System.out.println(name+"来存钱,存入了"+money+"剩余:"+this.money);// 4.等待自己,唤醒别人!this.notifyAll();this.wait();}}catch (Exception e){e.printStackTrace();}}// 两个线程取钱public synchronized void drawMoney(double money) {try{// 1.知道是谁来取钱String name = Thread.currentThread().getName();// 2.判断余额是否足够if(this.money > 0){// 3.账户有钱,有钱可以取this.money -= money;System.out.println(name+"来取钱"+money+"取钱后剩余:"+this.money);// 4.没钱,先唤醒别人,等待自己,。this.notifyAll();this.wait();}else{// 5.余额不足,没钱,先唤醒别人,等待自己,。this.notifyAll();this.wait();}}catch (Exception e){e.printStackTrace();}}
6.线程实现同步
作用:就是为了解决线程安全问题的方案。
线程同步解决线程安全问题的核心思想:让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
6.1同步代码块
把出现线程安全问题的核心代码给上锁,每次只能一个线程进入。 执行完毕以后自动解锁,其他线程才可以进来执行。
public void drawMoney(double moeny) {// 开始判断取钱逻辑// 1.先知道是谁来取钱// 同步方法,同步代码块 ,如果为静态方法,使用class类同步 例如Account.classString name = Thread.currentThread().getName();// 2.判断余额是否足够synchronized (this){if(this.moeny >= moeny){System.out.println(name+"来取钱,余额足够,吐出"+moeny);// 3.更新余额this.moeny -= moeny;System.out.println(name+"来取钱后,余额剩余"+ this.moeny);}else{System.out.println(name+"来取钱,余额不足!");}}}
6.2同步方法
把出现线程安全问题的核心方法给锁起来,每次只能一个线程进入访问,其他线程必须在方法外面等待。直接给方法加上一个修饰符 synchronized.
public synchronized void drawMoney(double moeny) {// 开始判断取钱逻辑// 1.先知道是谁来取钱// 同步方法,同步代码块 ,如果为静态方法,使用class类同步 例如Account.classString name = Thread.currentThread().getName();// 2.判断余额是否足够if(this.moeny >= moeny){System.out.println(name+"来取钱,余额足够,吐出"+moeny);// 3.更新余额this.moeny -= moeny;System.out.println(name+"来取钱后,余额剩余"+ this.moeny);}else{System.out.println(name+"来取钱,余额不足!");}}
6.3Lock锁
java.util.concurrent.locks.Lock提供的锁。
加锁:lock() 释放锁:unlock()
public void drawMoney(double moeny) {// 开始判断取钱逻辑// 1.先知道是谁来取钱// 使用Lock实现同步,注意lock锁住部分一定要用try catch finally,如果不使用如果锁住的部分出现异常,无法释放锁的资源String name = Thread.currentThread().getName();lock.lock(); //上锁try {// 2.判断余额是否足够if(this.moeny >= moeny){System.out.println(name+"来取钱,余额足够,吐出"+moeny);// 3.更新余额this.moeny -= moeny;System.out.println(name+"来取钱后,余额剩余"+ this.moeny);}else{System.out.println(name+"来取钱,余额不足!");}} catch (Exception e) {e.printStackTrace();}finally {lock.unlock(); //解锁}}
7.线程死锁问题
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
死锁产生的四个必要条件:(四个条件都满足才会形成死锁)
1、资源被一个线程使用(占有)时,别的线程不能使用。
2、资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待情况,p1要p2的资源,p2要p1的资源。这样就形成了一个等待环路
public class ThreadDead {public static Object resource01 =new Object();public static Object resource02=new Object();public static void main(String[] args) {new Thread(new Runnable(){@Overridepublic void run() {synchronized (resource01){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程占用资源1");synchronized (resource02){System.out.println("线程占用资源2");}}}}).start();new Thread(new Runnable(){@Overridepublic void run() {synchronized (resource02){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程占用资源2");synchronized (resource01){System.out.println("线程占用资源1");}}}}).start();}}
8.volitale关键字
它是在别的线程进行改动后,他会通知别的线程有改动,然后更新之前的值。
所有的成员变量和静态变量都会存在主存中,主存中的变量可以被多个线程共享。每个线程都有专属于自己的空间,工作内存一开始存储的是成员变量的副本。所以很多时候线程访问的自己空间中的变量,其他线程改变值将不可见。
解决方案:
1.加锁:线程获取锁之后,会清空工作内存,从主内存中拷贝共享变量最新的值到自己的工作空间。
2.使用volatile关键字:使用关键字主要是因为修改值之后他会通知别的线程,然后进行替换,保证了修改后的可见性。
volatile 和 synchronized的区别:
1.volatile 只能修饰成员变量和静态变量,而synchronized可以修饰方法和代码块。
2.volatile 只保证数据的可见性,但不保证多线程的写操作,不保证线程安全。
public class VolitaleDemo extends Thread{// private volatile boolean flag=false;private boolean flag=false;@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}flag=true;System.out.println("flag="+flag);}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}}class MainDemo{public static void main(String[] args) {// VolitaleDemo volitaleDemo=new VolitaleDemo();// volitaleDemo.start();//// while(true){// if(volitaleDemo.isFlag()) {// System.out.println("主线程在执行中~~~~");// }// }VolitaleDemo volitaleDemo=new VolitaleDemo();volitaleDemo.start();while(true){synchronized (MainDemo.class){if(volitaleDemo.isFlag()) {System.out.println("主线程在执行中~~~~");}}}}}
9.线程安全问题
多个线程同时操作同一个共享资源的时候可能会出现线程安全问题。
// 账户类public class Account {private String cardID;private double moeny;public void drawMoney(double moeny) {// 开始判断取钱逻辑// 1.先知道是谁来取钱String name = Thread.currentThread().getName();// 2.判断余额是否足够if(this.moeny >= moeny){System.out.println(name+"来取钱,余额足够,吐出"+moeny);// 3.更新余额this.moeny -= moeny;System.out.println(name+"来取钱后,余额剩余"+ this.moeny);}else{System.out.println(name+"来取钱,余额不足!");}}
// 线程类:创建取钱线程对象的。public class DrawThread extends Thread{// 定义一个成员变量接收账户对象private Account acc ;public DrawThread(Account acc,String name){super(name);this.acc = acc;}@Overridepublic void run() {// 小明 小红// 取钱100000acc.drawMoney(100000);}}
public class ThreadSafe {public static void main(String[] args) {// a.创建一个共享资源账户对象!Account acc = new Account("ICBC-110" , 100000);// b.创建2个线程对象去账户对象中取钱Thread littleMing = new DrawThread(acc,"小明");littleMing.start();Thread litterRed = new DrawThread(acc,"小红");litterRed.start();}}
