Java多线程,从零基础到入门
多线程简介
多线程的两个概念——并发和并行简介
并发:在同一时刻,有多个指令在单个cpu上交替执行
并行:在同一时刻,有多个指令在多个cpu上同时执行
并发和并行是有可能同时进行的。
例如你的电脑是2核4线程的,遇到多个线程,在并行的同时每条线程也在并发
多线程的实现方式
有3种方法
1.利用Thread类创造多线程
创造新执行线程有两种方法
1.
(1)定义一个类声明为Thread的子类,
(2)该子类重写Thread的run方法。
(3)然后就可以实例化并启动子例(创建对象并调用方法)。
下面是一个例子
class MyThread extends Thread{
public void run() {
int cnt = 0;
while(cnt<20) {
cnt++;
System.out.println(getName()+"run在运行");
}
}
}
public class Main {
public static void main(String[] args){
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程1");
t2.setName("线程2");
t1.start();t2.start();
}
}
运行结果发现线程1和线程2交替运行,即并发
提问:为什么是用start而不是run?
答:因为线程的启动是通过调用start()
方法来实现的,而非直接调用run()
方法。
当调用start()
方法时,它会启动一个新的线程,并在新线程中执行run()
方法的内容。这样做的好处是,每个线程都有自己独立的执行路径,可以同时执行多个线程,实现并发操作。
如果直接调用run()
方法,那么run()
方法将会在当前线程(主线程)中直接执行,而不会创建新的线程,那就是单线程了,跟以前的调用没有区别。这样做的结果是,run()
方法的执行会阻塞主线程,直到run()
方法执行完毕才会继续执行后续代码,无法实现并发操作。
因此,在使用多线程时,我们应该通过调用start()
方法来启动新线程,并让新线程执行run()
方法的内容,以实现并发执行。
2.利用Runnable接口的方式进行实现
(1)创造一个类继承Runnable接口
(2)重写里面的run方法
(3)创造自己的类的对象(表示要执行的任务)
(4)创造一个Thread类对象,并开启多线程
class MyRun implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
//System.out.println(getName()+"run方法在运行");
//这样是错的,因为getName是Thread里的方法,无法直接调用
//用Thread.currentThread可以获取到当前线程的对象
Thread t = Thread.currentThread();
for(int i = 0;i<10;i++) {
System.out.println(t.getName()+"run方法在执行");
//System.out.println(Thread.currentThread().getName()+"run方法在执行");
//这样的链式编程也可以,尽量用这种
}
}
}
public class Main {
public static void main(String[] args){
//实例化MyRun对象
//表示多线程要执行的任务
MyRun mr = new MyRun();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
//给线程设置名字
t1.setName("线程1");
t2.setName("线程2");
//开启多线程
t1.start();t2.start();
}
}
提问:
3.利用Callable和Future接口方式实现
第三种实现方法相当于对前面方法的补充。因为我们可以看到Thread和Runnable里的run方法都没有返回值,无法获取多线程运行的结果,第三种方法就弥补了这个缺陷。
特点:可以获取多线程运行的结果
(1)创建MyCallable类实现Callable接口
(2)重写call方法(是有返回值的,表示多线程的运行结果)
(3)创建MyCallable的对象(表示要多线程执行的任务)
(4)创建FutureTask的对象(管理多线程运行结果)。因为Future是一个接口,无法直接实例化,所以要创建一个类
(5)创建Thread类的对象,并启动
class MyCallable implements Callable<Integer>{
//<>里写返回值的类型
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
//任务,算1-9的和
int sum = 0;
for(int i = 1;i<=9;i++) {
sum+=i;
}
return sum;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException{
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread t1 = new Thread(ft);
t1.start();
Integer res = ft.get();
System.out.println(res);
}
}
提问:不是说用Future接口实现吗,为什么用了FutureTask?
首先要知道一点,FutureTask类是编译器已经为我们实现好的一个类。
FutureTask
是实现了Future
接口的一个具体类,它实现了Future
接口的所有方法,并且还提供了一些额外的方法。FutureTask
可以用来包装一个Callable
或Runnable
对象,将其异步执行,同时允许对其执行状态进行查询和取消任务的执行。
小结
多线程中常用的成员方法
Thread的构造方法
Thread的成员方法
下面是对以上方法的解读
线程的命名
我们可以用setName和构造方法对线程命名,但用构造方法命名,需要调用父类(也就是Thread)的构造方法,因为 子类不会继承到父类的 Thread(String name)构造方法!
package demo;
import java.util.*;
import java.util.Scanner;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.Random;
import java.util.Arrays;
import javax.swing.*;
import java.awt.*;
import java.awt.*;
import java.awt.event.*;
class MyThread extends Thread{
public MyThread() {
super();
// TODO Auto-generated constructor stub
}
//我们可以用setName和构造方法对线程命名,但用构造方法命名,
//需要调用父类(也就是Thread)的构造方法,
//因为 子类不会继承到父类的 Thread(String name)构造方法!
public MyThread(String name) {
super(name);
// TODO Auto-generated constructor stub
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0;i<10;i++) {
System.out.println(getName()+"@"+i);
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyThread t1 = new MyThread("线程1");
MyThread t2 = new MyThread("线程2");
t1.start();t2.start();
}
}
获取当前线程与线程休眠
要注意这两个方法是静态方法,因此可以通过类名来使用
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
System.out.println(Thread.currentThread().getName());
}
}
再讲一下sleep方法。在哪条线程用sleep,哪条线程就休眠。不要理解成sleep会让整个程序都休眠!下面是验证。
如果没有用sleep,那么一定是线程1先执行完,因为线程1只用输出0个数,线程2要输出1000个数。
但在线程1加上sleep后,线程1每输出一个数就会休眠1秒,在休眠的时候线程2就执行完毕了。因此是线程2先执行完。
package demo;
import java.util.*;
import java.util.Scanner;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.Random;
import java.util.Arrays;
import javax.swing.*;
import java.awt.*;
import java.awt.*;
import java.awt.event.*;
class MyThread extends Thread{
public MyThread() {
super();
// TODO Auto-generated constructor stub
}
//我们可以用setName和构造方法对线程命名,但用构造方法命名,
//需要调用父类(也就是Thread)的构造方法,
//因为 子类不会继承到父类的 Thread(String name)构造方法!
public MyThread(String name) {
super(name);
// TODO Auto-generated constructor stub
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0;i<10;i++) {
System.out.println(getName()+"@"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class MyThread2 extends Thread{
public MyThread2() {
;
}
public MyThread2(String name) {
super(name);
// TODO Auto-generated constructor stub
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0;i<1000;i++) {
System.out.println(getName()+"@"+i);
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyThread t1 = new MyThread("线程1");
MyThread2 t2 = new MyThread2("线程2");
t1.start();
t2.start();
}
}
线程的优先级
优先级共有 1-10 十个等级,5是默认的等级
线程的优先级高意味着它更有可能被CPU调度器选中执行,但并不意味着一定会抢占CPU。在多线程编程中,线程的优先级高只是增加了被选中执行的概率,而不是绝对的抢占。
详细地说,java采取的是抢占式调度,即多个线程去抢夺cpu的执行权,但是抢不抢的到是随机的,体现了随机性!
因此,就算此时有两个线程,设置线程1的优先级是1,线程2的优先级是10。线程1依然有可能比线程2先执行完。因为线程2的优先级虽然高,但这是代表线程2先被执行的概率高,而不是一定会被优先执行!
package demo;
import java.util.*;
import java.util.Scanner;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.Random;
import java.util.Arrays;
import javax.swing.*;
import java.awt.*;
import java.awt.*;
import java.awt.event.*;
class MyThread extends Thread{
public MyThread() {
super();
// TODO Auto-generated constructor stub
}
//我们可以用setName和构造方法对线程命名,但用构造方法命名,
//需要调用父类(也就是Thread)的构造方法,
//因为 子类不会继承到父类的 Thread(String name)构造方法!
public MyThread(String name) {
super(name);
// TODO Auto-generated constructor stub
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0;i<10;i++) {
System.out.println(getName()+"@"+i);
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyThread t1 = new MyThread("线程1");
MyThread t2 = new MyThread("线程2");
t1.setPriority(1);
t2.setPriority(10);
//按照概率来讲,线程1更有可能先执行完
t1.start();
t2.start();
}
}
守护线程
线程可以分为守护线程和非守护线程。
当其他非守护线程执行完毕,守护线程会陆续结束。(注意,不是马上结束,而是会再执行一会才结束)
我们把非守护线程比作女神线程,守护线程比作备胎线程,当女神不存在,那么备胎也没有存在的必要了。验证一下
package demo;
import java.util.*;
import java.util.Scanner;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.Random;
import java.util.Arrays;
import javax.swing.*;
import java.awt.*;
import java.awt.*;
import java.awt.event.*;
class MyThread extends Thread{
public MyThread() {
super();
// TODO Auto-generated constructor stub
}
//我们可以用setName和构造方法对线程命名,但用构造方法命名,
//需要调用父类(也就是Thread)的构造方法,
//因为 子类不会继承到父类的 Thread(String name)构造方法!
public MyThread(String name) {
super(name);
// TODO Auto-generated constructor stub
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0;i<10;i++) {
System.out.println(getName()+"@"+i);
}
}
}
class MyThread2 extends Thread{
public MyThread2() {
;
}
public MyThread2(String name) {
super(name);
// TODO Auto-generated constructor stub
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0;i<1000;i++) {
System.out.println(getName()+"@"+i);
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyThread t1 = new MyThread("女神");
MyThread2 t2 = new MyThread2("备胎");
t2.setDaemon(true);
t1.start();
t2.start();
}
}
可以看到,女神线程结束后,备胎线程没有马上结束,而是陆续结束。
如果不是陆续结束的话,那么会打印到 备胎@999。运行结果显然不是这样的。
举个守护线程的例子,助于理解。
比如:我们在qq聊天,边聊天边给对方发文件,此时就有两条线程。
如果关闭了聊天窗口,那么发送文件的线程也会停止。
显然,在这里,传输文件线程就是守护线程,因为它会随着聊天线程的结束而陆续结束。
礼让线程
假如现在有线程1和线程2,此时线程1有cpu执行权
如果不使用礼让线程,那么线程1执行一次后依然拥有cpu的执行权,直到执行权被别的线程抢夺。这种情况的结果很大概率是线程1一下子就执行完了
如果使用礼让线程,那么线程1执行一次后,就会主动放弃cpu的执行权,然后所有线程(当然也包括线程1)再进行抢夺.
如运行结果所示,线程的执行变得更均匀了,因为会有使用礼让线程
package demo;
import java.util.*;
import java.util.Scanner;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.Random;
import java.util.Arrays;
import javax.swing.*;
import java.awt.*;
import java.awt.*;
import java.awt.event.*;
class MyThread extends Thread{
public MyThread() {
super();
// TODO Auto-generated constructor stub
}
//我们可以用setName和构造方法对线程命名,但用构造方法命名,
//需要调用父类(也就是Thread)的构造方法,
//因为 子类不会继承到父类的 Thread(String name)构造方法!
public MyThread(String name) {
super(name);
// TODO Auto-generated constructor stub
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0;i<100;i++) {
System.out.println(getName()+"@"+i);
Thread.yield();
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyThread t1 = new MyThread("线程1");
MyThread t2 = new MyThread("线程2");
t1.start();
t2.start();
}
}
插入线程
插入线程是指在运行中的程序中创建一个新的线程,并将其插入到已有的线程执行流中。
下面这段代码,有两个线程
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyThread t1 = new MyThread("线程1");
t1.start();
for(int i = 0;i<10;i++) {
System.out.println("main线程"+i);
}
}
}
如果我们想执行完线程1在执行main线程,我们就可以用插入线程
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyThread t1 = new MyThread("线程1");
t1.start();
//将t1插入到当前线程之前
//当前在main函数里,所以当前是main线程
t1.join();
for(int i = 0;i<10;i++) {
System.out.println("main线程"+i);
}
}
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!