文章目录
多线程简介
- 进程
进程是系统进行资源分配和调用的独立单位
- 线程
在同一个进程中又可以同时执行多个任务,每一个任务可以看做是一个线程
线程是程序执行的单元、执行路径,也是程序使用 CPU 的基本单元 - 多线程及其意义
多线程不能提高程序的执行速度,但是可以使应用程序在抢占 CPU 资源时占得上风,但无法保证,线程的执行具有随机性
- 并行和并发
并行:逻辑上同时发生,指在某一个时间内同时运行多个程序
并发:物理上同时发生,值在某一个时间点同时运行多个程序 - Java 的运行原理
由 java 命令启动 JVM,相当于启动了一个进程,该进程创建一个主线程去调用 main 方法,垃圾回收线程也从开始就已经启动,否则很容易造成内存溢出
创建多线程方法
- 继承 Thread
自定义类继承 Thread,并且重写 Thread 类的 run 方法(其中放入想要被线程执行的方法),创建对象,启动线程
- 实现 Runnable 接口
自定义类实现 Runnable 接口,重写 run 方法,创建该类对象,并把该对象作为构造 Thread 时的参数传入,用于解决单继承的局限性,也可以实现多个线程操作同一份资源,一般使用该方法进行多线程操作
- 实现 Callable 接口
该方案的优点是可以抛出异常,也有返回值,该返回值由 Future<?> f 接收,并通过其 get 方法获得
与实现 Runnable 接口的方案类似,不同的是需要重写 call 方法
此外 Callable 是带泛型的接口,泛型的类型绝定了 call 方法的返回值类型
该方法不可搭配 Thread 使用,只能用在线程池中
构造方法
- public Thread()
- public Thread(Runnable target)
- public Thread(Runnable target, String name)
- public Thread(String name)
- public Thread(ThreadGroup group, Runnable target)
- public Thread(ThreadGroup group, Runnable target, String name)
- public Thread(ThreadGroup group, String name)
成员方法
- public String getName()
获取线程名称
- public void setName(String name)
设置线程名称
- public static Thread currentThread()
返回正在执行的线程对象
- public void setPriority(int newPriority)
设置线程优先级
- public static void sleep(long millis)
设置线程休眠
- public static void sleep(long millis, int nanos)
- public void join()
等待这个线程死亡,需要抛出异常 InterruptedException
- public void join(long millis)
等待这个线程死亡最多…毫秒
- public void join(long millis, int nanos)
- public static void yield()
表示当前线程愿意让出当前使用的处理器,但仍不是绝对的
- public void setDaemon(boolean on)
将线程标记为守护线程或者用户线程,当运行的线程都是守护线程时,Java 虚拟机退出,该方法必须在启动线程之前调用
- public void interrupt()
中断线程
多线程实现案例 1
创建 Thread 子类,重写其 run 方法,创建多个线程对象,调用对象的 start 方法
package javaPackage;
class MyThread extends Thread {
@Override
public void run() {
//一般来说,被线程执行的方法肯定是比较耗时的
for (int i = 0; i < 200; i++) {
System.out.println(i + this.getName());
}
}
}
public class JavaClass {
public static void main(String[] args) {
//Java不能直接调用系统功能,必须通过调用C/C++实现的方法来实现多线程程序
//创建线程对象
MyThread mt = new MyThread();
//设置线程名
mt.setName("hello");
//启动线程
//mt.run();
//mt.run();
//直接调用run方法是单线程
//start方法首先启动了线程,然后再由jvm去调用该线程的run方法
mt.start();
//mt.start();
//单个线程对象调用两次start方法会出现IllegalThreadStateException
//如果要多线程执行,则需要创建多个线程对象
MyThread mts = new MyThread();
mts.start();
}
}
多线程实现案例 2
实现 Runnable 接口(重写 run 方法),创建 Thread 对象(传入实现类对象),调用 start 启动线程
package javaPackage;
class RunnableDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//当前类不是继承自Thread,无法使用Thread方法,但是可以通过Thread.currentThread()间接使用
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class JavaClass {
public static void main(String[] args) {
RunnableDemo rd = new RunnableDemo();
Thread t1 = new Thread(rd, "张三");
Thread t2 = new Thread(rd, "李四");
t1.start();
t2.start();
}
}
线程调度
- 分时调度
轮流获得 CPU 使用权,平均分配每个线程占用 CPU 的时间
- 抢占式调度
优先让优先级高的线程使用 CPU,优先级相同的情况下随机选择
线程优先级
- 线程优先级介于 1~10 之间,默认为 5
- 通过 setPriority 方法设置线程优先级
- 线程优先级高不一定优先执行,仍具有偶然性,在运行次数较多时优先效果比较明显
package javaPackage;
class ThreadPriority extends Thread {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(i + this.getName());
}
}
}
public class JavaClass {
public static void main(String[] args) {
ThreadPriority tp1=new ThreadPriority();
ThreadPriority tp2=new ThreadPriority();
ThreadPriority tp3=new ThreadPriority();
tp1.setName("张三");
tp2.setName("李四");
tp3.setName("王五");
tp1.setPriority(8);
tp1.start();
tp2.start();
tp3.start();
}
}
线程控制
休眠线程
使用 sleep 函数实现线程休眠
传入一个参数为毫秒,传入两个参数为毫秒和纳秒
package javaPackage;
import java.util.Date;
class ThreadSleep extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + new Date());
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class JavaClass {
public static void main(String[] args) {
ThreadSleep ts1 = new ThreadSleep();
ThreadSleep ts2 = new ThreadSleep();
ThreadSleep ts3 = new ThreadSleep();
ts1.start();
ts2.start();
ts3.start();
}
}
加入线程
当且仅当某个线程执行完成之后,其余线程才可以执行
使用 join 方法实现
package javaPackage;
import java.util.Date;
class ThreadJion extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + new Date());
}
}
}
public class JavaClass {
public static void main(String[] args) throws InterruptedException {
ThreadJion ts1 = new ThreadJion();
ThreadJion ts2 = new ThreadJion();
ThreadJion ts3 = new ThreadJion();
ts1.start();
ts1.join();
ts2.start();
ts3.start();
}
}
礼让线程
使用 yield 方法实现线程的让出
可以让多个线程的执行更加和谐,但是不能靠它保证一个线程一次
package javaPackage;
class ThreadDemo extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + " " + i);
Thread.yield();
}
}
}
public class JavaClass {
public static void main(String[] args) {
ThreadDemo td1 = new ThreadDemo();
ThreadDemo td2 = new ThreadDemo();
ThreadDemo td3 = new ThreadDemo();
td1.start();
td2.start();
td3.start();
}
}
守护线程
设置当某线程结束执行时,该线程也停止执行(有延迟)
使用 setDaemon 方法实现
package javaPackage;
class ThreadDemo extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + " " + i);
}
}
}
public class JavaClass {
public static void main(String[] args) {
ThreadDemo td1 = new ThreadDemo();
ThreadDemo td2 = new ThreadDemo();
td1.setDaemon(true);
td2.setDaemon(true);
td1.start();
td2.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
中断线程
stop 方法已弃用,终止线程后不会抛出异常
interrupt 方法,终止线程会抛出异常 InterruptedException
package javaPackage;
class ThreadDemo extends Thread {
@Override
public void run() {
try{
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("线程被终止了");
}
}
}
public class JavaClass {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
td.start();
try{
Thread.sleep(3000);
//interrupt()方法终止线程并抛出InterruptedException异常,该异常被捕获后可继续执行catch中的内容--"线程被终止了"
td.interrupt();
//stop()方法终止线程不会继续执行catch中的内容--"线程被终止了"
//td.stop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程安全
- 当多线程操作一块共享数据时,可能因为线程抢占资源发生错误操作数据的情况,进而导致判断语句失效、顺序错乱等问题
- 解决该问题的方案为锁定多条语句操作共享数据的代码,使得任意时刻只能有一个线程执行
判断线程安全问题存在可能性的标准
- 是否是多线程环境
- 是否有共享数据
- 是否有多条语句操作共享数据
线程安全问题的解决方案:同步机制
格式为 synchronized(对象){需要同步的代码}
该方案的关键在于传入的对象上,该对象就像是一把锁,使用的必须是同一把锁,除此之外没有要求
同步会降低代码的执行效率
package javaPackage;
class Runnablelmpl implements Runnable {
private int num = 100;
private Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
//注意判断必须放在循环里面,如果是放在while括号内,或者达到条件退出循环会出现num的值出现负数的情况
if (num >= 0) {
System.out.println(num-- + " " + Thread.currentThread().getName());
}
else {
break;
}
}
}
}
}
public class JavaClass {
public static void main(String[] args) {
Runnablelmpl rl = new Runnablelmpl();
Thread t1 = new Thread(rl, "一号");
Thread t2 = new Thread(rl, "二号");
Thread t3 = new Thread(rl, "三号");
t1.start();
t2.start();
t3.start();
}
}
同步方法的格式及锁对象问题
- 同步方法只需将 synchronized 关键字用于修饰方法即可
- 同步方法无法自行设置锁对象,其锁对象就是 this
- 静态方法的锁对象是当前的类的字节码文件对象(Object.class)
Lock
Lock 接口提供了比 synchronized 方法更广泛的锁定操作
实现类
- ReentrantLock
- ReentrantReadWriteLock.ReadLock
- ReentrantReadWriteLock.WriteLock
成员方法
- public void lock()
获取锁,加锁
- public void unlock()
释放锁
package javaPackage;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Runnablelmpl implements Runnable {
private int tickets = 100;
//定义锁对象
private Lock lock =new ReentrantLock();
public int getTickets() {
return tickets;
}
@Override
public void run() {
while (true) {
//加锁
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "号窗口正在出售第" + (100 - (--tickets)) + "张票");
} else {
break;
}
//释放锁
lock.unlock();
}
}
}
public class JavaClass {
public static void main(String[] args) {
Runnablelmpl rl = new Runnablelmpl();
Thread t1 = new Thread(rl, "一");
Thread t2 = new Thread(rl, "二");
Thread t3 = new Thread(rl, "三");
t1.start();
t2.start();
t3.start();
}
}
死锁问题
两个或两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待的现象
如果出现了同步嵌套,就容易产生死锁问题
javapackage javaPackage;
class MyLock {
//创建两把锁对象
private static final Object objA = new Object();
private static final Object objB = new Object();
public static Object getObjA() {
return objA;
}
public static Object getObjB() {
return objB;
}
}
class DeadLock extends Thread {
private boolean flag;
public DeadLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (this.flag) {
synchronized (MyLock.getObjA()) {
System.out.println("if objA");
synchronized (MyLock.getObjB()) {
System.out.println("if objB");
}
}
} else {
synchronized (MyLock.getObjB()) {
System.out.println("else objB");
synchronized (MyLock.getObjA()) {
System.out.println("else objA");
}
}
}
}
}
public class JavaClass {
public static void main(String[] args) {
DeadLock dl1 = new DeadLock(true);
DeadLock dl2 = new DeadLock(false);
dl1.start();
dl2.start();
//最后的输出为if objA和else objB
//或者是else objB和if objA
}
}
部分线程安全与不安全类
- StringBuffer、Vector、Hashtable 都是线程安全的类
- 即使需要线程安全也不需要用 Vector,而是用 Collections 中设置的线程安全的类
package javaPackage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class JavaClass {
public static void main(String[] args) {
//线程不安全
List<String> list1 = new ArrayList<String>();
//线程安全
List<String> list2 = Collections.synchronizedList(new ArrayList<String>());
}
}
线程通信
以生产者-商品-消费者为例,生产和消费均为线程操作,如果消费者先抢到执行权,则无商品可消费,程序出错,因此必须让生产者先执行生产操作,再通知消费者进行消费,消费者消费完了商品再通知生产者生产商品
未通信导致错误案例
package javaPackage;
class Person {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
class SetThread implements Runnable {
private Person person;
public SetThread(Person p) {
this.person = p;
}
public Person getPerson() {
return person;
}
@Override
public void run() {
person.setAge(10);
person.setName("张三");
}
}
class GetThread implements Runnable {
private Person person;
public GetThread(Person p) {
this.person = p;
}
public Person getPerson() {
return person;
}
@Override
public void run() {
System.out.println("此人名为" + person.getName() + ",今年" + person.getAge() + "岁了");
}
}
public class JavaClass {
public static void main(String[] args) {
Person p = new Person();
SetThread st = new SetThread(p);
GetThread gt = new GetThread(p);
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
t1.start();
t2.start();
//如果t2抢到执行优先权,则打印出的名字和年龄都有可能变成默认值
}
}
等待唤醒机制
Object 类中提供了等待唤醒机制的方法
- public void notify()
唤醒在锁上等待的单个线程
- public void notifyAll()
唤醒在锁上等待的所有线程
- public void wait()
在其他线程调用此对象的 notify 或者 notifyAll 方法前,导致该线程等待
- public void wait(long timeout)
在未传形参的基础上添加最长等待时间
- public void wait(long timeout, int nanos)
等待唤醒机制实现案例
package javaPackage;
class Person {
private String name;
private int age;
//flag用于标记对象是否被初始化
private boolean flag = false;
public void setFlag(boolean flag) {
this.flag = flag;
}
public boolean isFlag() {
return flag;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
class SetThread implements Runnable {
private Person person;
private int select = 0;
public SetThread(Person p) {
this.person = p;
}
public Person getPerson() {
return person;
}
@Override
public void run() {
while (true) {
synchronized (person) {
if (person.isFlag()) {
try {
person.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (select % 2 == 0) {
person.setAge(10);
person.setName("张三");
} else {
person.setAge(34);
person.setName("李四");
}
select++;
//修改标记
person.setFlag(true);
//唤醒休眠线程
person.notify();
}
}
}
}
class GetThread implements Runnable {
private Person person;
public GetThread(Person p) {
this.person = p;
}
public Person getPerson() {
return person;
}
@Override
public void run() {
while (true) {
synchronized (person) {
if (!person.isFlag()) {
try {
person.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("此人名为" + person.getName() + ",今年" + person.getAge() + "岁了");
//修改标记
person.setFlag(false);
//唤醒休眠线程
person.notify();
}
}
}
}
public class JavaClass {
public static void main(String[] args) {
Person p = new Person();
SetThread st = new SetThread(p);
GetThread gt = new GetThread(p);
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
t1.start();
t2.start();
//如果t2抢到执行优先权,则打印出的名字和年龄都有可能变成默认值
}
}
ThreadGroup 线程组
线程组可以对一批线程进行分类管理
默认情况下所有线程都属于主线程组
可以通过 Thread 的 public Thread(ThreadGroup tg, Runnable r, String name)构造方法来将线程加入到指定的线程组中
package javaPackage;
class Runnablelmpl implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getThreadGroup().getName());
}
}
public class JavaClass {
public static void main(String[] args) {
method2();
}
private static void method1() {
Runnablelmpl rl = new Runnablelmpl();
Thread t1 = new Thread(rl, "一号");
Thread t2 = new Thread(rl, "二号");
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
System.out.println(tg1.getName() + " " + tg2.getName());
}
private static void method2() {
ThreadGroup tg = new ThreadGroup("新的线程组");
Runnablelmpl rl = new Runnablelmpl();
Thread t1 = new Thread(tg, rl, "线程一");
Thread t2 = new Thread(tg, rl, "线程二");
t1.start();
t2.start();
}
}
构造方法
- public ThreadGroup(String name)
- public ThreadGroup(ThreadGroup parent, String name)
成员方法
- public ThreadGroup getThreadGroup()
属于 Thread 的方法,返回线程所属的线程组
- public String getName()
- public void setMaxPriority(int pri)
设置最大优先级
- public void setDaemon(boolean daemon)
更改此线程组的守护程序状态
- public void interrupt()
线程池
一开始就产生一些线程,需要使用时取出线程,使用完毕后线程返回到池中,从而减少开启线程的资源消耗
使用 Executors 工厂类来产生线程池,其方法返回 ExecutorService 对象,该对象表示线程池,可以执行 Runnable 对象或者 Callable 对象代表的线程
Executors 成员方法
- public static ExecutorService newCachedThreadPool()
创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程
- public static ExecutorService newFixedThreadPool(int nThreads)
创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程
- public static ExecutorService newSingleThreadExecutor()
创建一个使用从无界队列运行的单个工作线程的执行程序
- public void shutdown()
启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务
ExecutorService 成员方法
- public Future<?> submit(Runnable task)
提交一个可运行的任务执行,并返回一个表示该任务的未来
- public Future submit(Callable task)
提交值返回任务以执行,并返回代表任务待处理结果的 Future
线程池实现案例一:Runnable
package javaPackage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class Runnablelmpl implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class JavaClass {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(new Runnablelmpl());
pool.submit(new Runnablelmpl());
//代码执行结束后不会退出程序,需要关闭线程池才会退出
pool.shutdown();
}
}
线程池实现案例二:Callable
package javaPackage;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class Callablelmpl implements Callable {
@Override
public Object call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
return null;
}
}
public class JavaClass {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(new Callablelmpl());
pool.submit(new Callablelmpl());
pool.shutdown();
}
}
线程池实现案例三:Callable 带泛型
package javaPackage;
import java.util.concurrent.*;
class Callablelmpl implements Callable<Integer> {
private int num;
public Callablelmpl(int num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < this.num; i++) {
sum += i;
}
return sum;
}
}
public class JavaClass {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> f1 = pool.submit(new Callablelmpl(13));
Future<Integer> f2 = pool.submit(new Callablelmpl(20));
System.out.println(f1.get());
System.out.println(f2.get());
pool.shutdown();
}
}
匿名内部类方式实现多线程案例
package javaPackage;
import java.util.concurrent.*;
public class JavaClass {
public static void main(String[] args) throws ExecutionException, InterruptedException {
method1();
method2();
methoc3();
}
private static void method1() {
//继承thread类实现多线程
new Thread() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}.start();
}
private static void method2() {
//实现Runnable接口实现多线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}).start();
}
private static void methoc3() throws ExecutionException, InterruptedException {
//实现Callable接口实现多线程
ExecutorService pool = Executors.newFixedThreadPool(1);
Future<Integer> f = pool.submit(new Callable<Integer>() {
private int num = 10;
@Override
public Integer call() throws Exception {
return num;
}
});
System.out.println(f.get());
}
}
Timer 定时器
定时器可以安排线程在后台线程中执行的任务,可以安排任务执行一次或者定期重复执行
构造方法
- public Timer()
- public Timer(boolean isDaemon)
其相关线程可以指定为 run as a daemon
- public Timer(String name)
- public Timer(String name, boolean isDaemon)
成员方法
- public void cancel()
终止此计时器,丢弃任何当前计划的任务
- public int purge()
从该计时器的任务队列中删除所有取消的任务
- public void schedule(TimerTask task, Date time)
在指定的时间安排指定的任务执行
- public void schedule(TimerTask task, Date firstTime, long period)
从指定的时间开始 ,对指定的任务执行重复的固定延迟执行
- public void schedule(TimerTask task, long delay)
在指定的延迟之后安排指定的任务执行
- public void schedule(TimerTask task, long delay, long period)
在指定的延迟之后开始,重新执行固定延迟执行的指定任务
- public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
从指定的时间开始,对指定的任务执行重复的固定速率执行
- public void scheduleAtFixedRate(TimerTask task, long delay, long period)
在指定的延迟之后开始 ,重新执行固定速率的指定任务
定时器的简单实现案例
package javaPackage;
import java.util.Timer;
import java.util.TimerTask;
class MyTask extends TimerTask {
private Timer time;
public MyTask() {
}
public MyTask(Timer t) {
this.time = t;
}
@Override
public void run() {
System.out.println("hello world");
//结束任务
time.cancel();
}
}
public class JavaClass {
public static void main(String[] args) {
Timer t = new Timer();
t.schedule(new MyTask(t), 3000);
}
}
来源:CSDN
作者:CTRL ALT DEL
链接:https://blog.csdn.net/weixin_44690437/article/details/104198431