黑马程序员————多线程


------Java培训Android培训iOS培训.Net培训、期待与您交流!-------


一、进程和线程


进程定义:是一个正在执行的程序。

    每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。

线程的定义:就是进程中的一个独立的控制单元(执行路径),

线程程控制着进程的运行。

 

二、小知识


1.Java VM启动时候都会有一个进程java.exe

该进程中至少有一个线程负责java程序的运行,而且这个线程运行的代码存于main方法中。

该线程称为主线程。

 

2.其实java启动不止一个线程,还有负责垃圾回收机制的线程。

 

3.其实应用程序的执行都是CPU在作者快速的切换,这个切换时随机的

 

三、多线程的好处与弊端


好处:解决了多部分同时运行的问题

弊端:线程太多回收效率的降低

  

四、线程的创建第一种方法


继承Thread类

步骤:

1)定义一个类来继承Thread

2)覆盖Thread中的run方法

3)直接创建Thread类的子类对象的线程。

4)调用start方法开启线程并调用线程的任务的run方法执行。


补充:

1)为什么要覆盖run方法呢?

要弄清楚这个问题,首先要明白

创建线程的目的是为了开启一条执行路径去执行指定的代码和其他代码实现同时的运行,而运行的指定代码就是这个执行路径的任务。

Jvm创建的主线程的任务都定义在主函数中。

而自定义的任务在哪呢?

Thread类用于描述线程,线程是需要有任务的,所以Thread类也对任务有描述,这个任务就是通过Thread类中的run方法来体现,也就是说,run方法就是封装自定义线程运行任务的函数,run方法中定义的就是线程需要运行的任务的代码。

综上就是覆盖run方法的原因。

2)开启线程就为了运行指定的代码,只有继承Thread类,并复写run方法,将运行的代码定义在run方法中即可。

3)任务只能用run方法体现

如果有任务,封装在run方法中,如果任务在其他函数中,复写run方法只需调用任务函数即可,子类继承线程,子类就是线程,创建子类的对象。如果直接调用run方法是无法开启线程的,因为这和调用普通的run方法一样,所以必须调用start()开启线程自动调用run()方法。


例子:

class Demo extends Thread{
private String name;
Demo(String name){
this.name=name;
}
public void run(){
for(int i = 0; i<10; i++){
System.out.println(name+"........"+i);
}
}
}
package thread;
 
public class ThreadDemo {
/**
 * @param args
 */
public static void main(String[] args) {
Thread t1 = new Demo("xiaoming");
Thread t2 = new Demo("lisi");
t1.start();
t2.start();
}
}

五、多线程创建的第二种方法


步骤:

1.定义类实现Runnable接口

2.覆盖接口中的run方法,将线程的任务代码封装到run方法中

3.通过Thread类创建线程的对象,并将Runnable接口子类的对象作为Thread类构造函数参数进行传递。

原因是:因为线程的任务都封装在Runnable接口子类对象的run方法中,所以要在线程对象创建时就必须明确需要运行的任务

4.调用线程对象的start()方法开启线程


实现Runnable的好处:

1.将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成了对象。

2.避免了java单独继承的局限性,所以创建线程的第二种方法较为常用。


例子

需求:四个窗口售100张票

package thread;
public class ThreadDemo2 {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket implements Runnable {
private int num = 100;
public void run() {
while (true) {
synchronized (this) {/为什么用这个呢?可以看下面的内容
if (num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "...sale.." + num--);
}
}
}
}
}

六、线程状态的图解



 




















被创建:start()

运行:具备执行资格,同时具备执行权;

冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;

临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;

消亡:stop()

 

七、线程的安全问题及解决办法

 

拿售票的例子来说:

package thread;
public class ThreadDemo2 {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket implements Runnable {
private int num = 100;
public void run() {
while (true) {
if (num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "...sale.." + num--);
}
}
}
}

运行结果:


 

 

 







原因:

1.多个线程在操作共享的数据(num=100),一条语句不会出问题,2条或以上就会出现问题。

2.操作共享的数据的线程代码有多条,当一个线程在执行共享的数据多条代码的过程中,其他线程参与了运算,就会导致线程的安全问题的产生。

 

涉及到两个因素:
1,多个线程在操作共享数据。
2,有多条语句对共享数据进行运算。
原因:这多条语句,在某一个时刻被一个线程执行时,还没有执行完,就被其他线程执行了。


解决思路:

就是讲多余操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程是不可以参与运算的,必须要当当前线程把这些代码执行完毕后,其他线程才可以参与运算。

这时就用

1.同步代码块来解决这个问题:

格式:

synchronized (同步的对象){

需要同步的代码

}


例如:

class Ticket implements Runnable// extends Thread
{
private int num = 100;
 
Object obj = new Object();
 
public void run() {
while (true) {
synchronized (obj) {
if (num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()
+ ".....sale...." + num--);
}
}
}
}
}
 

同步的对象(锁)的确定:

可以使用Object obj = new Object();中的obj

也可以使用this


2.通同步函数来解决


同步函数和同步代码块的区别:

同步函数的锁是固定的this,而同步代码块的锁是任意的,建议使用同步代码块。

 

例如:

class Ticket implements Runnable {
private int tick = 100;
boolean flag = true;
public void run() {
if (flag) {
while (true) {
show();
}
}
}
public synchronized void show(){//同步函数的锁是 this
if (tick > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName()
+ "....show.... : " + tick--);
}
}
}

当同步函数被static修饰时,这时的同步用的是哪个锁呢

静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。

所以静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。

这个对象就是类名.class


例如:

class Single {
private static Single s = null;
private Single() {
}
public static Single getInstance() {
if (s == null) {
synchronized (Single.class) {
if (s == null)
s = new Single();
}
}
return s;
}
}

同步的好处和弊端:

好处:

解决了线程的安全问题

弊端:

相对降低了效率,同步外的线程都会判断同步锁

 

补充:

同步的前提:必须有多个线程并使用同一个锁,不能将

Object obj = new Object();放在run方法中。

如果加了同步还不能解决安全问题时,一定要看同步的锁是不是同一把锁

 

八、线程间的通信

 

线程间通讯:

其实就是多个线程在操作同一个资源,但是操作的动作不同。

1:将资源封装成对象。

2:将线程执行的任务(任务其实就是run方法。)也封装成对象。

 

等待唤醒机制:

涉及的方法:

wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。

notify:唤醒线程池中某一个等待线程。

notifyAll:唤醒的是线程池中的所有线程

 

例子:单生产单消费

package thread1;
class Resource { // 资源
private int count;
private String name;
boolean flag = false;// 定义一个标记判断有没有烤鸭
public synchronized void set(String name) {// 生产烤鸭
while (flag)
try {
this.wait();// 等待消费者消费
} catch (Exception e) {
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()
+ ".....生产者....." + this.name);
flag = true;
this.notify();
}
public synchronized void out() {// 消费烤鸭
while (!flag)
try {
this.wait();// 等待生产者生产
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + "...消费者...."
+ this.name);
flag = false;
this.notify();
}
}
class Producer implements Runnable {
private Resource r;
 
public Producer(Resource r) {
this.r = r;
}
@Override
public  void run() {
while (true) {
r.set("烤鸭");
}
}
}
class Consumer implements Runnable {
private Resource r;
public Consumer(Resource r) {
this.r = r;
}
@Override
public  void run() {
while (true)
r.out();
}
}
public class ThreadDemo6 {
public static void main(String[] args) {
Resource r = new Resource();
Producer p = new Producer(r);
Consumer c = new Consumer(r);
Thread t0 = new Thread(p);
Thread t1 = new Thread(c);
t0.start();
t1.start();
}
}

例子2:多生产多消费:

package thread;
class Resource {  //资源
private int count;
private String name;
boolean flag = false;//定义一个标记判断有没有烤鸭
public synchronized void set(String name) {//生产烤鸭
while (flag)
try {
this.wait();//等待消费者消费
} catch (Exception e) {
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName() + ".....生产者....."
+ this.name);
flag = true;
notifyAll();
}
public synchronized void out() {//消费烤鸭
while (!flag)
try {
this.wait();//等待生产者生产
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + "...消费者...."
+ this.name);
flag = false;
notifyAll();
}
}
class Producer implements Runnable {
private Resource r;
public Producer(Resource r) {
this.r = r;
}
public void run() {
while (true) {
while (true)
r.set("烤鸭");
}
}
 
}
class Consumer implements Runnable {
private Resource r;
 
public Consumer(Resource r) {
this.r = r;
}
public void run() {
while (true)
r.out();
}
 
}
public class ThreadDemo4 {
 
public static void main(String[] args) {
Resource r = new Resource();
Producer p = new Producer(r);
Producer p1 = new Producer(r);
Consumer c = new Consumer(r);
Consumer c1 = new Consumer(r);
Thread t0 = new Thread(p);
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(c);
t0.start();
t1.start();
t2.start();
t3.start();
 
}
 
}

九、线程间通信之多生产多消费在JDK1.5后的处理

 

替换了synchronized,JDK1.5以后将同步和和锁封装成了对象,并将操作锁的隐式定义到了该对象中,将隐式的动作变成了显示动作。

 

Lock接口:出现替换了同步代码块或者同步函数,将同步的隐式锁操作变成显示锁的操作。同时更为灵活,可以一个锁加上多组监视器。

 

Condition接口:出现替换了Object中的wait,notify,notifyAll的方法


以前是:

1.关于同步

void show(){
synchronized (obj) {
code....
}
}


2.关于锁的方法

class Object{
wait();
notify();
notifyAll();
}
class Demo extends Object{  }
Demo  d = new Demo();
synchronized(d){  
d.wait();
}

这个锁即监视生产者也监视消费者。如果生产者唤醒消费者,消费者唤醒生产者,只能用两把锁,是不可行的。


现在是:

1.关于同步

Lock lock = new ReentrantLock();//自定义锁
void show(){try {
lock.lock();   	//获取锁
code...throw Exception(); 
}
finally {
lock.unlock;//一定要释放锁
}
}

2.关于锁的方法

interface Condition{
await();
single();
singleAll();
}
//一个锁有多个监视器
Lock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();

用两个监视器可以指定唤醒指定的线程。即生产者唤醒消费者,消费者唤醒生产者。用同一把锁监视两个线程。


生产者和消费者的代码改写:

package thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Resource { // 资源
private int count;
private String name;
boolean flag = false;// 定义一个标记判断有没有烤鸭
Lock lock = new ReentrantLock();
Condition pro = lock.newCondition();
Condition con = lock.newCondition();
public void set(String name) {// 生产烤鸭
lock.lock();
try {
while (flag)
try {
pro.await();// 等待消费者消费
} catch (Exception e) {
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()
+ ".....生产者....." + this.name);
flag = true;
con.signal();
} finally {
lock.unlock();
}
}
public void out() {// 消费烤鸭
lock.lock();
try {
while (!flag)
try {
con.await();// 等待生产者生产
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + "...消费者...."
+ this.name);
flag = false;
 
}
finally {
pro.signal();
}
}
}
class Producer implements Runnable {
private Resource r;
public Producer(Resource r) {
this.r = r;
}
public void run() {
while (true) {
while (true)
r.set("烤鸭");
}
}
}
class Consumer implements Runnable {
private Resource r;
 
public Consumer(Resource r) {
this.r = r;
}
public void run() {
while (true)
r.out();
}
}
public class ThreadDemo4 {
public static void main(String[] args) {
Resource r = new Resource();
Producer p = new Producer(r);
Producer p1 = new Producer(r);
Consumer c = new Consumer(r);
Consumer c1 = new Consumer(r);
Thread t0 = new Thread(p);
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(c);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
 

十、wait和sleep的区别

 

1.wait可以指定时间也可以不指定时间

sleep必须指定时间

2.在同步时,cpu的执行权和锁的处理不同

wait:释放执行权,释放锁

sleep:释放执行权,不释放锁

 

例子:t3唤醒了t0 t1 t2而且同步里面有共享的代码,为什么执行过程中没有出现问题?

void show(){
synchronized (this) {
wait();//t0  t1  t2
code.....
}
}
void method(){
synchronized (this) {
notifyAll(); // t3
}
}

原因:

t3唤醒了t0 t1 t2,他们的确是都"活了",但是他们只是具备执行的资格,不具备执行的权,只有当t3出了同步,释放锁时,t0 t1 t2他们当中才会有一个执行,因为在同步中想要执行代码,必须要持有锁才可以。

 

十一、停止线程

 

1.stop方法

2.run方法结束


怎么控制线程的任务结束呢?

任务中会有循环结构,只要控制循环就可以结束任务,控制循环通常就用定义标识表完成


线程能停下来

例子1:

package thread;
class StopThread implements Runnable {
private boolean flag = true;
public void run() {
while (flag)
System.out.println(Thread.currentThread().getName() + "..run...");
}
public void setFlag() {
flag = false;
}
}
public class ThreadDemo5 {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 1;
for (;;) {
if (++num == 50) {
st.setFlag();
break;
}
System.out.println("main....." + num);
}
System.out.println("over");
}
}

线程停不下来

因为线程处于冻结状态,无法读取标记,这时可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格,但是强制动作会发生InterruptException,记得处理。


例子2:

package thread;
public class ThreadDemo5 {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 1;
for (;;) {
if (++num == 50) {
t1.interrupt();
t2.interrupt();
break;
}
System.out.println("main....." + num);
}
System.out.println("over");
}
}
class StopThread implements Runnable {
private boolean flag = true;
public synchronized void run() {
while (flag) {
try {
this.wait();
} catch (InterruptedException e) {
System.out
.println(Thread.currentThread().getName() + "..." + e);
flag = false;
}
System.out.println(Thread.currentThread().getName() + "..run...");
}
}
public void setFlag() {
flag = false;
}
}
 

十二、守护线程

 

可以理解为后台线程

特点:

和前台线程都是正常开启,但是结束不同,前台必须手动结束;后台线程如果所有的前台线程都结束了,无论后台的线程处于何种状态,它都会结束。

例子:

package thread;
public class ThreadDemo5 {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.setDaemon(true);//守护线程的开启
t2.start();
int num = 1;
for (;;) {
if (++num == 50) {
t1.interrupt();//仅停止t1线程
break;
}
System.out.println("main....." + num);
}
System.out.println("over");
}
}
class StopThread implements Runnable {
private boolean flag = true;
public synchronized void run() {
while (flag) {
try {
this.wait();
} catch (InterruptedException e) {
System.out
.println(Thread.currentThread().getName() + "..." + e);
flag = false;
}
System.out.println(Thread.currentThread().getName() + "..run...");
}
}
public void setFlag() {
flag = false;
}
}

十三、线程的其他的方法

 

1.join方法

package thread;
class Demo1 implements Runnable{
public void run() {
System.out.println(Thread.currentThread().getName()+"........."+i);
}
}
}
public class JoinDemo {
public static void main(String[] args) throws Exception {
Demo1 d = new Demo1();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
t1.join();//t1线程要申请加入进来运行,主线程释放了执行权,是处于冻结状
态的,必须等待t1执行完才能执行,不管t2执没执行完。
for(int i = 0;i<=50;i++){
System.out.println(Thread.currentThread().getName()+"....."+i);
}
}
}

2.setPriority


线程的优先级:

MAX_PRIORITY  10

MIN_PRIORITY  1

NORM_PRIORITY  5 ( 默认 )


3.Yield


暂停线程,释放执行权,别的线程就有可能获得执行权


------Java培训Android培训iOS培训.Net培训、期待与您交流!-------

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于MySQL+MFC设计实现的小型点餐系统C++源码(含设计报告).zip基于MySQL+MFC设计实现的小型点餐系统C++源码(含设计报告).zip基于MySQL+MFC设计实现的小型点餐系统C++源码(含设计报告).zip基于MySQL+MFC设计实现的小型点餐系统C++源码(含设计报告).zip基于MySQL+MFC设计实现的小型点餐系统C++源码(含设计报告).zip基于MySQL+MFC设计实现的小型点餐系统C++源码(含设计报告).zip 基于MySQL+MFC设计实现的小型点餐系统C++源码(含设计报告).zip 基于MySQL+MFC设计实现的小型点餐系统C++源码(含设计报告).zip

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值