在Java中,如果要实现多线程,必须依靠一个线程的主体类(就好比主类的概念一样,表示的是一个线程的主类)。
但是这个线程的主体类在定义时也需要一些特殊的要求,即类需要继承Thread类或实现Runnable(Callable)接口来完成定义
多线程的实现
继承Thread类,实现多线程
public class MyThread extends Thread { //多线程的操作类
private String name ;
public MyThread(String name) {
this.name = name ;
}
@Override
public void run() { //覆写run方法作为线程的主操作类
for(int x = 0 ; x <200 ;x++) {
System.out.println(this.name+"-->"+x);
}
}
}
main:
MyThread mt1 = new MyThread("线程A");
MyThread mt2 = new MyThread("线程B");
MyThread mt3 = new MyThread("线程C");
//启动多线程
mt1.start();
mt2.start();
mt3.start();
为什么多线程启动不是调用run()而必须调用start()?
在java的开发里面有一门技术称为Java本地接口(Java Native Interface,JNI)技术。使用Java调用本地操作系统提供的函数。
这个技术不能离开特定的操作系统,如果要执行线程,需要根据操作系统来进行资源的分配。主要是由JVM根据不同的操作系统来实现。
即使用Thread类的start()方法不仅要启动多线程的执行代码,还要根据不同的操作系统进行资源分配
实现Runnable接口,实现多线程
public class MyThread2 implements Runnable { //实现接口
private String name ;
public MyThread2(String name) {
this.name = name ;
}
@Override
public void run() { //覆写run()
for(int x = 0 ; x <200 ;x++) {
System.out.println(this.name+"-->"+x);
}
}
}
main:
/***实现Runnable接口的多线程,,Thread是Runnable接口 的子类(代理), * 通过Thread类对象包装Runnable接口对象实例,然后利用Thread 类的start()方法启动多线程***/
MyThread2 mt01 = new MyThread2("线程1");
MyThread2 mt02 = new MyThread2("线程2");
MyThread2 mt03 = new MyThread2("线程3");
new Thread(mt01).start();
new Thread(mt02).start();
new Thread(mt03).start();
使用Runnable接口可以有效避免单继承局限问题,所以在实际的开发中对于多线程的实现首选Runnable接口
两种实现方式的区别
public class Thread extends Object implements Runnable
通过定义可以发现,Thread类也是Runnable接口的子类,之前利用Runnable接口实现的多线程,实际结构:
Runnable接口 Thread类
class MyThread implements Runnable{ class MyThread extends Thread(){
@Override @Override
public void run(){//线程主方法 public void run(){ //线程主方法
//线程操作方法 //线程操作方法
} }
} }
MyThread mt = new MyThread(); MyThread mt = new MyThread();
new Thread(mt).start(); mt.start();
利用Callable接口实现多线程
使用Runnable接口可以避免单继承的局限性,但是Runnable接口里面的run()方法不能返回操作结果。
从jdk1.5开始提供了新的接口: java.util.concurrent.Callable
@FunctionalInterface
public interface Callable<V>{
public V call() throws Exception;
}
public class MyThread implements Callable<String> {
private int ticket = 0 ;
@Override
public String call() throws Exception {
for(int i = 0 ; i <100; i ++) {
if(this.ticket > 0) {
System.out.println("卖出,剩余"+this.ticket --);
}
}
return "票卖完了!"; //返回结果
}
}
如何启动实现Callable接口的多线程?
Thread类没有定义构造方法可以直接接收Callble接口对象实例,并且由于需要接收call()方法返回值的问题。JDK1.5开始,提供了java.util.concurrent.FutureTask<V>类
public class FutureTask<V> extends Object implements RunnableFuture<V>
/***实现了callable 接口的多线程,可以返回结果; * RunnableFuture接口 实现了Runnable接口和Future接口 * FutureTask 又实现了 RunnableFuture 接口 * ***/
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
FutureTask<String> task1 = new FutureTask<String>(mt1);
FutureTask<String> task2 = new FutureTask<String>(mt2);//FutureTask是Runnable接口子类,所以可以使用Thread类的构造来接收task对象
new Thread(task1).start();
new Thread(task2).start(); //多线程执行完毕后可以取得内容
System.out.println("A线程的返回结果"+task1.get());
System.out.println("B线程的返回结果"+task2.get());
线程的操作状态
1.创建状态
在程序中用构造方法创建一个线程对象后,新的线程对象便处于新建状态。此时已经有相应内存空间和其他资源,但处于不可运行状态。
2.就绪状态
新建线程对象后,调用该线程的start()方法就可以启动线程。当线程启动时,线程进入就绪状态。此时线程将进入线程队列排队,等待cpu服务,这表明它已经具备了运行状态。
3.运行状态
当就绪状态的线程被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的run()方法。
4.堵塞状态
一个正在执行的线程在某些情况下,如被人为挂起或需要执行耗时的输入输出操作时,将让出CPU,并暂时中止自己的执行,进入堵塞状态。在可执行状态下,如果调用sleep()、suspend()、wait()等方法,线程都将进入堵塞状态。堵塞时,线程不能进入排队队列,只有当引起堵塞的原因被消除后,线程才可以转入就绪状态。
5.终止状态
线程调用stop()方法时候run()方法执行结束后,就处于终止状态。处于终止状态的线程不具有继续运行的能力
多线程常用操作方法
线程的命名与取得
public Thread(Runnable target, String name) 构造方法 实例化线程对象,接受Runnable接口子类对象,同时设置线程名字
public final void setName(String name) 普通方法 设置线程名字
public final String getName() 普通方法 取得线程名字
进程在哪里?
当用户使用Java命令执行一个类时就表示启动了一个JVM的进程,而主方法只是进程上的一个线程而已,当一个类执行完毕后,此进程会自动消失。
而且每个JVM进程都至少启动一下两个线程:
-
- main线程:程序的主要执行,以及启动子线程
- gc线程:负责垃圾的收集。
线程的休眠
public static void sleep(long millis) throws InterruptedException ,设置的休眠单位时间是毫秒(ms)
线程优先级
在Java线程操作中,所有的线程在运行前都会保持就绪状态,此时哪个线程的优先级高,就可能先被执行。
public static final int MAX_PRIORITY 常量 最高优先级,数值为10
public static final int NORM_PRIORITY 常量 中等优先级,数值为5
public staic final int MIN_PRIORITY 常量 最低优先级,数值为1
public final void setPriority(int newPriority) 普通 设置线程优先级
public final int getPriority() 普通 取得线程优先级
线程的同步与死锁
当多个线程操作同一资源时,就有可能出现不同步的情况:还没有等到前一个线程的执行结果就进行了下一个线程的操作,从而出现问题。
例如:
多个线程进行卖票操作,当票数为1的时候,前一个线程获取到数量为1的票数,还没等它的执行完成,后一个线程也获取到了数量为1的票数开始执行。最终出现问题
同步操作
一个代码块中的多个操作在同一个时间段内只能有一个线程进行,其他线程要等待此线程完成后才可以继续执行。
实现同步操作可以使用synchronized关键字。synchronized关键字可以通过以下两种方式进行使用:
1.同步代码块,利用synchronized包装的代码块,但是需要指定同步对象,一般设置为this;
2.同步方法,利用synchronized定义的方法
同步代码块:
public MyThread implements Runnable{
private int ticket = 5 ;
@Override
public void run(){
for(int x =0;x<20;x++){
synchronized(this){
if(this.ticket > 0){
//卖票操作
}
}
}
}
}
同步方法:
public MyThread implements Runnable{
private int ticket = 5 ;
@Override
public void run(){
for(int x =0;x<20;x++){
this.sale() //调用synchronized方法
} }
public synchronized void sale(){
//卖票操作。。。
}
}
abstract的method是否可以同时是static,是否可以同时是native,是否可以同时是synchronized?
method,static,natice,synchronized都不能和“abstract”同时声明方法
当一个线程进入一个对象的synchronized方法后,其他线程是否可以访问此对象的其他方法?
不能访问,一个对象操作一个synchronized方法只能由一个线程访问。
死锁
概念:两个线程都在等待彼此先完成,造成了程序的停滞状态,一般程序的死锁都是在程序运行时出现的。过多的同步也会造成死锁。