目录
并发、并行
java进程中涉及到两个线程工作的原理:1.并发,2.并行
1.并发:
并发指单核cpu交替执行多个线程,当然不排除多核cpu在资源紧张的时候,调用一个cpu 去并行多个线程
2. 并行:
并行就是多核cpu 分别调用相应个数的cpu去执行多个线程
那就用下面一段代码来查看一下你电脑的cpu 个数吧:
package xiancheng.bingfa.com;
public class CpuNum {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
// 获取当前电脑CPU个数
int cpu=runtime.availableProcessors();
System.out.println("当前电脑CPU个数为:"+cpu);
}
}
我们知道在使用对象去调用相应的方法时必须要等当前方法执行完毕后才会开始执行后面的代码,并且不能同时使多个方法在同一时间执行,那么多线程就可以做到同时执行多个线程,让我们来看看实现多线程需要做到哪些条件吧:(相应解释和注意事项我已写如代码块中,便于大家解读)
实现多线程的条件:
package xiancheng.bingfa.com;
import org.junit.jupiter.api.Test;
// 由于java 类只能实现单继承,当一个类已经继承了其他类时就不能再继承Thread 类来实现多线程,
// 因此这里可以实现Runnable 接口来实现多线程
public class Runnable_Test {
public static void main(String[] args) {
}
@Test
public void Test01(){
Dog dog = new Dog();
// 此时不能再 dog.start();
// 创建一个Thread 对象,把实现Runnable接口的dog对象放进去
Thread thread = new Thread(dog);
// 这里底层使用了设计模式【静态代理模式】
thread.start();
}
}
class Animal{}
class Tiger extends Animal implements Runnable{
@Override
public void run() {
System.out.println("老虎嗷嗷叫 "+Thread.currentThread().getName());
}
}
class Dog implements Runnable{
int count=0;
@Override
public void run() {
while (true){
System.out.println("小狗汪汪叫:Hi "+(++count)+Thread.currentThread().getName());
// 休眠一秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count==10){
break;
}
}
}
}
实现Runnable 接口是实现多线程的一种方法,那接下来就让我们来看看另一种方法吧:
package xiancheng.bingfa.com;
public class Thread_Runnable {
public static void main(String[] args) {
// 创建两个线程,一个线程每隔一秒输出"Hello World",输出10次,退出,另一个线程每隔一秒输出"Hi",输出5次退出
A a = new A();
a.start();// 启动一个线程
B b = new B();
b.start();// 启动另一个线程
}
}
class A extends Thread{
private int i=0;
@Override
public void run() {
while (true){
System.out.println("Hello World "+(++i)+Thread.currentThread().getName());
// 使程序暂停1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i==10){
break;
}
}
}
}
class B extends Thread{
private int count;
@Override
public void run() {
while (true){
System.out.println("Hi "+(++count)+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count==5){
break;
}
}
}
}
那通过上面大家可以发现和之前不一样的是,我们通过b.start();来启动线程,那为什么不 b.run(); 呢?接下来就来看看实现多线程的底层源码以及这个问题的解答吧:
package xiancheng.bingfa.com;
// 演示通过继承Thread 类创建线程
public class Thread_Test {// 执行该类启动进程
public static void main(String[] args) {// 开启主线程main
// 创建一个Cat 对象,可以当作线程使用了
Cat cat = new Cat();
cat.start();// 启动线程
/**
* 那为什么不 cat.run(); 呢? 因为这样就只有main 线程了,因为cat.run();就只是一个简单的方法,并没有其他子线程
* 源码:
*1. public synchronized void start() {
* start0();
* }
* // start0(); 是本地方法,是由 JVM 来调用的,底层为C/C++实现
* // 真正实现多线程的效果,是start0(),而不是 run
* 2. private native void start0();
*/
// 当main线程启动一个子线程 Thread-0,主线程不会阻塞,会继续执行,
// 就是和方法调用不同,它不等子线程运行结束就会继续执行主线程
System.out.println("主线程执行:"+Thread.currentThread().getName());//main
//此时可以发现主线程与子线程是交替执行的 -> 并行(多核CPU) 如果单核CPU则为并发
// 注意: 要清楚的知道,主线程main结束了,进程不一定结束了(因为可能还有子线程正在进行)
// 只有所有的线程都结束了整个进程才会结束
for (int i=0;i<10;i++){
System.out.println("主线程 i="+i);
// 同样让主线程休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 1. 当一个类继承了Thread 类,该类就可以当作一个线程使用
// 2. 我们会重写 run 方法,写上自己的业务逻辑
// 3. run Thread 类实现了 Runnable 接口的run方法
/**
* @Override
* public void run() {
* if (target != null) {
* target.run();
* }
* }
*/
class Cat extends Thread{
private int time=0;
@Override
public void run() {// 重写 run 方法,写上自己的业务逻辑
// 该线程每隔一秒,在控制台输出”喵喵,我是一只小猫咪“
while (true) {
System.out.println("喵喵,我是一只小猫咪"+(++time)+"线程名:"+Thread.currentThread().getName());
// 让线程休眠一秒 Ctrl+Alt+t ->异常处理快捷键
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (time==80){
break;// 当time 到达80,就退出循环
}
}
}
}
那为了让大家更容易看懂这个线程是怎样运行的,我为大家写一段代码模拟一个Thread 类,看他是如何通过调用start方法来实现多线程的:
import org.junit.jupiter.api.Test;
public class Runnable_Test {
public static void main(String[] args) {
}
@Test
public void Test02(){
Dog dog = new Dog();
Proxy proxy = new Proxy(dog);
proxy.strat();
}
@Test
public void Test03(){
Tiger tiger = new Tiger();
Proxy proxy = new Proxy(tiger);
proxy.strat();
}
}
// 代码模拟 实现Runnable 接口 开发线程的机制
// 线程代理类 模拟了一个极简单的Thread 类
class Proxy implements Runnable{// 你可以把Proxy(代理)类当作 Thread 类来使用
private Runnable taget=null;// 设置一个Runnable 类型的属性
public Proxy(Runnable taget) {
this.taget = taget;
}
@Override
public void run() {
if (taget!=null){
taget.run();
}
}
public void strat(){
strat0();// 真正实现多线程的方法
}
public void strat0(){
run();
}
}
多线程主要方法:
那下面我们来看看线程的主要方法吧:
package xiancheng.duoxiancheng;
public class Thread_method {
public static void main(String[] args) {
Test02();
}
public static void Test01(){
// 线程常用方法: 第一组
TDemo1 tDemo1 = new TDemo1();
// 1.setName(String s): 设置线程名
tDemo1.setName("马俊毅");
// 2. setPriority(): 设置优先级
tDemo1.setPriority(Thread.MIN_PRIORITY);
tDemo1.start();
// main 线程打印5句 Hi,然后中断子线程的休眠
for (int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Hi "+i);
}
// 3. getName(): 获取线程名
// 4. getPriority(): 获取线程优先级
System.out.println(tDemo1.getName()+" 优先级为:"+tDemo1.getPriority());
// 5. interrupt(); 中断线程休眠(线程正在休眠)或者中断线程
tDemo1.interrupt();// 当执行到这里时就会中断t 线程休眠
}
public static void Test02(){
// 线程常用方法: 第二组
/**
* 案例:创建一子线程,每隔1s输出hello,输出20次,主线程每隔1s输出Hi,输出20次
* 要求:两个线程同时进行,当主线程输出5次后,就让子线程运行完毕,主线程再继续执行
*/
// 1. yield(让步,放弃): 线程的礼让。让出CUP,让其他的线程执行,但礼让的时间不确定,所以也不一定成功
// 2. join: 线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
// t1.join(); 人必须要等t1 执行完毕后才执行main 线程
TDemo2 tDemo2 = new TDemo2();
tDemo2.start();// 启动子线程
for (int i=1;i<=20;i++){
System.out.println("Hi");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i==5){
// Thread.yield();// 主线程让步(由于都是多核CPU,资源一般较为充足,因此该方法不容易成功)0
try {
tDemo2.join();// 子线程插队
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class TDemo2 extends Thread{
private int count=0;
@Override
public void run() {
while (true){
System.out.println("Hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (++count==20){
break;
}
}
}
}
class TDemo1 extends Thread{
@Override
public void run() {
while (true){
for (int i=0;i<100;i++){
// Thread.currentThread().getName() 获取当前线程名称
System.out.println(Thread.currentThread().getName()+" 吃包子~~~~~~");
}
try {
System.out.println(Thread.currentThread().getName()+" 休眠中~~~~");
Thread.sleep(20000);
} catch (InterruptedException e) {
// 当线程执行到一个interrupt 方法时,就会catch 一个异常,可以加入自己的业务代码
// InterruptedException 是捕获到一个中断异常(不是终止异常)
System.out.println(Thread.currentThread().getName()+" 被 interrupt 了!!!");
}
}
}
}
线程终止:
线程终止就是在线程执行过程中出现异常,从而终止线程
package xiancheng.duoxiancheng;
public class Thread_Stop {
public static void main(String[] args) {
/**
* 线程终止(退出):
* 1. 当线程完成任务后,会自动退出
* 2. 还可以通过使用变量来控制 run方法退出的方式停止线程,即通知方式
*
* 需求: 启动一个线程 t,要求在main 线程中去停止线程 t
*/
T01 t01 = new T01();
t01.start();
// 如果希望main线程去控制t1 线程的终止,就必须可以修改loop变量
// 让t1 退出run方法,从而终止 t1 线程 -> 通知方式
// 先让main 线程休眠10秒,再通知t1 线程终止退出
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t01.setLoop(false);
}
}
class T01 extends Thread{
private int count=0;
// 设置一个控制变量
private boolean loop=true;
@Override
public void run() {
while (loop){
System.out.println(Thread.currentThread().getName()+" 线程正在运行中 "+(++count));
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
线程插队:
线程插队是告诉cpu 暂停当前执行的线程,先执行插队的线程,等插队线程执行完毕后再去执行之前的线程
package xiancheng.duoxiancheng;
// java 线程插队练习
public class Thread_join {
public static void main(String[] args) {
/**
* 要求: 主线程每隔1s,输出Hi,一共10次,当输出到Hi 5时,启动一个子线程(要求实现Runnable 接口),
* 每隔1s 输出Hello ,等该线程输出10次 Hello后,退出主线程继续输出Hi,直到主线程结束
*/
Tjoin tjoin = new Tjoin();
Thread thread = new Thread(tjoin);
for (int i=1; i<=10;i++){
System.out.println("Hi "+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i==5){
thread.start();//启动子线程
try {
thread.join();// 插队
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("主线程结束~~~");
}
}
class Tjoin implements Runnable{
private int count=0;
@Override
public void run() {
while (true) {
System.out.println("Hello "+(++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count==10){
System.out.println("子线程结束~~~");
break;
}
}
}
}
守护线程:
守护线程的意思就是,当一个子线程被设置为守护线程后,在主线程结束后,不管该子线程是否执行结束,都让他结束
package xiancheng.duoxiancheng;
// 用户线程和守护线程
public class Guard_Thread {
public static void main(String[] args) {
/**
* 1. 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
* 2. 守护线程: 一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
* 3. 常见的守护线程:垃圾回收机制
*/
MyDaemon myDaemon = new MyDaemon();
// 如果我们希望在主线程结束后,子线程自动结束、
// 只需将子线程设置成一个守护线程即可
myDaemon.setDaemon(true);
myDaemon.start();// 必须要先设置再启动,否则会抛出异常
for (int i=1;i<=10;i++){
System.out.println("宝强在幸苦的工作~~~~");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyDaemon extends Thread{
@Override
public void run() {
for ( ; ; ){// 无限循环
System.out.println("马蓉和宋泽快乐的聊天~~~~");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程的七大状态:
那我们先通过一张图来看一下线程各个状态之间的关系吧:
那我先为大家介绍一下线程最为主要的五大状态:
线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。
1.新建状态(New): 当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码
2.就绪状态(Runnable)
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。
3.运行状态(Running)
当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.
4.阻塞状态(Blocked)
线程运行过程中,可能由于各种原因进入阻塞状态: (1)线程通过调用sleep方法进入睡眠状态; (2)线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者; (3)线程试图得到一个锁,而该锁正被其他线程持有; (4)线程在等待某个触发条件;
所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
5.死亡状态(Dead)
有两个原因会导致线程死亡: (1) run方法正常退出而自然死亡, (2) 一个未捕获的异常终止了run方法而使线程猝死。 为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.
具体的线程状态可以通过 查看jdk1.8源码中的 Enum Thread.State 枚举类。
package xiancheng.duoxiancheng;
// 线程的七大状态
// 具体的线程状态可以通过 查看jdk1.8源码中的 Enum Thread.State 枚举类
public class Thread_State {
public static void main(String[] args) {
MyT myT = new MyT();
// 1. 查看状态 getState()
System.out.println(myT.getName()+" 状态"+myT.getState());
myT.start();
while (Thread.State.TERMINATED!=myT.getState()){// 只要线程状态不是终止状态 就输出
System.out.println(myT.getName()+"状态"+myT.getState());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(myT.getState());
}
}
class MyT extends Thread{
@Override
public void run() {
while (true){
for (int i=1;i<=10;i++){
System.out.println("Hello "+i);
try {
Thread.sleep(500);// 只要调用了sleep()方法就会进入休眠状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
}
}
那么通过上面对java 多线程机制的了解让我们来看一看下面这道题吧,看看会出什么问题呢?
package xiancheng.duoxiancheng;
import org.junit.jupiter.api.Test;
public class Thread_RunnableTest {
public static void main(String[] args) {
/**
* 模拟三个售票窗口售票 ,一共100张
* 1. 分别使用 继承Thread 和实现Runnable 方式
* 2. 并分析有什么问题
*
* 问题分析,由于线程的同时进行,可能出现多个窗口同时卖出一张票或者在只剩一张票或者两张票的时候出现超卖现象
*/
Test02();
}
public static void Test01(){
// 继承 Thread 类实现、
A01 a01 = new A01();// 窗口一
A01 a02 = new A01();//窗口二
A01 a03 = new A01();//窗口三
a01.start();
a02.start();
a03.start();
// 出现超卖现象
}
public static void Test02(){
// 实现 Runnable 接口实现
B01 b01 = new B01();
Thread thread01 = new Thread(b01);
Thread thread02 = new Thread(b01);
Thread thread03 = new Thread(b01);// 资源共享
thread01.start();
thread02.start();
thread03.start();
// 同样出现超卖现象
}
}
class A01 extends Thread{
private int ticketNum=100;// 因为只创建一个对象,因此可以不用static 修饰
@Override
public void run() {
while (true){
// 没票就退出
if (ticketNum<=0){
System.out.println("票已卖完!!!");
break;
}
// 如果还有票,休眠50毫秒 模拟现实
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口"+Thread.currentThread().getName()+"售出了一张票"+
"剩余票数"+(--ticketNum));
}
}
}
class B01 implements Runnable{
private static int ticketNum=100;// 因为票只有一百张,所以三个售票窗口共享100张票
@Override
public void run() {
while (true){
// 没票就退出
if (ticketNum<=0){
System.out.println("票已卖完!!!");
break;
}
// 如果还有票,休眠50毫秒 模拟现实
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口"+Thread.currentThread().getName()+"售出了一张票"+
"剩余票数"+(--ticketNum));
}
}
}
运行示例:
显然票数不能为负数,而且还出现了多个窗口卖出的票是同一张的问题,那是为什么呢 ?
在多个线程同时执行时,出现了多个线程同时对同一个对象(火车票)操作的情况 ,从而导致了上面问题的出现,那么该如何解决呢? 这就涉及到了我们的线程同步问题:
线程同步:
package xiancheng.duoxiancheng;
// 线程同步机制 synchronized
/**
*线程同步机制 :
* 1. 在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,
* 保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性
* 2. 也可以理解:线程同步,即当有一个线程对内存进行操作时,其他线程都不能对这个内存地址进行操作,
* 直到该线程完成操作,其他线程才能对该内存地址进行操作
*/
public class Thread_Synchronized {
public static void main(String[] args) {
/**
* 如何实现同步? synchronized
* 1. 同步代码块
* synchronized(对象){// 得到对象的锁,才能操作同步代码
* // 需要被同步代码
* }
* 2. synchronized 还可以放在方法声明中,表示整个方法->为同步方法
* public synchronized void m(String name){
* // 需要同步的代码
* }
*/
// 解决卖火车票出现的问题
Test01();
//Test02();
}
public static void Test01(){
// 继承 Thread 类实现、
MyTest01 a01 = new MyTest01();// 窗口一
MyTest01 a02 = new MyTest01();//窗口二
MyTest01 a03 = new MyTest01();//窗口三
a01.start();
a02.start();
a03.start();
// 可以发现依然存在超卖问题
}
public static void Test02(){
// 实现 Runnable 接口实现
MyTest02 myTest02 = new MyTest02();
Thread thread01 = new Thread(myTest02);
Thread thread02 = new Thread(myTest02);
Thread thread03 = new Thread(myTest02);// 资源共享
thread01.start();
thread02.start();
thread03.start();
//可以直接 new Thread(myTest02).start();
// 没有再出现超卖问题
}
}
class MyTest01 extends Thread{
private static int ticketNum=100;// 因为票只有一百张,所以三个售票窗口共享100张票
private static boolean loop=true;
@Override
public void run() {
while (loop){
sell();
}
}
public synchronized void sell(){// 把sell方法上锁(同步方法),在同一个时刻,只允许一个线程来执行sell方法
// 没票就退出
if (ticketNum<=0){
System.out.println("票已卖完!!!");
loop=false;
}
else {
// 如果还有票,休眠50毫秒 模拟现实
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口"+Thread.currentThread().getName()+"售出了一张票"+
"剩余票数"+(--ticketNum));
}
}
}
// 使用Synchronized 实现线程同步
class MyTest02 implements Runnable{
private int ticketNum=100;// 因为只创建一个对象,因此可以不用static 修饰
private boolean loop=true;
@Override
public void run() {
while (loop){
sell();
}
}
public synchronized void sell(){// 把sell方法上锁(同步方法),在同一个时刻,只允许一个线程来执行sell方法
// 没票就退出
if (ticketNum<=0){
System.out.println("票已卖完!!!");
loop=false;
}
else {
// 如果还有票,休眠50毫秒 模拟现实
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口"+Thread.currentThread().getName()+"售出了一张票"+
"剩余票数"+(--ticketNum));
}
}
}
对于上面的实现Runnable 接口的Test02方法已经解决了之前的问题,但继承Thread 类的Test02 方法依然没有解决问题,那该怎么办呢? 在接下来的 互斥锁中我将为大家进行详细的讲解:
互斥锁:
package xiancheng.duoxiancheng;
/**
* 互斥锁
* 基本介绍
* 1.java在java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
* 2.每个对象都对应于一个可称为"互斥锁"的标记,这个标记用来保证在任一时刻,只
* 能有一个线程访问该对象
* 3.关键字 synchronized来与对象的互斥锁联系当某个对象用 synchronized修饰时,
* 表明该对象在任一时刻只能由一个线程访问
* 4.同步的局限性:导致程序的执行效率要降低
* 5.同步方法(非静态的)的锁可以是this,可以是其他对象要求是同一个对象
* 6.同步方法(静态的)的锁为当前类本身
*/
public class Thread_lock {
public static void main(String[] args) {
Test01();
//Test02();
}
/**
* 总结:
* ●注意事项和细节:
* 1.同步方法如果没有使用 static修饰:默认锁对象为this
* 2.如果方法使用static修饰,默认锁对象当前class
* 3.实现的落地步骤:
* 需要先分析上锁的代码
* 选择同步代码块或同步方法
* 要求多个线程的锁对象为同一个即可!
*/
public static void Test01(){
// 继承 Thread 类实现、
MyTest03 a01 = new MyTest03();// 窗口一
MyTest03 a02 = new MyTest03();//窗口二
MyTest03 a03 = new MyTest03();//窗口三
a01.start();
a02.start();
a03.start();
// 将方法 public synchronized static void sell(){} 设为static 后没有在出现问题
// 因为继承Thread 类的线程,创建了三个对象,在操作线程时,方法属于类,此时的锁也是加在类上的,
// 不同的对象在线程同步时也能受到互斥锁的限制,从而不再出现超卖现象
}
public static void Test02(){
// 实现 Runnable 接口实现
MyTest04 myTest02 = new MyTest04();
Thread thread01 = new Thread(myTest02);
Thread thread02 = new Thread(myTest02);
Thread thread03 = new Thread(myTest02);// 资源共享
thread01.start();
thread02.start();
thread03.start();
//可以直接 new Thread(myTest02).start();
// 没有再出现超卖问题
}
}
class MyTest03 extends Thread{
private static int ticketNum=100;// 因为票只有一百张,所以三个售票窗口共享100张票
private static boolean loop=true;
@Override
public void run() {
while (loop){
sell();
}
}
// public synchronized static void sell(){} 锁是加在类上的
// 当然此时也可以使用代码块的互斥锁进行线程同步,但由于该方法为 static ,应将this/object 改为MyTest03.class
public /*synchronized*/ static void sell(){// 把sell方法上锁(同步方法),在同一个时刻,只允许一个线程来执行sell方法
synchronized (MyTest03.class) {
// 没票就退出
if (ticketNum <= 0) {
System.out.println("票已卖完!!!");
loop = false;
} else {
// 如果还有票,休眠50毫秒 模拟现实
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出了一张票" +
"剩余票数" + (--ticketNum));
}
}
}
}
// 使用Synchronized 实现线程同步
class MyTest04 implements Runnable{
private int ticketNum=100;// 因为只创建一个对象,因此可以不用static 修饰
private boolean loop=true;
Object object= new Object();
@Override
public void run() {
while (loop){
sell();
}
}
// public synchronized void sell(){}就是一个同步方法
//1. 这时锁在 this 对象
// 2. 也可以在代码块上写 synchronized ,同步代码块,此时的互斥锁依然是在 this 对象,
// 当然也可以是其他对象,但必须是同一个对象
public /*synchronized*/ void sell(){// 把sell方法上锁(同步方法),在同一个时刻,只允许一个线程来执行sell方法
synchronized (/*this*/object) {//都是同一个对象
// 没票就退出
if (ticketNum <= 0) {
System.out.println("票已卖完!!!");
loop = false;
} else {
// 如果还有票,休眠50毫秒 模拟现实
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出了一张票" +
"剩余票数" + (--ticketNum));
}
}
}
}
运行示例:
那通过上面的代码及解释呢,我们已经解决了火车票超卖的问题。
线程死锁:
package xiancheng.duoxiancheng;
// 线程死锁
public class Thread_Dielock {
public static void main(String[] args) {
// 多线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程时一定要避免死锁的发生
// 模拟死锁现象:
DeadLock A = new DeadLock(true);
DeadLock B = new DeadLock(false);
A.setName("A线程");
A.start();
B.setName("B线程");
B.start();
/**
* 运行结果:
* A线程进入1
* B线程进入3
* 可以发现他卡住了,所以在实现线程同步时,一定要避免死锁的产生
*/
}
}
class DeadLock extends Thread{
static Object o1= new Object();// 保证多个线程共享一个对象
static Object o2= new Object();
boolean flag;
public DeadLock(boolean flag) {// 构造器
this.flag = flag;
}
@Override
public void run() {
// 下面业务逻辑的分析
// 1. 如果flag 为真,线程A就会先得到/持有o1 对象锁,然后尝试去获取 o2 对象锁
// 如果线程A得不到o2 对象锁,就会Blocked (阻塞状态)
// 2. 如果flag 为假,线程B就会先得到/持有o2 对象锁,然后尝试去获取 o1 对象锁
// 如果线程B得不到o1 对象锁,也会Blocked (阻塞状态)
if (flag){
synchronized (o1){// 对象互斥锁,下面就是同步代码
System.out.println(Thread.currentThread().getName()+"进入1");
synchronized (o2){// 这里获得li对象的监视权
System.out.println(Thread.currentThread().getName()+"进入2");
}
}
}
else {
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"进入3");
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"进入4");
}
}
}
}
}
通过对死锁的认识,我们在编程时应该要避免死锁的产生。
释放锁:
释放锁
●下面操作会释放锁
1.当前线程的同步方法、同步代码块执行结束
2.当前线程在同步代码块、同步方法中遇到 break, return
3.当前线程在同步代码块、同步方法中出现了未处理的 ErrorException,导致异常结束
4.当前线程在同步代码块、同步方法中执行了线程对象的wait0方法,当前线程暂停,并释
放锁●下面操作不会释放锁
1.线程执行同步代码块或同步方法时,程序调用 Thread. sleep、 Thread.yield方
法暂停当前线程的执行不会释放锁
案例:上厕所,太困了,在坑位上眯了一会
2.线程执行同步代码块时,其他线程调用了该线程的 suspend方法将该线程挂起,
该线程不会释放锁。
提示:应尽量避免使用 suspend和 resume来控制线程,方法不再推荐使用
通过以上对多线程的学习,让我们来做两道题吧:
多线程练习:
1. 在main 中启动两个线程,teat01 线程随机输出1~100 的整数,使用线程test02 控制线程test01是否结束, 直到用户输入Q结束test01 线程,随之结束test02 线程
package xiancheng.duoxiancheng;
import java.util.Scanner;
public class Synchronized_Test {
public static void main(String[] args) {
// 在main 中启动两个线程,teat01 线程随机输出1~100 的整数,使用线程test02 控制线程test01是否结束,
// 直到用户输入Q结束test01 线程,随之结束test02 线程
Test01 test01 = new Test01();
Test02 test02 = new Test02(test01);
test01.start();
test02.start();
}
}
class Test01 extends Thread{
private boolean loop=true;
@Override
public void run() {
// 输出1~100 的整数
while (loop){
System.out.println((int)(Math.random()*100+1));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程test01退出");
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
class Test02 extends Thread{
private Test01 test01;
private Scanner s = new Scanner(System.in);
public Test02(Test01 test01){
this.test01=test01;
}
@Override
public void run() {
while (true){
// 接收到用户输入
System.out.println("请输入您的指令");
char key = s.next().toUpperCase().charAt(0);
if (key=='Q'){
// 已通知的方式结束线程test01
test01.setLoop(false);
System.out.println("线程test02 结束");
break;
}
}
}
}
2. 要求: 创建两个账户,两人共同使用一张银行卡取款,起始金额为10000; 余额不足时不能再取款,更不能出现透支现象
package xiancheng.duoxiancheng;
import java.util.Scanner;
public class Synchronized_Test_ {
public static void main(String[] args) {
// 要求: 创建两个账户,两人共同使用一张银行卡取款,起始金额为10000; 余额不足时不能再取款,更不能出现透支现象
/**
* 分析:
* 该题目和之前的火车票问题一样,我为大家演示继承Thread 类的方式实现
*/
Boank o1 = new Boank();// 人物一:李明
o1.setName("李明");
Boank o2 = new Boank();// 人物二:王刚
o2.setName("王刚");
o1.start();
o2.start();
}
}
class Boank extends Thread{
private static double count = 10000;
private static boolean loop=true;
@Override
public void run() {
while (loop){
sell();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized static void sell(){
if (count<=0){
System.out.println("银行卡余额不足!!!");
loop=false;
}
else {
System.out.println("请输入取款金额:");
double s = new Scanner(System.in).nextDouble();
while (s > count){
System.out.println("余额不足 "+s+"\n请重新输入取款金额!!!");
s = new Scanner(System.in).nextDouble();
}
// 如果还有余额,休眠50毫秒 模拟现实
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 取出金额:" +s+
"剩余金额:" + (count-=s));
}
}
}
运行示例:
那到这里就结束啦,有问题欢迎大家评论哦,谢谢!!!