多线程
1.1.程序
程序:是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象
1.2.进程
进程:是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
- 程序是静态的,进程是动态的
- 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
1.3.线程
进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的。
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小 。
- 一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。
2.线程的使用
2.1.继承Thread类
构造器
➢Thread():创建新的Thread对象
➢Thread(String threadname):创建线程并指定线程实例名
➢Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
➢Thread(Runnable target, String name):创建新的Thread对象
//线程类,继承Thread类来实现,重写父类run方法
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " MyThread " + i);
}
}
}
public class MyThreadTest {
public static void main(String[] args) {
System.out.println("主线程开始。。。");
Thread t1 = new MyThread();
t1.setName("t1");
t1.start();
Thread t2 = new MyThread();
t2.setName("t2");
t2.start();
//只要有一个线程存活,就会等待
while(Thread.activeCount()>1) {
Thread.yield();
}
System.out.println("主线程结束。。。");
}
}
定义子类继承Thread类。
子类中重写Thread类中的run方法。
创建Thread子类对象,即创建了线程对象。
调用线程对象start方法:启动线程,调用run方法。
2.2.实现Runnable接口
- 定义子类,实现Runnable接口。
- 子类中重写Runnable接口中的run方法。
- 通过Thread类含参构造器创建线程对象。
- 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
- 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " MyRunnable " + i);
}
}
}
public class MyRunnableTest {
public static void main(String[] args) {
System.out.println("主线程开始");
//启动子线程1
new Thread(new MyRunnable(),"r1").start();
//启动子线程2
new Thread(new MyRunnable(),"r2").start();
//只要有一个线程存活,就会等待
while(Thread.activeCount()>1) {
Thread.yield();
}
System.out.println("主线程结束");
}
}
2.3.实现Callable接口
与使用Runnable相比,Callable功能更强大些
➢相比run()方法,可以有返回值
➢方法可以抛出异常
➢支持泛型的返回值
➢需要借助FutureTask类, 比如获取返回结果
Future接口
➢可以对具体Runnable、Callable任 务的执行结果进行取消、查询是否完成、获取结果等。
➢FutrueTask是Futrue接 口的唯一的实现类
➢FutureTask 同时实现了Runnable,Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("请求接口。。。");
return "MyCallable call()";
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyCallableTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask ft = new FutureTask(new MyCallable());
new Thread(ft).start();
//是否退出
System.out.println("ft.isCancelled() " + ft.isCancelled());
//是否完成
System.out.println("ft.isDone() " + ft.isDone());
//获取返回值
System.out.println(ft.get());
//线程退出
ft.cancel(true);
}
}
3.线程同步
线程同步用来解决线程安全、线程冲突问题。
问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
3.1.Synchronized的使用方法
public class SyncAcount {
private int num = 100;
private boolean flag = true;
// 不安全的方法
public void add(int n) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = num + n;
}
public void sub(int n) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = num - n;
}
// 安全的方法,有同步锁
public synchronized void syncAdd(int n) throws InterruptedException {
Thread.sleep(50);
while (!flag) {
// 标志位不为真则等待
this.wait();
}
num = num + n;
flag = false;
// 唤醒等待的线程
this.notify();
System.out.println("syncAdd " + n);
}
// 安全的方法,有同步锁
public synchronized void syncSub(int n) throws InterruptedException {
Thread.sleep(50);
while (flag) {
// 标志位为真则等待
this.wait();
}
num = num - n;
flag = true;
// 唤醒等待的线程
this.notify();
System.out.println("syncSub " + n);
}
public int getNum() {
return num;
}
}
public class SyncAcountTest {
public static void main(String[] args) {
SyncAcount sa = new SyncAcount();
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
//不安全的调用方法
// sa.add(10);
// sa.sub(10);
//安全的调用方式
try {
sa.syncAdd(10);
sa.syncSub(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
while(Thread.activeCount()>1) {
Thread.yield();
}
System.out.println(sa.getNum());
}
}
3.2.ReentrantLock使用方法
●从JDK 5.0开始,Java提供了更强大的线程同步机制,通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
●java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
●ReentrantLock 类实现了Lock ,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
public static void main(String[] args) {
Lock2 1 = new Lock2();
for(int i=0;i < 10;i++){
new Thread(new Runnable() {
@Override
public void run() {
1.add(10);
},"Thread"+i).start();
//主线程让出CPU时间
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(1.acount);
}
static class Lock2 {
private int acount = 100;
private final ReentrantLock lock = new ReentrantLock();
public void add(int num) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try{
acount = acount + num;
System.out.println(Thread.currentThread().getName() +":"+ acount);
} finally{
lock.unlock();
}
}
}