Java 线程与多线程
Java是一门支持多线程的编程语言!
什么是进程?
计算机中内存、处理器、IO等资源操作都要为进程进行服务。
一个进程上可以创建多个线程,线程比进程更快的处理单元,而且所占用的资源也小,多线程的应用也是性能最高的。
Java的多线程实现:(三种方式)
在Java中实现多线程有两种途径:
1、继承Thread类
2、实现Runnable接口
3、实现Callable接口
继承Thread类:
class MyThread extends Thread { //继承Thread 即 多线程类【线程操作主类】 }
ps:在Java中,任何一个类继承了Thread类,都视为该类为多线程类。
在Java程序中都有一个“起点”即开始的地方;那么多线程类也有一个“起点”——run()方法,也就是说在多线程的每个主体类中都必须要覆写Thread类中所提供的run()方法
public void run() ;
run()方法没有提供参数和返回值,那么也就表示了线程一旦开始就要一直执行,不能够返回内容。
import sun.security.mscapi.KeyStore.MY; class MyThread extends Thread { //继承Thread 即 多线程类
private String name ;
public MyThread(String name) {
this.name = name ;
}
@Override
public void run () {
for (int x = 0 ; x < 200 ; x ++) {
System.out.println(this.name + "--->" + x);
} //线程类执行的功能就是循环的输出操作
}
}
public class TestDemo { public static void main(String[] args) {
MyThread mt1 = new MyThread("A >>>") ;
MyThread mt2 = new MyThread("B >>>") ;
MyThread mt3 = new MyThread("C >>>") ; mt1.run(); // 未真正意义的启动多线程
mt2.run();
mt3.run();
} }
多线程的启动方法:Start()方法
public void start() ;
// 启动start()方法
public class TestDemo { public static void main(String[] args) {
MyThread mt1 = new MyThread("A >>>") ;
MyThread mt2 = new MyThread("B >>>") ;
MyThread mt3 = new MyThread("C >>>") ; mt1.start();
mt2.start();
mt3.start();
} }
真正的多线程,会交替的抢占资源执行程序。
上例程序的功能就是多线程输出,而a,b,c三个抢占资源执行自己的功能
为什么多线程的启用调用的不是run()而是必须调用start() ???
——————————————————————————————————
实现Runnable接口:(为了规避单继承局限问题)
@FunctionalInterface // 函数式接口
public interface Runnable {
public void run() ;
}
【函数式接口的特点:一个接口只有一个方法】
import sun.security.mscapi.KeyStore.MY; class MyThread implements Runnable { //实现 Runnable 即 多线程继承
private String name ;
public MyThread(String name) {
this.name = name ;
}
@Override
public void run () {
for (int x = 0 ; x < 200 ; x ++) {
System.out.println(this.name + "--->" + x);
} //线程类执行的功能就是循环的输出操作
}
}
public class TestDemo { public static void main(String[] args) {
MyThread mt1 = new MyThread("A >>>") ;
MyThread mt2 = new MyThread("B >>>") ;
MyThread mt3 = new MyThread("C >>>") ;
// 由于Runnable接口没有定义start方法,而一定的规定要求,必须通过Thread。start方法来启动继承;
// Thread类中有 【Thread(Runnable target)】构造方法,可以接收Runnable接口的参数
// 于是我们可以实例化Threda类,将Runnable对象交给Thread类处理并start()方法启动多线程
new Thread(mt1).start();
new Thread(mt2).start();
new Thread(mt3).start();
} }
#由此介绍了Thread和Runnable两种实现多线程的方法!
两者之间的实现方式:
使用Runnable接口与Thread类相比之下,解决了Thrad类单继承局限的问题;
数据共享的不同
class MyThread extends Thread {
private int tick = 10 ;
@Override
public void run() {
for (int x = 0 ; x < 100 ; x ++) {
if (this.tick > 0) {
System.out.println(this.tick --);
}
}
}
}
public class TestDemo { public static void main(String[] args) {
MyThread mt1 = new MyThread() ;
MyThread mt2 = new MyThread() ;
MyThread mt3 = new MyThread() ;
// 由于mythread继承了thread类,所以类中继承了start()方法
mt1.start();
mt2.start();
mt3.start();
} }
上例代码是通过继承Thread类实现的多线程操作,本例的目的在于表达,我们只有10元;例子中的15~17行声明了三个不同的对象,产生了三个不同内存,相互的数据是独立的;如果按照19~21行代码start()方法启动线程,结果就是10出现三次,因为三个对象的数据是独立的。
10
9
8
7
6
5
4
3
2
1
10
9
8
7
6
5
4
3
2
1
10
9
8
7
6
5
4
3
2
1
code 运行结果
class MyThread implements Runnable {
private int tick = 10 ;
@Override
public void run() {
for (int x = 0 ; x < 100 ; x ++) {
if (this.tick > 0) {
System.out.println(this.tick --);
}
}
}
}
public class TestDemo { public static void main(String[] args) {
// 在Runnable多线程下,只需要实例化一个MyThread对象
MyThread mt = new MyThread() ;
// 实例化Threda类,将Runnable对象交给Thread类处理并start()方法启动多线程
// 通过实例化Thread使用start()方法来实现三个(多)线程,且造作的数据对象都是同一个
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
} }
19~21行代码显示,我们将唯一的MyThread对象 “mt” 作为Runnable对象传给Thread的构造方法。
10
7
6
5
4
8
2
1
9
3
code 运行结果
两个程序例子,都是实现三个线程的启动;不同的地方在于,Thread的三个线程使用的是三个不同的实例对象,而Runnable的三个线程均是通过实例化Thread类调用Start()方法对同一实例对象进行三个线程的操作。Runnable更好的实现了同一数据共享(当然Thread也可以,只是没有Runnable简单)
# 两者的区别
Thread类是Runnable接口的一个子类,使用Runnable接口实现多线程可以避免单继承的局限性
Runnable接口实现的多线程可以比Thrad类实现的多线程更加清楚的描述
Callable接口:
Runnable接口实现的多线程不能返回操作结果;所以提供了一个新的多线程接口——Callable接口【java.util.concurrent 包】
@FunctionalInterface
public interface Callable<V> { // 函数式接口
public V call() ;
}
call()方法在 执行主要功能后,可以返回结果,而返回结果的类型有Calable接口泛型来决定。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask; class MyThread implements Callable<String> { //实现Callable接口,并定义泛型为String
private int tick = 10 ;
public String run() {
for (int x = 0 ; x < 100 ; x ++) {
if (this.tick > 0) {
System.out.println(this.tick --);
}
}
return "stop" ;
}
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
return null;
}
}
public class TestDemo { public static void main(String[] args) throws Exception {
/*
* Thread类中并没有接收Callabel对象的构造方法,所以无法通过start()来启动多线程
* 但是Java提供java.util.concurrent。FutureTask<V> 类 ,
* 在FutureTask类的定义结构如下:
* public class FutureTask<V> extends Object implements Future<V> , Runnable
* 类中有如下的构造方法:
* public FutureTask(Callable<V> callable)
* FutureTask类接收Callable接口对象。目的就是:取得call()方法的返回结果
*/
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
FutureTask<String> task1 = new FutureTask<String>(mt1); // 目的是为了接收call返回值
FutureTask<String> task2 = new FutureTask<String>(mt2);
// FutureTask是Runnable接口子类,所以可以使用Thread类的构造来接收task对象
new Thread(task1).start();
new Thread(task2).start();
// 多线程执行完毕后,依靠FutureTask的父接口 Future中的get()方法完成。
System.out.println("A--->" + task1.get());
System.out.println("B--->" + task2.get()); } }
多线程的常用操作方法:
1、线程的命名与取得:
所有线程的执行,每一次都是不同的结果;如果要想区分线程就要依靠线程的名字;对于线程的命名,一般会在启动之前定义。
构造方法: public Thread(Runnable target , String name) ;
设置命名: public final void setName(String name) ; // final 方法不可被覆写
取得命名: public final String getName() ;
上述的三个方法,是Thread类中的方法,主要实现的就是线程的命名和取得操作:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask; class MyThread implements Runnable {
/*
* 对于线程的命名,setName() getName() 等方法都在Thread类中
* 如果在实现Runnable接口的线程中,这个类就不会继承Thread类,无法实现Thread的命名操作方法
* 如果要取得名字和命名,能够取得的只有当前本方法的线程名【public static Thread currentThread()】
* currentThread()方法取得是当前线程的Thread对象,所以取得Thread对象后,就可以直接调用Thread中的方法,
* 并且,currentThread()是定义为 Static属性,不需要实例,可以直接类方法调用。
*/
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
// 取得当前run线程对象,并调用getName()
}
}
public class TestDemo { public static void main(String[] args) throws Exception {
MyThread mt = new MyThread() ;
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
} }
上例代码并没有设置线程名字,只是简单实现了getName()方法和CurrentThread()方法;当然如果我们没有给线程命名而调用方法的话!系统给自动的给线程赋名(会给线程编号命名):【运行结果】
Thread-1
Thread-2
Thread-0
设置线程名:(实例化Thread启动Start前命名)
public class TestDemo { public static void main(String[] args) throws Exception {
MyThread mt = new MyThread() ;
new Thread(mt,"线程 = A").start();
new Thread(mt,"线程 = B").start();
new Thread(mt,"线程 = C").start();
} }
运行结果:
线程 = A
线程 = C
线程 = B
#mian()主线程的存在
public class TestDemo { public static void main(String[] args) throws Exception {
MyThread mt = new MyThread() ;
new Thread(mt,"线程 ").start(); mt.run(); // 直接调用run()方法
} }
运行结果:
main
线程
因为run()中输出的是当前线程对象的名(取得当前对象,调用getName方法输出线程对象名);综合分析得知:main主方法也是一个线程,【mian线程】那么所有在主方法上创建的线程都可以表示为子线程;而我们都是在主线程下创建子线程。
每当使用Java命令去解释一个程序类的时候,对于操作系统而言,都相当于启动了一个进程上的一个子线程。
JVM启动:
main线程:程序的主要执行,以及启动子线程
GC线程:负责垃圾收集
2、休眠
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask; class MyThread implements Runnable {
@Override
public void run() { for ( int x = 0 ; x < 10000 ; x ++ ) {
try { // 执行sleep休眠,休眠1秒(sleep是以纳秒为单位)
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ">>>" + x);
}
}
}
public class TestDemo { public static void main(String[] args) throws Exception {
MyThread mt = new MyThread() ;
new Thread(mt,"线程 A").start();
} }
3、线程的优先级
优先级越高,越有可能先执行;Thread类中设置了两个方法:
改变线程优先级: public final void setPriority(int newPriority) ;
返回线程优先级: public final void getPriority() ;
设置和取得优先级均使用int数据;且为final属性,不可改变。
Thread类中也设置了三个常量字段值,分别约定了线程优先级的最高、中、最低三个级别
线程的最高优先级: public static final int MAX_PRIORITY
线程的中等优先级: public static final int NORM_PRIORITY
线程的最低优先级: public static final int MIN_PRIORITY
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask; class MyThread implements Runnable {
@Override
public void run() { for ( int x = 0 ; x < 20 ; x ++ ) {
try { // 执行sleep休眠,休眠1秒(sleep是以纳秒为单位)
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ">>>" + x);
}
}
}
public class TestDemo { public static void main(String[] args) throws Exception {
MyThread mt = new MyThread() ;
Thread t1 = new Thread(mt,"线程 A");
Thread t2 = new Thread(mt,"线程B");
Thread t3 = new Thread(mt,"线程C"); t1.start();
t2.start();
t3.start();
} }
上例代码中,运行执行的结果中,优先级没有设置,且顺序再每次输出都是不同的。
public class TestDemo { public static void main(String[] args) throws Exception {
MyThread mt = new MyThread() ;
Thread t1 = new Thread(mt,"线程 A");
Thread t2 = new Thread(mt,"线程B");
Thread t3 = new Thread(mt,"线程C");
t1.setPriority(Thread.MAX_PRIORITY); // 设置t1为最高优先级
t1.start();
t2.start();
t3.start();
} }
本段主方法中的第9行,设置t1为最高优先级,于是t1越是有可能的优先执行(不是绝对)
主方法的优先级:
主方法也是一个线程,但是主方法的优先级又是多少呢?
public class TestDemo { public static void main(String[] args) throws Exception {
System.out.println(Thread.currentThread().getPriority());
} }
显示的结果为:【 5 】 所以主方法(主线程)的属于中等优先级
# 总结:
1、Thread.currentThread:可以取得当前的线程类对象
2、Thread.sleep():实现休眠功能
3、优先级越高,越有可能先执行
线程的同步与死锁
线程的同步:
所谓的同步指的是多个线程访问同一资源时的问题;即多个线程对象操作同一个对象资源。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask; class MyThread implements Runnable {
private int ticket = 5 ; // 一共有5个点 @Override
public void run() { for ( int x = 0 ; x < 20; x ++ ) {
if (this.ticket > 0) {
System.out.println(Thread.currentThread().getName() + ticket--);
}
}
}
}
public class TestDemo { public static void main(String[] args) throws Exception {
MyThread mt = new MyThread();
new Thread(mt,"A >>>").start();
new Thread(mt,"B >>>").start();
new Thread(mt,"C >>>").start();
} }
【运行结果】
A >>>5
A >>>2
A >>>1
C >>>3
B >>>4
此时,上例没有出现任何问题!
但——在代码run()的for中加入延迟【Thread.sleep();】就会出现问题:
class MyThread implements Runnable {
private int ticket = 5 ; // 一共有5个点 @Override
public void run() { for ( int x = 0 ; x < 20; x ++ ) {
if (this.ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ticket--);
}
}
}
}
public class TestDemo { public static void main(String[] args) throws Exception {
MyThread mt = new MyThread();
new Thread(mt,"A >>>").start();
new Thread(mt,"B >>>").start();
new Thread(mt,"C >>>").start();
}
}
【运行结果】
B >>>3
A >>>4
C >>>5
B >>>2
A >>>1
C >>>0
B >>>-1
可以明显的发现,结果中出现了“负数”(意外值);这就是“不同步”的状况【异步操作】。
上例代码的想法是:从同一个资源取得当前的剩余的点数,但是由于延迟的存在,后续的线程不会等待之前的线程,会直接的进入,导致剩余值没有得到及时的刷新。
同步的操作
所谓同步就是指多个线程操作同一时间只能有一个线程进入同一个空间运行,其他线程要等待此线程完成之后才可以继续执行。而之前的不同步【或称为异步操作】,则是多个线程可以同一时间进入同一个空间运行。
Java中实现线程得同步则使用:synchronized 关键字。使用方法:1、同步代码块;2、[线程]同步方法;
Java 四种代码块:
普通代码块、构造块、静态块、同步块
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask; class MyThread implements Runnable {
private int ticket = 5 ; // 一共有5个点 @Override
public void run() { for ( int x = 0 ; x < 20; x ++ ) {
synchronized(this) { // 【同步块】;当前操作每次只允许一个对象进入
if (this.ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ticket--);
}
}
}
}
}
public class TestDemo { public static void main(String[] args) throws Exception {
MyThread mt = new MyThread();
new Thread(mt,"A >>>").start();
new Thread(mt,"B >>>").start();
new Thread(mt,"C >>>").start();
}
}
上例代码实现得则是“同步块”,利用synchronized关键字使得12~31行代码被关键字锁住,每一次执行只可以进入一个对象,实现同步。
所有(多个)线程,只有当里面得线程结束了,自己才可以进入同步块【一次只可以进入一个对象】
但是有人翻译同步代码块比较“粗糙”,所以还有另外得【同步方法】:
package hello; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask; class MyThread implements Runnable {
private int ticket = 5 ; // 一共有5个点 @Override
public void run() { for ( int x = 0 ; x < 20; x ++ ) {
this.sale(); // 调用同步方法
}
}
public synchronized void sale() { // 同步方法
if (this.ticket > 0 ) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ticket--);
}
}
}
public class TestDemo { public static void main(String[] args) throws Exception {
MyThread mt = new MyThread();
new Thread(mt,"A >>>").start();
new Thread(mt,"B >>>").start();
new Thread(mt,"C >>>").start();
}
}
上例代码则是放弃了同步块,使用同步方法实现“同步”。17~26行定义了同步方法:同步方法得定义结构依旧是使用synchronized关键字;(14行)在run()方法中,使用this方法调用同步方法。
【ps:异步操作得速度高于同步操作,而同步操作时得数据安全性高于异步操作时得数据安全性;】
死锁:
所谓的同步就是一个线程对象等待另外一个线程对象执行完毕后的操作形式;线程同步过多,就有可能造成死锁。
package hello; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask; class A {
public synchronized void say(B b) {
System.out.println("把你的本给我,我给你笔,否则不给!");
b.get();
}
public synchronized void get() {
System.out.println("得了本,付出了笔,还是什么都干不了!");
}
}
class B {
public synchronized void say(A a) {
System.out.println("把你的笔给我,我就给你本,否则不给!");
a.get();
}
public synchronized void get() {
System.out.println("得到了笔,付出了本,还是什么都干不了!");
} }
public class TestDemo implements Runnable{
private static A a = new A() ;
private static B b = new B() ;
public static void main(String[] args) throws Exception {
new TestDemo() ;
}
public TestDemo() {
new Thread(this).start();
b.say(a);
}
@Override
public void run() {
a.say(b);
}
}
上例是“死锁操作”,过多的同步线程,导致的死锁。【无意义代码】
死锁是程序开发中,由于某种逻辑上的错误所造成的问题;
# 同步产生的问题:
1、多个线程访问同一空间资源是一定要处理好同步,可以使用同步代码块或同步方法解决;
2、但是过多的同步,有可能造成“死锁”
# 总结:
1、最简单的同步或异步操作,就是通过 synchronized 关键字实现。
2、死锁是一种不定、不可预的状态。
---------
Java 线程与多线程的更多相关文章
- Java线程与多线程教程
本文由 ImportNew - liken 翻译自 Journaldev. Java线程是执行某些任务的轻量级进程.Java通过Thread类提供多线程支持,应用可以创建并发执行的多个线程. 应用 ...
- Java线程和多线程(十三)——Callable,Future,FutureTask
在Java多线程之中,Callable和Future的使用时非常广泛的.在之前的文章中,我们了解了关于Java线程池基础的一些内容,知道如何提交Runnable的任务.但是,Runnable的任务是无 ...
- Java线程和多线程(十二)——线程池基础
Java 线程池管理多个工作线程,其中包含了一个队列,包含着所有等待被执行的任务.开发者可以通过使用ThreadPoolExecutor来在Java中创建线程池. 线程池是Java中多线程的一个重要概 ...
- Java线程和多线程(三)——线程安全和同步
线程安全在Java中是一个很重要的课题.Java提供的多线程环境支持使用Java线程.我们都知道多线程共享一些对象实例的话,可能会在读取和更新共享数据的事后产生数据不一致问题. 线程安全 之所以会产生 ...
- Java线程和多线程(一)——线程的基本概念
Java 线程是一个轻量级执行任务的处理单元.Java提供了Thread类来支持多线程,开发者在应用中可以创建多个线程来支持并发执行任务. 在应用中存在两种类型的线程,用户线程和守护线程.当我们启动应 ...
- Java线程和多线程(八)——Thread Dump
Java的Thread Dump就是列出JVM中所有激活状态的线程. Java Thread Dump Java Thread Dump在分析应用性能瓶颈和死锁的时候,是非常有效的. 下面将介绍多种不 ...
- java线程跟多线程
java创建线程两种方式: 1.继承Thread创建线程 /** * Created by lsf on 16/4/18. */ class NewThread extends Thread { Ne ...
- Java 线程和多线程执行过程分析
*/ .hljs { display: block; overflow-x: auto; padding: 0.5em; color: #333; background: #f8f8f8; } .hl ...
- Java线程和多线程(十五)——线程的活性
当开发者在应用中使用了并发来提升性能的同时,开发者也需要注意线程之间有可能会相互阻塞.当整个应用执行的速度比预期要慢的时候,也就是应用没有按照预期的执行时间执行完毕.在本章中,我们来需要仔细分析可能会 ...
随机推荐
- PHP安全之道学习笔记4:系统命令注入
系统命令注入 我们有时候写代码会用php脚本去调用系统函数完成业务功能,但是一些系统函数属于高危操作,一旦被webshell或者抓住漏洞则后患极大. 下面整理如下风险系统函数. exec() 函数 该 ...
- PlayJava Day020
1.异常Exception补充: ①错误(Error)指的是致命性错误,一般无法处理 ②异常以类的形式封装 程序可以处理的异常对应的类是java.lang.Exception及其子类 运行时异常对应的 ...
- 关于scrapy中如何区分是接着发起请求还是开始保存文件
一.区分 根据yield迭代器生成的对象是request对象还是item对象 二.item 1.配置tem对象 在items.py文件中设置类 class MyscrapyItem(scrapy.It ...
- pecl安装和卸载示例
比如安装MongoDB扩展 命令行下输入: [#] /usr/local/php/bin/pecl install mongodb ......嗒吧嗒吧一大堆输出后 Build process com ...
- Python3---标准库---urllib
前言 该文章主要说明Python3 标准库urllib的使用. 修改时间:20191216 修改时间:20191217 修改时间:20191218 添加urllib.parse.urlencode,u ...
- JavaWeb入门——背景知识
JavaWeb入门——背景知识 摘要:本文主要介绍了Web服务器的相关知识. 概念 什么是JavaWeb JavaWeb,是用Java技术来解决相关Web互联网领域的技术的总称.Web包括:Web服务 ...
- 使用docker-compose部署springboot项目
由于是单机测试,没有测试多主机的跨网络分布式请求. 项目是前后分离的,所以使用nginx作为前端服务器,后端是springboot则直接基于java8环境的容器上跑,cache用的redis,mysq ...
- MySql 筛选条件、聚合分组、连接查询
筛选条件 比较运算符 等于: = ( 注意!不是 == ) 不等于: != 或 <> 大于: > 大于等于: >= 小于: < 小于等于: <= IS NULL I ...
- 并发相关基础知识 - MESI - JMM
一.CPU多级缓存 CPU的频率太快了,快到主存跟不上,这样在处理器时钟周期内,CPU常常需要等待主存,浪费资源,所以cache的出现,是为了缓解CPU和内存之间速度的不匹配问题.CPU多级缓存配置( ...
- vue自定义指令笔记
https://cn.vuejs.org/v2/guide/custom-directive.html 在vue中,有时候我们会把抽象的方法封装为一个自定义指令,多个地方共用 比如:拖拽指令 < ...