多线程编程
线程的概念
多线程简介
进程和线程的概念
在计算机中一个任务就是一个进程(例如:浏览器、视频播放器、音频播放器、Word),在某些进程内部还需要同时执行多个子任务(例如在Word进程中可能会执行打字、拼写检查、打印等子任务 ==>线程)
进程和线程的区别
1)一个进程可以包含一个或多个线程(至少一个线程)
2)线程是操作系统调度的最小任务单位
3)如何调度线程完全由操作系统决定
实现多任务的方法:
1)多进程模式(每个进程只有一个线程)
2)多线程模式(一个进程多个线程)
3)多进程+多线程模式(复杂度高)
多进程 VS 多线程
1)创建进程比创建线程开销大
2)进程间通信比线程间通信慢
3)多进程稳定性比线程高
Java语言内置多线程支持
1)一个Java程序实际上是一个JVM进程
2)JVM用一个主线程来执行main()方法
3)在main()方法中又可以启动多个线程
多线程编程的特点
1)多线程需要读写共享数据
2)多线程经常需要同步
3)多线程编程的复杂度高,调试更困难
Java多线程编程的特点
1)多线程模型是Java程序最基本的并发模型
2)网络、数据库、Web都依赖多线程模型
3)必须掌握Java多线程编程才能继续深入学习
创建新线程
继承Thread
1)继承Thread
2)覆写run()方法
3)创建MyThread实例
4)调用start()启动线程
public class MyThread extends Thread{
public void run(){
System.out.println()
}
}
public class Main{
public static void main(String[] args){
Thread t = new MyThread();
t.start();
}
}
实现Runnable接口
如果一个类已经从某个类派生,无法从Thread继承,我们就可以实现Runnable接口来创建线程
1)实现Runnable接口
2)覆写run()方法
3)在main()方法中创建Runnable实例
4)创建Thread实例并传入Runnable
5)调用start()启动线程
public class MyThread implements Runnable{
public void run(){
System.out.println();
}
}
public class Main{
public static void main(String[] args){
Runnable r = new MyThread();
Thread t = new Thread(r);
t.start();
}
}
直接调用run()方法是无效的,必须调用start()方法才能启动新线程
// 查看Thread源码
public class Thread implements Runnable{
public synchronized void start(){
start0();
}
private native void start0();//JVM 内部的C语言实现的
...
}
Thread.sleep()可以把当前线程暂停一段时间
线程的优先级:
1)可以对线程设定优先级——Thread.setPriority(int n) // 1~10 默认值5
2)优先级高的线程被操作系统调度的优先级高
3)不能通过设置优先级来确保功能的执行顺序
线程状态
一个线程对象只能调用一次start(),线程的执行代码是run()方法,线程调度由操作系统决定,程序本身无法决定
Java线程对象Thread的状态包括:
1)New(新创建)
2)Runnable(运行中)
3)Blocked(被阻塞)
4)Waiting(等待)
5)Timed Waiting(计时等待)
6)Terminated(已终止)
线程终止的原因
1)run()方法执行到return语句返回(线程正常终止)
2)因为未捕获的异常导致线程终止(线程意外终止)
3)对某个线程的Thread实例调用stop()方法强制终止(不推荐)
通过对另一个线程对象调用join()方法可以等待其执行结束
public class MyThread extends Thread{
public void run(){
System.out.println("Hello");
}
}
public class Main{
public static void main(String[] args){
Thread t = new MyThread();
System.out.println("Start");
t.start();
t.join(); // 等待线程t执行结束
System.out.println("End");
}
}
// 输出结果:Start Hello End
可以指定等待时间,超过时间线程仍然没有结束就不再等待
对已经运行结束的线程调用join()方法会立即返回
join练习
创建3个线程,每个线程先打印
Hello, xxx!
然后等待1秒,再打印
Goodbye, xxx!
输出例如:
START
Hello, Bob!
Hello, Alice!
Hello, Tom!
(等待约1秒)
Goodbye, Bob!
Goodbye, Alice!
Goodbye, Tom!
END
public class HelloThread extends Thread{
String name;
public HelloThread(String name){
this.name = name;
}
@Override
public void run(){
System.out.println("Hello, " + name + "!");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Goodbye, " + name + "!");
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
List<Thread> thList = new ArrayList<>();
for(String name : Arrays.asList("Bob", "Alice", "Tom")){
thList.add(new HelloThread(name));
}
System.out.println("START");
for(Thread t : thList){
t.start();
}
for(Thread t : thList){
t.join();
}
System.out.println("END");
}
}
中断线程
如果线程需要执行一个长时间任务,就可能需要中断线程。中断线程就是其他线程给该线程发一个信号,该线程收到信号后结束执行run()方法。
中断线程需要通过检测isInterrupted()标志获取当前线程是否已中断,而其他线程通过调用interrupt()方法可以中断一个线程。
class HelloThread extends Thread{
public void run(){
while(!isInterrupted()){
// doSomething...
}
}
}
public class Main{
public static void main(String[] args){
Thread t = HelloThread();
t.start();
Thread.sleep(1000);
t.interrupt(); // 中断t线程
}
}
如果线程处于等待状态,该线程会捕获InterruptedException;捕获InterruptedException说明有其他线程对其调用了interrupt()方法,通常情况下该线程应该立即结束运行。
还可以通过设置running标志位,例如
class HelloThread extends Thread{
public volatile boolean running = true;
public void run(){
while(running){
// doSomething...
}
}
}
public class Main{
public static void main(String[] args){
Thread t = new HelloThread();
t.start();
Thread.sleep(1000);
t.running = false;
}
}
// 当run()方法中的while循环检测到running值为false时,就会终止调循环,从而结束run()方法。
volatile
线程间共享变量需要使用volatile关键字标记,确保线程能读取到更新后的变量值

在Java内存模型中,变量存储在主内存中。但是当线程访问该变量时,会先复制一个变量副本,并且保存在自己的工作内存中。如果线程修改变量的值,虚拟机会在某一时刻把该值修改到主内存,但是这个时间是不确定的。这样就会导致,一个线程修改了变量的值,而另一个线程读取到的值还是之前的值。
volatile关键字的目的是告诉虚拟机
1)每次访问变量时,总是获取主内存的最新值
2)每次修改变量后,立刻回写到主内存
因此volatile关键字解决了共享变量在线程间的可见性问题,当一个线程修改了某个变量的值,其他线程能够立刻获取到修改后的值
守护线程
Java程序入口就是由JVM启动main线程,main线程又可以启动其他线程,当所有线程都运行结束时,JVM退出,进程结束。
但是有一种线程目的就是无限循环,例如:定时任务
Q:如果某个线程不结束,JVM进程就无法结束,那么由谁来结束该线程?
A:守护线程
守护线程(Daemon)
守护线程是为其他线程服务的线程,所有非守护线程都执行完毕后,虚拟机退出
守护线程特点
不能持有资源(如打开文件等)
创建守护线程:setDaemon(true)
class TimerThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
System.out.println("Main start");
TimerThread t = new TimerThread();
t.setDaemon(true); // 将线程t设置为守护线程
t.start();
Thread.sleep(5000);
System.out.println("Main end");
}
}
//Main start
//22:38:02
//22:38:03
//22:38:04
//22:38:05
//22:38:06
//Main end
// t设置为守护线程后,主线程结束,t线程也会随之结束
线程同步
线程同步
多线程同时修改变量,会造成逻辑错误,例如
// 我们启动两个线程分别对同一个变量count加减10000次,希望输出结果是0
class AddThread extends Thread{
public void run(){
for(int i=0; i<Main.LOOP; i++){
Main.count += 1;
}
}
}
class DecThread extends Thread{
public void run(){
for(int i=0; i<Main.LOOP; i++){
Main.count -= 1;
}
}
}
public class Main {
final static int LOOP = 10000;
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new AddThread();
Thread t2 = new DecThread();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}
// 实际输出结果是不确定的
对共享变量进行写入时,必须保证是原子操作(是指不能被中断的一个或一系列操作)
对于n = n + 1,它的字节码指令是
iload
iadd
istore
是三条指令,并不是原子操作的
要实现原子操作,就需要对 iload 之前进行加锁和 istore 之后进行解锁
Java通过synchronized对一个对象进行加锁
synchronized(lock){
n = n + 1;
}

synchronized保证了代码块在任意时刻最多只有一个线程能执行,synchronized的问题是性能下降,加锁和解锁都会损耗性能
如何使用synchronized:
1)找出修改共享变量的线程代码块
2)选择一个实例作为锁
3)使用synchronized(lockObject){ ... }
同步的本质就是给指定对象加锁,注意加锁对象必须是同一个实例
public class Main {
final static int LOOP = 10000;
public static int count = 0;
public static final Object LOCK = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new AddThread();
Thread t2 = new DecThread();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}
class AddThread extends Thread{
public void run(){
for(int i=0; i<Main.LOOP; i++){
synchronized(Main.LOCK){
Main.count += 1;
}


11万+

被折叠的 条评论
为什么被折叠?



