初识Java多线程
- 进程与线程
1、进程
进程是系统进行资源分配和调度的一个独立单位;
动态产生、动态消亡;
具有并发性;
并行指的是同一时刻,多个指令在多台处理器上同时运行。
并发指的是同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,看起来就好像多个指令同时执行一样。
进程由程序、数据和进程控制块三部分组成。
2、线程
是进程中的一个实体,是被系统调度的基本单位;
不拥有系统资源,与同个进程的其他线程共享资源;
同一进程中的多个线程之间可以并发执行;
多线程:在单个程序中同时运行多个线程完成不同的工作;
与jvm共同消亡;
- 线程的生命周期
关于Java中线程的生命周期,首先看一下下面这张较为经典的图:
1、新建状态:当线程对象创建后,即进入新建状态 Thread t = new MyThread()
2、就绪状态:当线程对象调用start()方法,即进入就绪状态。这意味着CPU随时可以对该线程进行调度执行
3、运行状态:当CPU开始调度处于就绪状态的线程,该线程开始执行,即进入运行状态。要进入执行状态之前必须是就绪状态
4、阻塞状态:处于运行状态当线程由于某种原因放弃对cpu的使用权,停止执行,即进入阻塞状态。
阻塞状态分以下三种:
a、等待阻塞 -- 线程执行wait()方法,释放锁
b、同步阻塞 -- 线程获取同步锁失败
c、其他阻塞 -- 调用线程的sleep()或join()或发出了I/O请求时
5、死亡状态:线程执行完了或者因异常退出了run()方法,该线程结束生命周期
- 创建多线程的方式
常用的实现多线程的两种方式
Callable、Future和FutureTask
1、继承Thread类,重写run()方法
package com.demo.test;public class MyThread extends Thread {
private String name;
public MyThread(String name){
this.name = name;
}
@Override
public void run() { //线程执行体
for (int i = 0; i < 10; i++) {
System.out.println(name + "运行 :" + i);
}
}
}
package com.demo.test;
public class ThreadTest {
public static void main(String[] args) {
Thread myThread1 = new MyThread("A"); // 创建一个新的线程 myThread1 此线程进入新建状态
Thread myThread2 = new MyThread("B"); // 创建一个新的线程 myThread2 此线程进入新建状态
myThread1.start(); // 调用start()方法使得线程进入就绪状态
myThread2.start(); // 调用start()方法使得线程进入就绪状态
}
}
运行结果:
A运行 :0B运行 :0
B运行 :1
B运行 :2
B运行 :3
B运行 :4
B运行 :5
B运行 :6
B运行 :7
B运行 :8
A运行 :1
A运行 :2
A运行 :3
B运行 :9
A运行 :4
A运行 :5
A运行 :6
A运行 :7
A运行 :8
A运行 :9
2、实现Runnable接口,重写run()方法
要注意的是:创建Runnable实现类的实例,作为Thread类的构造参数来创建Thread对象,这个Thread对象才是真正的线程对象
package com.demo.test;public class MyRunnable implements Runnable{
private String name;
public MyRunnable(String name){
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(name + "运行 :" + i);
}
}
}
package com.demo.test;
public class ThreadTest {
public static void main(String[] args) {
Runnable myRunnable = new MyRunnable("A"); // 创建一个Runnable实现类的对象
Thread thread1 = new Thread(myRunnable); // 将myRunnable作为Thread target创建新的线程
Runnable myRunnable1 = new MyRunnable("B");
Thread thread2 = new Thread(myRunnable1);
thread1.start(); // 调用start()方法使得线程进入就绪状态
thread2.start();
}
}
运行结果:
A运行 :0B运行 :0
B运行 :1
B运行 :2
A运行 :1
A运行 :2
B运行 :3
A运行 :3
A运行 :4
B运行 :4
A运行 :5
A运行 :6
A运行 :7
B运行 :5
A运行 :8
A运行 :9
B运行 :6
B运行 :7
B运行 :8
B运行 :9
3、使用Callable和Future接口创建线程【有返回值】
创建Callable接口的实现类,实现call()方法,使用FutureTask类来包装Callable对象,以FutureTask对象来作为Thread类的构造参数来创建Thread对象
package com.demo.test;import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer>{
// 与run()方法不同的是,call()方法具有返回值
@Override
public Integer call() throws Exception{
System.out.println("子线程在进行计算");
Thread.sleep(3000);
int sum = 0;
for (int i = 0; i < 100; i++) {
//System.out.println(Thread.currentThread().getName() + " " + i);
sum += i;
}
return sum;
}
}
package com.demo.test;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest {
public static void main(String[] args) {
Callable<Integer> myCallable = new MyCallable(); // 创建MyCallable对象
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象
//FutureTask = Runnable【作为thread的初始化参数】 + Future【获取返回值】
Thread thread = new Thread(ft); //FutureTask对象作为Thread对象的target创建新的线程
thread.start(); //线程进入到就绪状态
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("主线程在执行任务");
try {
int sum = ft.get(); //取得新创建的线程中的call()方法返回的结果
//call()未执行完 get方法一直阻塞
System.out.println("task运行结果,sum = " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("所有任务执行完毕");
}
}
运行结果:
子线程在进行计算主线程在执行任务
task运行结果,sum = 4950
所有任务执行完毕
- 线程调度方法
1、join()-- 在当前线程中调用另一个线程的join()方法,当前线程进入阻塞状态,直到另一个线程执行结束,当前线程从阻塞转就绪状态;
使用场景:当前线程需要用到另一个线程的计算结果
2、 sleep()-- 运行状态进入到阻塞状态,指定睡眠时间,超时后该线程进入就绪状态。注意,sleep方法不会释放锁
3、yield()-- 为了让相同优先级线程轮流执行,线程让步可以让该线程从运行状态进入就绪状态,相同优先级线程再获取执行,不保证下一个运行线程不是当前线程。注意,yield方法不会释放锁
4、interrupt()-- 通过判断线程的中断标记来控制线程
- 线程安全与线程同步
1、为什么需要保证线程安全?
多线程环境下对共享资源的访问可能会引起此共享资源的不一致性。因此,为避免线程安全问题,应该避免多线程环境下对此共享资源的并发访问。
2、为什么需要线程同步?
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
3、同步
a、 同步方法 -- 方法定义中加上synchronized关键字修饰
public synchronized void run() { // ....
}
b、同步代码块
synchronized (obj) { //...
}
关于synchronized关键字的说明:
① 原理
在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。当前线程调用某对象的synchronized方法时,就获取了该对象的同步锁。例如,synchronized(obj),当前线程就获取了“obj这个对象”的同步锁。
不同线程对同步锁的访问是互斥的。也就是说,某时间点,对象的同步锁只能被一个线程获取到!通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问。 例如,现在有个线程A和线程B,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁” —— 线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。
② 基本规则
第一条:当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
第二条:当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。
第三条:当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
③ 实例锁和全局锁
实例锁 -- 锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。实例锁对应的就是synchronized关键字。
全局锁 -- 该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。
就是说,一个非静态方法上的synchronized关键字,代表该方法依赖其所属对象。一个静态方法上synchronized关键字,代表该方法依赖这个类本身。
- 线程通信
1、wait():导致当前线程等待并使其进入到等待阻塞状态。直到其他线程调用该同步锁对象的notify()或notifyAll()方法来唤醒此线程。
2、notify():唤醒在此同步锁对象上等待的单个线程,如果有多个线程都在此同步锁对象上等待,则会任意选择其中某个线程进行唤醒操作,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。
3、notifyAll():唤醒在此同步锁对象上等待的所有线程,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。
- 线程优先级和守护线程
1、 java中的线程优先级的范围是1~10,默认的优先级是5。
每个线程默认的优先级都与创建它的父线程具有相同的优先级。
默认情况下,mian线程具有普通优先级。“高优先级线程”会优先于“低优先级线程”执行。
Thread提供了setPriority(int newPriority)和getPriority()方法来设置和返回线程优先级。
Thread类有3个静态常量:
MAX_PRIORITY = 10MIN_PRIORITY = 1
NORM_PRIORITY = 5
2、 java 中有两种线程:用户线程和守护线程。
可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。
用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务。
需要注意的是:Java虚拟机在“用户线程”都结束后会后退出。
将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。在使用守护线程时需要注意一下几点:(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会抛出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。
(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
以上是 初识Java多线程 的全部内容, 来源链接: utcz.com/z/511085.html