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类单继承局限的问题;
数据共享的不同
1 class MyThread extends Thread { 2 private int tick = 10 ; 3 @Override 4 public void run() { 5 for (int x = 0 ; x < 100 ; x ++) { 6 if (this.tick > 0) { 7 System.out.println(this.tick --); 8 } 9 } 10 } 11 } 12 public class TestDemo { 13 14 public static void main(String[] args) { 15 MyThread mt1 = new MyThread() ; 16 MyThread mt2 = new MyThread() ; 17 MyThread mt3 = new MyThread() ; 18 // 由于mythread继承了thread类,所以类中继承了start()方法 19 mt1.start(); 20 mt2.start(); 21 mt3.start(); 22 } 23 24 }
上例代码是通过继承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
1 class MyThread implements Runnable { 2 private int tick = 10 ; 3 @Override 4 public void run() { 5 for (int x = 0 ; x < 100 ; x ++) { 6 if (this.tick > 0) { 7 System.out.println(this.tick --); 8 } 9 } 10 } 11 } 12 public class TestDemo { 13 14 public static void main(String[] args) { 15 // 在Runnable多线程下,只需要实例化一个MyThread对象 16 MyThread mt = new MyThread() ; 17 // 实例化Threda类,将Runnable对象交给Thread类处理并start()方法启动多线程 18 // 通过实例化Thread使用start()方法来实现三个(多)线程,且造作的数据对象都是同一个 19 new Thread(mt).start(); 20 new Thread(mt).start(); 21 new Thread(mt).start(); 22 } 23 24 }
19~21行代码显示,我们将唯一的MyThread对象 “mt” 作为Runnable对象传给Thread的构造方法。
10 7 6 5 4 8 2 1 9 3
两个程序例子,都是实现三个线程的启动;不同的地方在于,Thread的三个线程使用的是三个不同的实例对象,而Runnable的三个线程均是通过实例化Thread类调用Start()方法对同一实例对象进行三个线程的操作。Runnable更好的实现了同一数据共享(当然Thread也可以,只是没有Runnable简单)
# 两者的区别
Thread类是Runnable接口的一个子类,使用Runnable接口实现多线程可以避免单继承的局限性
Runnable接口实现的多线程可以比Thrad类实现的多线程更加清楚的描述
Callable接口:
Runnable接口实现的多线程不能返回操作结果;所以提供了一个新的多线程接口——Callable接口【java.util.concurrent 包】
@FunctionalInterfacepublic interface Callable<V> { // 函数式接口 public V call() ; }
call()方法在 执行主要功能后,可以返回结果,而返回结果的类型有Calable接口泛型来决定。
1 import java.util.concurrent.Callable; 2 import java.util.concurrent.ExecutionException; 3 import java.util.concurrent.FutureTask; 4 5 class MyThread implements Callable<String> { //实现Callable接口,并定义泛型为String 6 private int tick = 10 ; 7 public String run() { 8 for (int x = 0 ; x < 100 ; x ++) { 9 if (this.tick > 0) { 10 System.out.println(this.tick --); 11 } 12 } 13 return "stop" ; 14 } 15 @Override 16 public String call() throws Exception { 17 // TODO Auto-generated method stub 18 return null; 19 } 20 } 21 public class TestDemo { 22 23 public static void main(String[] args) throws Exception { 24 /* 25 * Thread类中并没有接收Callabel对象的构造方法,所以无法通过start()来启动多线程 26 * 但是Java提供java.util.concurrent。FutureTask<V> 类 , 27 * 在FutureTask类的定义结构如下: 28 * public class FutureTask<V> extends Object implements Future<V> , Runnable 29 * 类中有如下的构造方法: 30 * public FutureTask(Callable<V> callable) 31 * FutureTask类接收Callable接口对象。目的就是:取得call()方法的返回结果 32 */ 33 MyThread mt1 = new MyThread(); 34 MyThread mt2 = new MyThread(); 35 FutureTask<String> task1 = new FutureTask<String>(mt1); // 目的是为了接收call返回值 36 FutureTask<String> task2 = new FutureTask<String>(mt2); 37 // FutureTask是Runnable接口子类,所以可以使用Thread类的构造来接收task对象 38 new Thread(task1).start(); 39 new Thread(task2).start(); 40 // 多线程执行完毕后,依靠FutureTask的父接口 Future中的get()方法完成。 41 System.out.println("A--->" + task1.get()); 42 System.out.println("B--->" + task2.get()); 43 44 } 45 46 }
多线程的常用操作方法:
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、休眠
1 import java.util.concurrent.Callable; 2 import java.util.concurrent.ExecutionException; 3 import java.util.concurrent.FutureTask; 4 5 class MyThread implements Runnable { 6 @Override 7 public void run() { 8 9 for ( int x = 0 ; x < 10000 ; x ++ ) { 10 try { // 执行sleep休眠,休眠1秒(sleep是以纳秒为单位) 11 Thread.sleep(1000); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 System.out.println(Thread.currentThread().getName() + ">>>" + x); 16 } 17 } 18 } 19 public class TestDemo { 20 21 public static void main(String[] args) throws Exception { 22 MyThread mt = new MyThread() ; 23 new Thread(mt,"线程 A").start(); 24 } 25 26 }
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 四种代码块: 普通代码块、构造块、静态块、同步块
1 import java.util.concurrent.Callable; 2 import java.util.concurrent.ExecutionException; 3 import java.util.concurrent.FutureTask; 4 5 class MyThread implements Runnable { 6 private int ticket = 5 ; // 一共有5个点 7 8 @Override 9 public void run() { 10 11 for ( int x = 0 ; x < 20; x ++ ) { 12 synchronized(this) { // 【同步块】;当前操作每次只允许一个对象进入 13 if (this.ticket > 0) { 14 try { 15 Thread.sleep(100); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 System.out.println(Thread.currentThread().getName() + ticket--); 20 } 21 } 22 } 23 } 24 } 25 public class TestDemo { 26 27 public static void main(String[] args) throws Exception { 28 MyThread mt = new MyThread(); 29 new Thread(mt,"A >>>").start(); 30 new Thread(mt,"B >>>").start(); 31 new Thread(mt,"C >>>").start(); 32 } 33 }
上例代码实现得则是“同步块”,利用synchronized关键字使得12~31行代码被关键字锁住,每一次执行只可以进入一个对象,实现同步。
所有(多个)线程,只有当里面得线程结束了,自己才可以进入同步块【一次只可以进入一个对象】
但是有人翻译同步代码块比较“粗糙”,所以还有另外得【同步方法】:
1 package hello; 2 3 import java.util.concurrent.Callable; 4 import java.util.concurrent.ExecutionException; 5 import java.util.concurrent.FutureTask; 6 7 class MyThread implements Runnable { 8 private int ticket = 5 ; // 一共有5个点 9 10 @Override 11 public void run() { 12 13 for ( int x = 0 ; x < 20; x ++ ) { 14 this.sale(); // 调用同步方法 15 } 16 } 17 public synchronized void sale() { // 同步方法 18 if (this.ticket > 0 ) { 19 try { 20 Thread.sleep(1000); 21 } catch (InterruptedException e) { 22 e.printStackTrace(); 23 } 24 System.out.println(Thread.currentThread().getName() + ticket--); 25 } 26 } 27 } 28 public class TestDemo { 29 30 public static void main(String[] args) throws Exception { 31 MyThread mt = new MyThread(); 32 new Thread(mt,"A >>>").start(); 33 new Thread(mt,"B >>>").start(); 34 new Thread(mt,"C >>>").start(); 35 } 36 }
上例代码则是放弃了同步块,使用同步方法实现“同步”。17~26行定义了同步方法:同步方法得定义结构依旧是使用synchronized关键字;(14行)在run()方法中,使用this方法调用同步方法。
【ps:异步操作得速度高于同步操作,而同步操作时得数据安全性高于异步操作时得数据安全性;】
死锁:
所谓的同步就是一个线程对象等待另外一个线程对象执行完毕后的操作形式;线程同步过多,就有可能造成死锁。
1 package hello; 2 3 import java.util.concurrent.Callable; 4 import java.util.concurrent.ExecutionException; 5 import java.util.concurrent.FutureTask; 6 7 class A { 8 public synchronized void say(B b) { 9 System.out.println("把你的本给我,我给你笔,否则不给!"); 10 b.get(); 11 } 12 public synchronized void get() { 13 System.out.println("得了本,付出了笔,还是什么都干不了!"); 14 } 15 } 16 class B { 17 public synchronized void say(A a) { 18 System.out.println("把你的笔给我,我就给你本,否则不给!"); 19 a.get(); 20 } 21 public synchronized void get() { 22 System.out.println("得到了笔,付出了本,还是什么都干不了!"); 23 } 24 25 } 26 public class TestDemo implements Runnable{ 27 private static A a = new A() ; 28 private static B b = new B() ; 29 public static void main(String[] args) throws Exception { 30 new TestDemo() ; 31 } 32 public TestDemo() { 33 new Thread(this).start(); 34 b.say(a); 35 } 36 @Override 37 public void run() { 38 a.say(b); 39 } 40 }
上例是“死锁操作”,过多的同步线程,导致的死锁。【无意义代码】
死锁是程序开发中,由于某种逻辑上的错误所造成的问题;
# 同步产生的问题:
1、多个线程访问同一空间资源是一定要处理好同步,可以使用同步代码块或同步方法解决;
2、但是过多的同步,有可能造成“死锁”
# 总结:
1、最简单的同步或异步操作,就是通过 synchronized 关键字实现。
2、死锁是一种不定、不可预的状态。
---------
来源:https://www.cnblogs.com/wangyuyang1016/p/10952161.html