目录
1、继承thread类,重写run方法 2、实现runnable接口,重写run方法
二、多线程下,需要上锁来保护公用资源(synchronized;lock)
一、创建线程几种方法:
1、继承thread类,重写run方法 2、实现runnable接口,重写run方法
//继承Thread类
public class MyThread extends Thread{
public void run(){
//重写run方法
}
}
//实现Runnable接口
public class MyThread2 implements Runnable {
public void run(){
//重写run方法
}
}
public class Main {
public static void main(String[] args){
new MyThread().start();//创建并启动线程
Thread t2 = new Thread(MyThread2);
t2.setName("线程2");
t2.start();
new Thread(MyThread2).start();
new Thread(MyThread2,"线程2").start();
}
}
Java 是单继承编程语言,继承是十分宝贵的,所以一般不使用这种方法:继承Thread类
3、使用匿名类 或 lamda表达式 让代码更简洁
//使用匿名类
new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
share.incr();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
//lamda表达式
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t上完自习,离开教室");
}, "AA").start();
4、Callable 接口
Runnable 缺少的一项功能是,当线程终止时(即 run()完成时),我们无法使线程返回结果。为了支持此功能,Java 中提供了 Callable 接口,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。
需要使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值。
FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。
package com.atguigu.java2;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//1.创建一个实现Callable的实现类
class NumThread implements Callable {
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
//把100以内的偶数相加
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//————————————————————————————————————————————————————————
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"执行Runnable");
}).start();
FutureTask<String> task = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + "使用Callable接口");
return "Callable接口返回值";
});
new Thread(task).start();
System.out.println("Callable返回值:" + task.get());
}
}
5、使用线程池创建线程
二、多线程下,需要上锁来保护公用资源(synchronized;lock)
- synchronized是java关键字,内置,而lock不是内置,是一个类,可以实现同步访问且比 synchronized中的方法更加丰富,并且可以支持多个相关联的对象 Condition
- synchronized不会手动释放锁,而lock需手动释放锁(不解锁会出现死锁,需要在 finally 块中释放锁)
- lock等待锁的线程会相应中断,而synchronized不会相应,只会一直等待
- 通 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到
- Lock 可以提高多个线程进行读操作的效率(当多个线程竞争的时候)锁会出现死锁,需要在 finally 块中释放锁)
关键字 synchronized 与 wait()/notify() 这两个方法一起使用可以实现等待/通知模式
用 notify()通知时,JVM 会随机唤醒某个等待的线程
public synchronized void incr() throws InterruptedException {
// 操作:判断、干活、通知
if (number != 0) {
this.wait();
}
number++;
System.out.print(Thread.currentThread().getName()+"::"+number);
// 唤醒其他线程,注意这里的通知是随机的,就是只能通知全部
this.notifyAll();
}
Lock 实现可重入锁
使用 Condition 类可以进行选择性通知: Condition有以下两个方法await()和signal()
// 创建可重入锁
private final ReentrantLock lock = new ReentrantLock();
//Lock 接口中的 newContition() 方法返回 Condition 对象,Condition 类也可以实现等待/通知模式
private Condition condition = lock.newCondition();
try {
//上锁
lock.lock();
//功能操作:————————————————————————————————————————
//使用 Condition 类可以进行选择性通知: Condition有以下两个方法await()和signal()
//判断循环await()、干活、通知signal()
// 判断
while (number != 0) {
condition.await();
}
// 干活
number++;
System.out.print(Thread.currentThread().getName() + "::" + number + "--->");
// 通知
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
//——————————————————————————————————————————
}finally {
//解锁
lock.unlock();
}
案列:启动三个线程,按照如下要求: AA打印5此,BB打印10次,CC打印15次,一共进行10轮
由于需要选择性通知唤醒进程,因此使用lock
class Share{
private int flag = 1;
private Lock lock = new ReentrantLock();
// 创建三个Comdition对象,为了定向唤醒相乘
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
Condition c3 = lock.newCondition();
public void Aprint(int loop) {
//上锁
lock.lock();
try{
// 判断
while(flag!=1) {
c1.await();
}
// 干活
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + " ::本次第" + i + "次打印,是第" + loop+ "次循环");
}
flag = 2; //修改标志位,定向唤醒 线程b
// 唤醒
c2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock();
}
}
public void Bprint(int loop) {
//上锁
lock.lock();
try{
// 判断
while(flag!=2) {
c2.await();
}
// 干活
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + " ::本次第" + i + "次打印,是第" + loop+ "次循环");
}
flag = 3; //修改标志位,定向唤醒 线程b
// 唤醒
c3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock();
}
}
public void Cprint(int loop) {
//上锁
lock.lock();
try{
// 判断,这个唤醒最好在while里头执行,防止出现虚假唤醒
//由于 wait() 方法使线程在哪里睡就在哪里醒,所以接下来C在执行时不会再通过 if 判断
while(flag!=3) {
c3.await();
}
// 干活
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName() + " ::本次第" + i + "次打印,是第" + loop+ "次循环");
}
flag = 1; //修改标志位,定向唤醒 线程b
// 唤醒
c1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock();
}
}
}
public class CustomInterThreadCommunication {
public static void main(String[] args) {
Share share = new Share();
//在主函数里创建线程:
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
share.Aprint(i);
}
}
},"A").start();
// new Thread(r, "A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
share.Bprint(i);
}
}
},"B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
share.Cprint(i);
}
}
},"C").start();
}
}