线程离不开进程。如果进程消失了线程肯定消失,反之如果线程消失了进程不一定消失。
- 目录
多线程
如果想要在Java中实现多线程有两种途径
- 继承Thread类;
- 实现Runnable接口(Callable接口);
继承Thread类
Thread类是一个支持多线程的功能类,只要有一个子类他就可以实现多线程的支持。
class MyThread extends Thread { //这就是一个多线程的操作类
}
我们都知道,所有程序的起点是main()
方法,但是所有的线程也有一个自己的起点,那么这个起点就是run()
方法,也就是说在多线程的每个主体类中我们必须覆写Thread()
类中的run()
方法;
public void run(){
}
这个方法没有返回值,也就是说明线程一旦开始就要一直执行下去不能够返回内容。
所有的线程与进程都是一样的,都必须轮流去抢占资源,所以多线程的执行都应该是多个线程彼此交替执行,也就是说直接调用run方法并不能够启动线程,多线程的启动的唯一方法就是Thread类中的start()
方法;
public void start();
//此方法调用的方法体是run方法定义的
使用start()
方法后,线程之间才可以交替执行。
使用Thread类的start()
方法不仅仅要启动多线程的执行代码,还要去根据不同的操作系统进行资源分配。
实现Runnable接口
虽然Thread类可以实现多线程主体类定义,但是它有一个问题,java具有单继承局限,所以我们应该尽量找的使用类得继承来实现多线程,为了解决单继承的闲置,在java中提供了Runnable接口,这个接口定义如下:
@FunctionalInterface
public interface Runnable{
public void run();
}
所以只需要让一个类实现Runnable接口即可,并且也需要覆写run()
方法;
- 与继承Thread类相比,多线程的类在接口上与之前是没有区别的,但是有一点严重区别,如果此时继承了Thread,那么可以直接继承
start()
方法,但是如果实现的Runnable接口,并没有start()
方法可以被继承。 - 不管何种情况下,想要启动多线程,都需要依靠Thread类来完成,在Thread里面定义有
构造方法(Thread(Runnable target)
);接收到额是Runnable接口对象
class MyThread implements Runnable{
private String name ;
public MyThread (String Thread){
this.name=name;
}
@Override
public void run(){//覆写run方法
for(int i=0;i<1000;i++){
System.out.println(this.name+" --> "+i);
}
}
}
public class Text{
public static void main (String args[]){//主类
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
MyThread my3 = new MyThread();
new Thread(my1).start();
new Thread(my2).start();
new Thread(my3).start();
}
}
这样就避免了单继承的局限性,在实际工作中使用Runnable是最合适的。
多线程两种方法的区别?
首先需要说明,Runnable接口和Thread类相比,解决了单继承的限制。
Thread实现过程
public class Thread extends Object implements Runnable
//来自java开发文档
这里我们可以看出来Thread实现了Runnable接口,而当我们使用Runnable接口来实现多线程的时候需要使用Thread中的方法
此时看起来像是代理设计模式,但是如果是代理设计模式,客户端调用的代理类的方法也应该是接口里提供的方法,那么也应该是run()方法才对。(设计者最早想到了代理的结构,但是他没有做到代理的样子,所以才提供了两种多线程的实现方式)
出来以上的联系之外还有:使用Runnable接口可以比Thread类更好的描述出数据共享这一概念。此时的数据共享指的是多个线程访问统一资源的操作
总结:
1. Thread类是Runnable接口的子类,使用Runnable可以避免单继承的局限;
2. Runnable实现的多线程可以比Thread类实现的多线程更加清晰得 描述数据共享的概念。
callable接口
使用Runnable接口实现的多线程可以避免单继承局限,但是Runnable接口中的
run()
没有返回值;
callable在java.util.concurrent.Callable内
@FunctionalInterface
public interface Callable<V>{
public V call() throws Exception;
}
call()方法执行完线程的主题功能之后就会返回一个结果,而且返回结果的类型由Callable接口上的泛型来决定。
范例:定义泛型主题类
class My implements Callable<String>{
private int tacket = 10;
public String call() throws Exception {
for(int i=0;i<100;i--){
if(tacket>=0){
System.out.println("买票:ticket= "+this.tacket--);
}
}
return "票买完了";
}
}
java.util.concurrent Class FutureTask<V>
//这个类主要是负责callable接口对象操作的,这个接口的定义接口为
public class FutureTask<V> extends Object implements RunnableFuture<V>
发现FutureTask实现了RunnableFuture接口,那么可以在查看RunnableFuture可以发现:
public interface RunnableFuture<V> extends Ruuture<V>
RunnableFuture又实现了Runnable接口!!!!
在FutureTask类里面定义有如下的构造方法
FutureTask(Callable<V> callable)
FutureTask接受的目的只有一个,那么就是去的call()方法的返回结果。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class My implements Callable<String>{
private int tacket = 10;
public String call() throws Exception {
for(int i=0;i<100;i--){
if(tacket>=0){
System.out.println("买票:ticket= "+this.tacket--);
}
}
return "票买完了";
}
}
public class Main {
public static void main(String args[]) throws ExecutionException, InterruptedException {
My my1 = new My();
My my2 = new My();
FutureTask<String> task1 = new FutureTask<String>(my1);
FutureTask<String> task2 = new FutureTask<String>(my2);
//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());
}
}
第三种方法最麻烦在问题在于需要接受返回值信息,并且又要于原始的多线程的实现靠拢(想Thread靠拢)
总结
- 对月多线程的实现,终点在于Runnable接口与Thread类启动的配合上;
- 对于JDK1.5新特性,了解就可以了,知道他们之间的区别在于返回结果;
扩展
线程的命名与取得
因为每一个线程运行都是不同的结果,需要去抢占属于自己的资源,如果想要区分每一个线程,那么我们就需要为每一个线程进行命名。建议在线程启动之前就命名。
如果先要对线程进行命名,需要是用到Thread类;
- 构造方法:Thread(Runnable target, String name)
- 设置名字:public final void setName(String name)
- 取得名字: public final String getName()
在为线程命名的时候由于这些方法是在Thread类里面的,所以我们在使用Runnable方式实现多线程的时候,想要获取线程名字,那么我们可以取得当前执行方法的线程名字。所以在Thread里面有一个这样的方法,
- 取得当前线程对象:static Thread currentThread()
import java.util.concurrent.ExecutionException;
class MyThread implements Runnable{
private int tacket = 10;
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class Main {
public static void main(String args[]) throws ExecutionException, InterruptedException {
MyThread mt1 = new MyThread();
new Thread(mt1).start();
new Thread(mt1).start();
new Thread(mt1).start();
}
}
运行结果为:
Thread-0
Thread-1
Thread-2
通过以上程序我们可以知道,在实例化Thread类对象的时候没有为其命名,那么会自动进行编号命名。
- 设置名字的时候只需要在构造方法的参数后面加上线程的名字即可。
new Thread(mt1,"线程1").start();
- 同时我们观察一下程序会发现
System.out.println(Thread.currentThread().getName());//不要放在线程里面
//输出为main
我们可以知道,其实main是一个主线程,而在main主方法上面创建的线程为main的子线程。
- 通过以上程序我们可以发现,线程一直存在(主方法就是主线程),可是进程在哪儿呢?
每当的使用java命令去解释一个程序类的时候,对于操作系统而言都相当与启动了一个新的进程,而main只是新新进程上的子线程。
- 每一个jvm在启动的时候会启动几个线程呢?
1. main线程,程序的主要执行以及启动子线程
2. gc线程,垃圾收集。
线程的休眠
线程休眠即:让线程速度变慢一点,休眠方法为:
public static void sleep(long millis) throws InterruptedException
实例:
import java.util.concurrent.ExecutionException;
class MyThread implements Runnable{
public void run() {
for(int i=0;i<100;i++){
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"x= "+i);
}
}
}
public class Main {
public static void main(String args[]) throws ExecutionException, InterruptedException {
MyThread mt1 = new MyThread();
new Thread(mt1,"线程1").start();
}
}
默认情况下在休眠的时候设置了多个线程对象,那么所有的线程对象将一起进入到run()
方法,所以会出现资源抢占问题。
线程优先级
优先级高的线程有可能先执行
- 在Thread里面有两个方法来设置优先级
-
设置优先级:
public final void setPriority(int newPriority)
- 取得优先级:
public final int getPriority()
设置和取得的返回值是int类型,对这个内容有三种取值:
- 最大优先级:
public static final int MAX_PRIORITY
(10) - 默认优先级:
public static final int NORM_PRIORITY
(5) - 最大优先级:
public static final int MIN_PRIORITY
(1)
实例程序:
import java.util.concurrent.ExecutionException;
class MyThread implements Runnable{
public void run() {
for(int i=0;i<100;i++){
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"x= "+i);
}
}
}
public class Main {
public static void main(String args[]) throws ExecutionException, InterruptedException {
MyThread mt1 = new MyThread();
Thread th1 =new Thread(mt1,"线程1");
Thread th2 =new Thread(mt1,"线程2");
Thread th3 =new Thread(mt1,"线程3");
th1.setPriority(Thread.MAX_PRIORITY);
th1.start();
th2.start();
th3.start();
}
}
通过这个代码我们可以大致了解到,线程优先级高的并不一定先执行。
总结
- Thread.currentThread可以取得当前线程对象;
- Thread.sleep();主要是休眠,感觉是一起休眠,但实际上室友先后顺序的;
- 优先级越高的咸亨对象有可能限制性
线程同步问题
同步问题即:多个线程访问同一个资源是所需要考虑的问题。
在java中要实现同步需要使用synchronized
关键字。这个关键字有两种使用方法:
- 同步代码块
synchronized(锁定对象){
//需要同步执行的代码;
}
- 同步方法
public synchronized 返回值 函数名(){
//需要同步的代码;
}
同步操作和异步操作相比较:
- 同步代码执行速度较慢,但是数据安全性高,是相对安全的线程操作;
- 异步操作执行速度较快,但是数据有可能会因为竞争同一个资源而出现差错。
死锁
有时候为了解决数据安全,启用了线程同步,但是启用的线程同步越多,遇到的死锁可能性就越大,所以为了避免这种逻辑错误,需要进行调试和优化。
生产者和消费者问题
生成这和消费者指的是两个不同的线程对象,操作统一资源的情况,具体操作流程如下。
- 生产者负责生产数据
- 生产者没生产完一组数据之后,消费者就要取走一组数据。
唤醒与等待
- 等待
public final void wait() throws InterruptedException
- 唤醒
public final void notify()
- 唤醒正在等待的全部线程:
public final void notifyAll()
生产者和消费者问题代码实例:
生产者生产一个,只有当消费者消费完之后才可以在生产一个。
class Info {
private String name;
private String age;
private boolean flag = true;
public synchronized void set(String name, String age) {
if (this.flag == false) {
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.age = age;
this.flag = false;
super.notify();
}
public synchronized void get(){
if(this.flag == true){
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.name + " -- " + this.age);
this.flag=true;
super.notify();
}
}
class Productor implements Runnable{
private Info info ;
public Productor(Info info){
this.info=info;
}
public void run() {
for(int i=0;i<100;i++){
if(i%2==0) {
this.info.set("ssss", "23333");
}else{
this.info.set("xx","18");
}
}
}
}
class Consumer implements Runnable{
private Info info ;
public Consumer(Info info){
this.info=info;
}
public void run() {
for(int i=0;i<100;i++){
this.info.get();
}
}
}
public class Main {
public static void main(String args[]) throws Exception{
Info info =new Info();
Productor pr = new Productor(info);
Consumer co = new Consumer(info);
new Thread(pr).start();
new Thread(co).start();
}
}