上面主要是通过抽象的描述了进程,其实进程是可以很直观的看的到的,我们可以再电脑底部任务栏,右键—–>打开任务管理器,可以查看当前任务的进程:

3.2、 单线程的局限性

单线程不仅效率低下,而且存在很大的局限性,惟一的优点就是安全。所以说女孩子长得安全其实也是一种优点,噗哈哈哈…

如何体现出单线程效率低下以及它的局限性呢实只要一句代码即可,还是以上面的单线程Main线程为例:

package demo;

public class MainThreadDemo {

public static void main(String[] args) {

Person per=new Person(“常威”);

per.run();

int a=6/0; //=====================特别注意这行代码

Person Per2=new Person(“来福”);

Per2.run();

}

}

试想一下运行结果…

784e9d38f3cf02762cc82edeb572c86e.png

如果对上面的运行结果有问题,或者疑问。那没错了,你简直是个天(小)才(白)!真真的天(小)才(白),很有可能异常机制没学好,好吧我给你贴出来:【java基础之异常】死了都要try,不淋漓尽致地catch我不痛快!

言归正传,效率低下何以见得是数据少,如果是一亿条数据呢,单线程就是一个一个打印。那局限性又何以见得呢上面运行结果来看也能看出,只因为一行代码而导致下面代码不再执行。已经很明显了。

4、 创建多线程的四种方式

说是说创建多线程有四种方式,但考虑到是入门文章还是主要写入门的两种方式,剩下的两个暂时忽略。忽略的两种方法有:实现Callable接口通过FutureTask包装器来创建Thread线程、使用ExecutorService、Callable、Future实现有返回结果的线程。现在可能对于入门的童鞋来说是接收不了的,以后再去了解也不晚!

4.1、继承Thread类

Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。

Java中通过继承Thread类来创建并启动多线程的步骤如下:

定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。

创建Thread子类的实例,即创建了线程对象

调用线程对象的start()方法来启动该线程

代码如下:

测试类:

public class Demo01 {

public static void main(String[] args) {

//创建自定义线程对象

MyThread mt = new MyThread(“新的线程!”);

//开启新线程

mt.start();

//在主方法中执行for循环

for (int i = 0; i

System.out.println(“main线程!”+i);

}

}

}

自定义线程类:

public class MyThread extends Thread {

//定义指定线程名称的构造方法

public MyThread(String name) {

//调用父类的String参数的构造方法,指定线程的名称

super(name);

}

/**

* 重写run方法,完成该线程执行的逻辑

*/

@Override

public void run() {

for (int i = 0; i

System.out.println(getName()+”:正在执行!”+i);

}

}

}

Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。

4.2、实现Runnable接口

如果自己的类已经继承另一个类,就无法直接继承Thread,此时,可以实现一个Runnable接口来创建线程,显然实现Runnable接口方式创建线程的优势就很明显了。

直接撸码:

自定义一个类实现Runnable接口,并重写接口中的run()方法,并为run方法添加要执行的代码方法。

public class RunableDemo implements Runnable{

@Override

public void run() {

int a = 1;

while (a

System.out.println(Thread.currentThread().getName()+ a);//Thread.currentThread().getName()为获取当前线程的名字

a++;

}

}

}

编写Main方法

为了启动自定义类RunableDemo ,需要首先实例化一个Thread,并传入RunableDemo 实例:

public class MainThreadDemo {

public static void main(String[] args) {

RunableDemo runn=new RunableDemo();

//实例化一个Thread并传入自己的RunableDemo 实例

Thread thread=new Thread(runn);

thread.start();

int a = 1;

while (a

//Thread.currentThread().getName()为获取当前线程的名字

System.out.println(Thread.currentThread().getName()+ a);

a++;

}

}

}

运行结果:

main1

main2

main3

Thread-01

Thread-02

Thread-03

Thread-04

Thread-05

Thread-06

….

其实多运行几遍,你会方法每次运行的结果顺序都不一样,这主要是由于多线程会去抢占CPU的资源,谁抢到了谁就执行,而Main和Thread两个线程一直在争抢。

实际上,当传入一个Runnable target(目标)参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码:

public void run() {

if (target != null) {

target.run();

}

}

4.3、两种入门级创建线程的区别

采用继承Thread类方式:

(1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。

(2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。

采用实现Runnable接口方式:

(1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相

同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

(2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

小结:

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

实现Runnable接口比继承Thread类的优势:

1.适合多个相同代码的线程去处理同一个资源。

2.可以避免java中单继承的限制。

3.增加代码的健壮性,实现解耦。代码可以被多个线程共享,代码和数据独立。

4.线程池中只能放入实现Runnable或Callable类线程,不能放入继承Thread的类【线程池概念之后会慢慢涉及】

所以,如果选择哪种方式,尽量选择实现Runnable接口!

其实学到后面的线程池,你会发现上面两种创建线程的方法实际上很少使用,一般都是用线程池的方式比较多一点。使用线程池的方式也是最推荐的一种方式,另外,《阿里巴巴Java开发手册》在第一章第六节并发处理这一部分也强调到“线程资源必须通过线程池提供,不允许在应用中自行显示创建线程”。不过处于入门阶段的童鞋博主还是强烈建议一步一个脚印比较好!

5、使用匿名内部类方式创建线程

谈起匿名内部类,可能很多小白是比较陌生的,毕竟开发中使用的还是比较少,但是同样是非常重要的一个知识!于此同时我就贴出关于匿名内部类的文章程序员你真的理解匿名内部类吗果小白童鞋能看懂下面这个代码,真的你不需要看那篇文章了,你T喵的简直是个天才!

package AnonymousInner;

public class NiMingInnerClassThread {

public static void main(String[] args) {

Runnable r = new Runnable() {

@Override

public void run() {

for (int i = 0; i

System.out.println(“熊孩子:”+i);

}

}

};

new Thread(r).start();

for (int i = 0; i

System.out.println(“傻狍子:”+i);

}

}

}

小白童鞋还愣着干啥呀赶紧去补补…

6、线程安全问题

线程安全问题主要是共享资源竞争的问题,也就是在多个线程情况下,一个或多个线程同时抢占同一资源导致出现的一些不必要的问题,最典型的例子就是火车四个窗口售票问题了,这里就不再举售票例子了,已经烂大街了,这里就简单实现一个线程安全问题代码…

实现Runnable接口方式为例,主要实现过程是:实例化三个Thread,并传入同一个RunableDemo 实例作为参数,最后开启三条相同参数的线程,代码如下:

public class RunableDemo implements Runnable{

public int a = 100;//线程共享数据

@Override

public void run() {

while (a>0){

System.out.println(“线程”+Thread.currentThread().getName()+”执行到”+ a);

a–;

}

}

}

public class MainThreadDemo {

public static void main(String[] args) {

RunableDemo runn=new RunableDemo();

Thread thread1=new Thread(runn);

Thread thread2=new Thread(runn);

Thread thread3=new Thread(runn);

thread1.start();

thread2.start();

thread3.start();

}

}

运行结果:

Thread-0==100

Thread-0==99

Thread-1==100

Thread-1==97

Thread-1==96

Thread-1==95

Thread-2==98

根据结果可以看出,确实是三条线程(Thread-0、1、2)在执行,安全问题就出在线程会出现相同的结果比如上面的100就出现了两次,如果循环条件更改一下可能也会出现负数的情况。这种情况该怎么解决呢个时候就需要线程同步了!

7、解决线程安全问题:线程同步

实际上,线程安全问题的解决方法有三种:

1、同步代码块

2、同步方法

3、锁机制

7.1、 synchronized同步代码块

第一种方法:同步代码块

格式:

synchronized(锁对象) {

可能会出现线程安全问题的代码(访问共享数据的代码)

}

使用同步代码块特别注意:

1、通过代码块的锁对象,可以是任意对象

2、必须保证多个线程使用的锁对象必须是同一个

3、锁对象的作用是把同步代码快锁住,只允许一个线程在同步代码块执行

还是以上面线程安全问题为例子,使用同步代码块举例:

public class RunableDemo implements Runnable{

public int a = 100;//线程共享数据

Object object=new Object(); //事先准备好一个锁对象

@Override

public void run() {

synchronized (object){ //使用同步代码块

while (a>0){

System.out.println(“线程”+Thread.currentThread().getName()+”执行到”+ a);

a–;

}

}

}

}

Main方法没有任何改动,运行一下结果是绝对没问题的,数据都是正确的没有出现重复情况这一出,各位可以自己尝试一下!

同步代码块的原理:

使用了一个锁对象,叫同步锁,对象锁,也叫同步监视器,当开启多个线程的时候,多个线程就开始抢夺CPU的执行权,比如现在t0线程首先的到执行,就会开始执行run方法,遇到同步代码快,首先检查是否有锁对象,发现有,则获取该锁对象,执行同步代码块中的代码。之后当CUP切换线程时,比如t1得到执行,也开始执行run方法,但是遇到同步代码块检查是否有锁对象时发现没有锁对象,t1便被阻塞,等待t0执行完毕同步代码块,释放锁对象,t1才可以获取从而进入同步代码块执行。

同步中的线程,没有执行完毕是不会释放锁的,这样便实现了线程对临界区的互斥访问,保证了共享数据安全。

缺点:频繁的获取释放锁对象,降低程序效率

7.2、同步方法

使用步骤:

1、把访问了共享数据的代码抽取出来,放到一个方法中

2、在该方法上添加 synchronized 修饰符

格式:

修饰符 synchronized 返回值类型 方法名称(参数列表) {

方法体…

}

代码示例:

public class RunableDemo implements Runnable{

public int a = 100;//线程共享数据

@Override

public void run() {

while (true){

sell(); //调用下面的sell方法

}

}

//访问了共享数据的代码抽取出来,放到一个方法sell中

public synchronized void sell(){

while (a>0){

System.out.println(“线程”+Thread.currentThread().getName()+”执行到”+ a);

a–;

}

}

}

同步方法的也是一样锁住同步的代码,但是锁对象的是Runable实现类对象,也就是this,谁调用方法,就是谁。

说到同步方法,就不得不说一下静态同步方法,顾名思义,就是在同步方法上加上static,静态的同步方法,添加一个静态static修饰符,此时锁对象就不是this了,静态同步方法的锁对象是本类的class属性,class文件对象(反射)

public class RunableDemo implements Runnable{

public static int a = 100;//线程共享数据 =====此时共享数据也要加上static

@Override

public void run() {

while (true){

sell();

}

}

public static synchronized void sell(){ //注意添加了static关键字

while (a>0){

System.out.println(“线程”+Thread.currentThread().getName()+”执行到”+ a);

a–;

}

}

}

使用静态同步方法时,此时共享数据也要加上static,因为static成员才能访问static成员,如果对static关键字不是他别理解的可以补补了,放心,博主有信心让你有所收获,会让你重新认识到static的魅力:深入理解static关键字

当然静态同步方法了解即可!

7.3、Lock锁

Lock接口位于java.util.concurrent.locks.Lock它是JDK1.5之后出现的,Lock接口中的方法:

void lock(): 获取锁

void unlock(): 释放锁

Lock接口的一个实现类java.util.concurrent.locks.ReentrantLock implements Lock接口

使用方法:

1、在Runable实现类的成员变量创建一个ReentrantLock对象

2、在可能产生线程安全问题的代码前该对象调用lock方法获取锁

3、在可能产生线程安全问题的代码后该对象调用unlock方法释放锁

代码示例:

import java.util.concurrent.locks.ReentrantLock;

public class RunableDemo implements Runnable{

public static int a = 100;//线程共享数据

//1、在Runable实现类的成员变量创建一个ReentrantLock对象============

ReentrantLock reentrantLock = new ReentrantLock();

@Override

public void run() {

// 2、在可能产生线程安全问题的代码前该对象调用lock方法获取锁=======

reentrantLock.lock();

while (a>0){

System.out.println(“线程”+Thread.currentThread().getName()+”执行到”+ a);

a–;

}

// 3、在可能产生线程安全问题的代码后该对象调用unlock方法获取锁======

reentrantLock.unlock();

}

}

当然更安全的写法是,在线程安全问题代码中try…catchy

原文链接:https://blog.csdn.net/qq_44543508/article/details/103158095

站方申明:本站部分内容来自 区用户分享,若涉及侵权,请联系站方删除。

发表于 2020-02-02 18:03

阅读 ( 166 )

文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览91337 人正在系统学习中 相关资源:android实现手机摇晃摆动效果_android开发-Android代码类资源…

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年2月7日
下一篇 2021年2月7日

相关推荐