-
线程状态图:
- New状态:新建状态 【new 一个Thread 或者 new 一个Thread子类对象】
- Runable状态: 运行状态 【多个线程抢占CPU资源,谁抢到CPU资源,该线程则为运行状态,未抢到CPU资源的线程则为阻塞状态】【运行状态中的线程调用 wait()方法,则该线程将会进入永久等待状态,调用notify()方法,如果CPU空闲则会进入运行状态,否则进入阻塞状态】
- Block状态: 阻塞状态 【多个线程抢占CPU资源,谁抢到CPU资源,该线程则为运行状态,未抢到CPU资源的线程则为阻塞状态】
- Terminated状态:死亡状态 【线程执行完run()方法或调用stop()方法后或者线程中产生了异常】
- Timed_Waiting状态:休眠状态/计时等待 【线程调用sleep(long)方法或者wait(long)方法】【休眠状态结束 如果CPU被占用,那么该线程则成为阻塞状态】
- Waiting状态:永久等待状态 【线程执行】 【与休眠状态的区别是 休眠状态会自动唤醒,而永久等待状态需要调用notify()方法才能被唤醒】
-
创建线程的方式
方式一:继承Thread父类 实现父类Run方法
public class Test2 {
public static void main(String[] args){
MakeHuotui makeHuotui = new MakeHuotui();
GetHuotui getHuotui = new GetHuotui();
makeHuotui.start();
getHuotui.start();
}
}
class MakeHuotui extends Thread{
@Override
public void run() {
System.out.println("制作火腿....");
}
}
class GetHuotui extends Thread{
@Override
public void run() {
System.err.println("获取火腿....");
}
}
- 方式二:实现Runable接口
package javaBase2; public class Test2 { public static void main(String[] args){ Thread t1 = new Thread(new MakeA()); Thread t2 = new Thread(new GetA()); t1.start(); t2.start(); } } class MakeA implements Runnable{ @Override public void run() { for (int i=0; i<1000; i++){ System.out.println(i); } } } class GetA implements Runnable{ @Override public void run() { for (int i=0; i<1000; i++){ System.err.println(i); } } }
两者不同:
- 一个是用继承实现 java是单继承模式,这样就是造成一个类的局限性
- 一个是用接口接口实现,接口是多实现,这样就避免了类的局限性
- 通过代码可以看到两者的启动方式也有所不同【一般推荐使用接口实现方式】
-
多线程造成的一些问题:
举个例子:public class Test { public static void main(String[] args) { // 模拟买票案例 同时开启三个线程 共享票源资源 RunableImpl run = new RunableImpl(); Thread t1 = new Thread(run); Thread t2 = new Thread(run); Thread t3 = new Thread(run); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } } class RunableImpl implements Runnable{ // 定义多个线程共享的票源 private int ticket = 100; @Override public void run() { while(true){ // 判断票是否存在 if(ticket>0){ try { // 线程休眠10毫秒 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"-----> 正在卖第"+ticket+"号票"); ticket--; }else { break; } } } }
这是一个模拟电影院买票场景,创建三个线程分别模拟三个售票窗口,共享电影票100张这个资源,这样会造成什么问题呢,请看结果:
现象1:出现卖出负数票
现象2:卖出相同的票
造成这个现象的原因:
怎么说呢,emmm只可意会不能言传,你可以这么理解线程在不断的抢占CPU资源,这个抢占的时间间隔是非常小的
理论上的你们可以看看这个B站的视频:线程安全问题产生的原理。
如何解决线程安全问题
-
方法一:同步代码块
// 格式
synchronized(同步锁){
// 需要同步操作的代码块
}
- 锁对象:即自定义同步锁对象;
- 将上面的例子改成一个同步代码块的形式:
package javaBase3; public class Test { public static void main(String[] args) { // 模拟买票案例 同时开启三个线程 共享票源资源 RunableImpl run = new RunableImpl(); Thread t1 = new Thread(run); Thread t2 = new Thread(run); Thread t3 = new Thread(run); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } } class RunableImpl implements Runnable{ // 定义多个线程共享的票源 private int ticket = 100; // 创建一个锁对象 可以是任意对象 private Object object = new Object(); @Override public void run() { // 同步代码块 synchronized (object){ while(true){ // 判断票是否存在 if(ticket>0){ try { // 线程休眠10毫秒 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"-----> 正在卖第"+ticket+"号票"); ticket--; }else { break; } } } } }
实现原理:通俗的理解就是 当一个线程抢占到CPU资源时,该线程就会拿到对象锁(对象监视器),直到同步代码块全部执行完毕
-
方法二:同步方法
步骤:1 将访问了共享数据的的代码抽取出来,放到一个方法中 2 在方法上加上synchronized
锁对象:即实现Runable接口的实现类对象
将原例子改成一个同步方法
package javaBase4;
public class Test {
public static void main(String[] args) {
myThread mythread = new myThread();
Thread t1 = new Thread(mythread);
Thread t2 = new Thread(mythread);
Thread t3 = new Thread(mythread);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class myThread implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
payTicket();
}
}
// 定义一个同步方法
public synchronized void payTicket(){
if (ticket > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在售出第------->" + ticket + "号票");
ticket--;
} else {
System.err.println("票已经售光");
}
}
}
-
方法三:静态同步方法
锁对象:静态方法的是优先于对象创建,所有它与同步方法的锁对象不同;静态方法的锁对象就是本类class属性--->class文件对象
package javaBase5;
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread);
Thread t2 = new Thread(myThread);
Thread t3 = new Thread(myThread);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class MyThread implements Runnable {
private static int ticket = 100;
@Override
public void run() {
MyThread.payTicket();
}
public static synchronized void payTicket(){
while (true) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在售出--------" + ticket + "号票");
ticket--;
} else {
System.err.println("票已经卖完");
break;
}
}
}
}
-
方法四:Lock锁:
在jdk1.5之后出现 lock实现了比synchronized方法和语句更广泛的的锁机制
Lock接口中的两个方法
void lock() 获取锁
void unlock() 释放锁
Lock接口的一个实现类:ReentrantLock
使用步骤: 1 在成员位置创建一个ReetrantLock对象; 2 在可能出现线程安全问题的代码前调用lock接口中的方法 lock()获取锁
3 在可能出现线程安全问题的代码后调用lock接口中国的方法unlock()释放锁;
package javaBase5;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread);
Thread t2 = new Thread(myThread);
Thread t3 = new Thread(myThread);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class MyThread implements Runnable {
// 定义共享影票资源
private static int ticket = 100;
// 在成员位置创建一个ReentrantLock()对象
Lock mylock = new ReentrantLock();
@Override
public void run() {
// 在可能出现线程安全问题的代码钱 使用lock接口中的方法lock()获取锁
mylock.lock();
while (true) {
if (ticket > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在售出--------" + ticket + "号票");
ticket--;
} else {
System.err.println("票已经卖完");
break;
}
}
// 在可能出现线程安全问题的代码后,使用lock接口中的方法unloc() 释放锁
mylock.unlock();
}
}
对上面的代码进行一个优化【即无论最后是否出现线程安全问题都将释放锁】
package javaBase5;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread);
Thread t2 = new Thread(myThread);
Thread t3 = new Thread(myThread);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class MyThread implements Runnable {
// 定义共享影票资源
private static int ticket = 10000;
// 在成员位置创建一个ReentrantLock()对象
Lock mylock = new ReentrantLock();
@Override
public void run() {
while (true) {
//在可能出现线程安全问题的代码钱 使用lock接口中的方法lock()获取锁
mylock.lock();
if (ticket > 0) {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "正在售出--------" + ticket + "号票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//在可能出现线程安全问题的代码后,使用lock接口中的方法unlock() 释放锁
mylock.unlock();
}
}else {
System.err.println("电影票已经售完");
break;
}
}
}
}