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.class
String 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.class
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+"来取钱,余额不足!");
}
}
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(){
@Override
public 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(){
@Override
public 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;
@Override
public 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;
}
@Override
public void run() {
// 小明 小红
// 取钱100000
acc.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();
}
}