目录
一.概述
进程:正在运行的程序,具有一定的独立功能。
线程:一个进程中,可能包含多个顺序执行流,每个顺序执行流就是一个线程。
简单理解:软件中互相独立,可以同时运行的功能。
并发:单个处理器,同一时刻只能有一条指令执行,但多个指令被快速替换执行,到达"同时执行"的效果。
并行:多个处理器,同时执行多条指令
多线程的三种实现方式
1.继承Thread类
Thread代表线程,所有线程对象必须是Thread类或者子类的实例 。
//1.定义一个Thread子类
class ThreadDemo extends Thread{
//2.重写run方法,run方法被称为线程执行体
@Override
public void run(){
for(int i=0;i<100;i++){
System.out.println("线程"+getName());
}
}
}
public class Main {
public static void main(String[] args) {
//3.创建Thread子类实例,即创建线程对象
ThreadDemo thread1=new ThreadDemo();
ThreadDemo thread2=new ThreadDemo();
thread1.setName("1");
thread2.setName("2");
//3.调用线程对象的start()方法,来启动该线程
thread1.start();
thread2.start();
}
}
步骤:
- 定义一个Thread子类
- 重写run方法,run方法被称为线程执行体
- 创建Thread子类实例,即创建线程对象
- 调用线程对象的start()方法,来启动该线程
#使用继承Thread类的方法创建线程,多个线程之间无法共享线程类的实例变量
2.实现Runnable接口
//1.定义Runnable接口的实现类
public class RunnableDemo implements Runnable {
//2.重写run方法
private int i=0;//多线程共享该实例变量
@Override
public void run(){
for(;i<100;i++){
Thread thread=Thread.currentThread();//获取当前线程对象
System.out.println("线程"+thread.getName()+" i="+i);
}
}
public static void main(String[] args){
//3.创建Runnable实现类的实例
RunnableDemo rd=new RunnableDemo();
//4.以Runnable实现类的实例作为Thread的target来创建Thread的对象(真正的线程对象)
Thread thread1=new Thread(rd,"1");
thread1.start();
Thread thread2=new Thread(rd,"2");
thread2.start();
}
}
步骤:
- 定义Runnable接口的实现类,重写run方法
- 创建Runnable实现类的实例。
- 以该实例作为Thread的target来创建Thread的对象(真正的线程对象)
#多个线程共享一个target,因此多个线程共享一个target类的实例变量
#通过Thread.currentThread();方法来获取当前线程对象
3.利用Callable接口和Future接口
Callable接口提供了一个call()方法作为线程执行体,它可以有返回值,可以声明抛出异常。
但是它不是Runnable的子接口,不能直接作为target,况且call()方法作为线程执行体,如何获取返回值?
Future接口代表call()方法的返回值,并且Future接口的实现类FutureTask同时实现了Runnable接口。
Future接口中的公共方法控制与其关联的Callable任务
- boolean cancel(bealean mayInterruptIfRunning);//试图取消关联任务
- V get();//返回任务中call的返回值,调用该方法会导致主线程阻塞,直到call方法结束。
- V get(long timeout,TimeUnit unit);//返回任务的返回值。程序最多阻塞timeout和unit指定的时间,时间过去仍然没有返回值,抛出TimeoutException异常
- boolean isCancelled();//任务是否在正常完成前被取消
- bool isDone();//如果任务完成,返回true
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
//1.创建Callable接口的实现类,并实现call方法
class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception{
int sum=0;
for(int i=1;i<=100;i++){
sum+=i;
}
return sum;
}
}
public class callable {
public static void main(String[] args) throws Exception{
//2.创建Callable的实现类的实例,并且使用FutureTask来包装该实例
MyCallable mc=new MyCallable();
FutureTask<Integer> ft=new FutureTask<>(mc);
//将FutureTask对象作为target,创建并启动线程
Thread thread=new Thread(ft);
thread.start();
//获取线程的返回值
System.out.println(ft.get());
}
}
#Callable接口有泛型限制,泛型形参必须和call()方法返回值一致
#Callable接口是函数式接口,可以使用Lambda表达式
#call方法允许抛出异常
三者对比
- 继承Thread编写简单,通过this可以直接获取当前线程。但单继承扩展性差
- 后俩者编程稍复杂,但线程类可以继承其他的类,同时共用一个target
Thread常见成员方法
守护线程:当其他非守护线程结束,守护线程会陆续结束。
出让线程:暂停线程,让出cpu。
插入线程:将指定线程插入到当前线程之前。
线程的生命周期
阻塞:
- sleep()方法
- 阻塞式IO方法
- 线程试图获得一个同步监视器,但该同步监视器被其他线程占用
- 正在等待某个通知(notify)
- 程序调用了该线程的suspend()方法,将其挂起。容易死锁
死亡:
- run()或call()方法执行完毕
- 抛出一个未捕获的异常
- 直接调用该线程的stop();方法;容易死锁
线程安全
当使用多个线程来访问同一个数据时,容易出现线程安全问题。
使用同步监视器解决
1.同步代码块
//1.定义Runnable接口的实现类
public class RunnableDemo implements Runnable {
//2.重写run方法
private int i=0;//多线程共享该实例变量
static Object obj=new Object();
@Override
public void run(){
for(;i<100;i++){
synchronized (obj){
Thread thread=Thread.currentThread();//获取当前线程对象
System.out.println("线程"+thread.getName()+" i="+i);
}
}
}
public static void main(String[] args){
//3.创建Runnable实现类的实例
RunnableDemo rd=new RunnableDemo();
//4.以Runnable实现类的实例作为Thread的target来创建Thread的对象(真正的线程对象)
Thread thread1=new Thread(rd,"1");
thread1.start();
Thread thread2=new Thread(rd,"2");
thread2.start();
}
}
线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。
synchronized()内就是同步监视器
通常推荐使用可能被并发访问的共享资源充当同步监视器
#细节:1.synchronized加在循环外和循环内是不一样的 2.同步监视器应当唯一
2.同步方法
使用synchronized关键字修饰方法。锁住方法内所有的代码,同步监视器不能自己指定。
synchronized修饰的实例方法,无需显示指定同步监视器,同步监视器就是this。
如果是static方法,就是当前类的字节码文件对象。
//1.定义Runnable接口的实现类
public class RunnableDemo implements Runnable {
//2.重写run方法
private int i=0;//多线程共享该实例变量
static Object obj=new Object();
@Override
public void run(){
try {
for(;i<100;i++){
synchronized (obj){
Thread thread=Thread.currentThread();//获取当前线程对象
System.out.println("线程"+thread.getName()+" i="+i);
thread.sleep(10);
}
}
method();
}catch (Exception e){
}
}
synchronized void method() throws Exception{
for(;i<200;i++){
Thread thread=Thread.currentThread();//获取当前线程对象
System.out.println("线程"+thread.getName()+" i="+i);
thread.sleep(10);
}
}
public static void main(String[] args){
//3.创建Runnable实现类的实例
RunnableDemo rd=new RunnableDemo();
//4.以Runnable实现类的实例作为Thread的target来创建Thread的对象(真正的线程对象)
Thread thread1=new Thread(rd,"1");
thread1.start();
Thread thread2=new Thread(rd,"2");
thread2.start();
}
}
3.同步锁Lock
Lock为接口,ReentrantLock(可重入锁)为其常用的实现类。
ReentrantLock对象可以显示加锁和释放锁。
#为防止死锁,可使用finally释放锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//1.定义一个Thread子类
class ThreadDemo extends Thread{
//2.重写run方法,run方法被称为线程执行体
static Lock lk=new ReentrantLock();
static int i=0;
@Override
public void run() {
for (; i < 100; i++) {
try {
lk.lock();
System.out.println("线程" + getName() + " " + i);
} catch (Exception e) {
} finally{
lk.unlock();
}
}
}
}
public class Main {
public static void main(String[] args) {
//3.创建Thread子类实例,即创建线程对象
ThreadDemo thread1=new ThreadDemo();
ThreadDemo thread2=new ThreadDemo();
thread1.setName("1");
thread2.setName("2");
//3.调用线程对象的start()方法,来启动该线程
thread1.start();
thread2.start();
}
}
死锁 :多个线程互相等待对方释放同步监视器,就会发生死锁。尤其是在线程中出现多个同步监视器的情况下。
线程通信
1.传统的线程通信
借助Object提供的wait(),notify(),notifyAll()三个方法实现。这三个方法必须由同步监视器对象来调用
- 同步方法的监视器就是this,所以可以在同步方法中直接调用
- 同步代码块,通过括号里面的监视器调用
public class touch {
public static void main(String[] args) {
Thread tc=new Cook();
Thread tf=new foodie();
tc.start();
tf.start();
}
}
class Desk {
public static int Foodflag=0;
public static int count=10;
//同步监视器
public static Object lock=new Object();
}
class foodie extends Thread{
public void run(){
while(true){
synchronized (Desk.lock){
if(Desk.count==0){
break;
}else{
if(Desk.Foodflag==0){
try {
//没有食物,就等待
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
Desk.count--;
System.out.println("吃掉这个食物,还能再吃"+Desk.count);
//吃掉后,唤醒厨师
Desk.lock.notifyAll();
//
Desk.Foodflag=0;
}
}
}
}
}
}
class Cook extends Thread{
public void run(){
while(true){
synchronized (Desk.lock){
if(Desk.count==0){
break;
}else{
if(Desk.Foodflag==1){
try {
//有食物,就等待
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
System.out.println("厨师制作食物");
Desk.Foodflag=1;
//做好后,通知食客
Desk.lock.notifyAll();
}
}
}
}
}
}
2.阻塞队列(BlockingQueue)
接口,其实现类有ArrayBlockingQueue和LinkedBlockingQueue
两个线程必须使用同一个阻塞队列
public class BlockingQueueDemo {
public static void main(String[] args) {
ArrayBlockingQueue<String> queue=new ArrayBlockingQueue<>(1);
cook c=new cook(queue);
Foodie f=new Foodie(queue);
c.start();
f.start();
}
}
class Foodie extends Thread{
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> que){
this.queue=que;
}
@Override
public void run(){
while (true){
//不断从阻塞队列中获取
try{
String food= queue.take();
System.out.println(food);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
class cook extends Thread{
ArrayBlockingQueue<String> queue;
public cook(ArrayBlockingQueue<String> que){
this.queue=que;
}
@Override
public void run(){
while (true){
//不断从阻塞队列中获取
try{
queue.put("noodles");
System.out.println("制作面条");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
插入元素:put 取出对首元素:take 两个方法都会阻塞线程
线程池
Executors:线程池工具类,通过调用方法返回不同类型的线程池对象
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Threadpool {
public static void main(String[] args) throws Exception{
ExecutorService pool= Executors.newFixedThreadPool(3);
Runnable target=()->{
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
};
pool.submit(target);
pool.submit(target);
pool.submit(target);
pool.shutdown();
}
}
自定义线程池
插图来源于黑马程序员