Java多线程总结

本文详细介绍了Java中的多线程概念,包括进程与线程的区别,如何创建线程,线程安全问题及解决方案,如synchronized和死锁处理。此外,还探讨了线程的常用API,如线程优先级、Jion方法、停止线程的方法以及守护线程的设置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.进程与线程

进程就是一个程序(例如打开QQ),线程就是所答开QQ中同时打开多个聊天窗口,各个进程都是独立的,而线程则不同,他们有时是可以进行数据共享的。

多线程就是同时执行多个线程(其实还是处理器逐个去执行)

 

二.使用多线程

使用多线程最常用的方法有两种:一是直接继承Thread  二是实现runnable接口

继承Thread:

public class MyThread extends Thread {
	@Override
	public void run() {
		System.out.println("Test");
	}

    public static void main(String[] args) {
		MyThread mythread = new MyThread();
		mythread.start();
	}
}

此处要重写run方法,run方法中的程序就是在开启这条线程后要执行的代码块

实现runnable:

public class MyRunnable implements Runnable {
	@Override
	public void run() {
		System.out.println("Test");
	}

	public static void main(String[] args) {
		Runnable runnable=new MyRunnable();
		Thread thread=new Thread(runnable);
		thread.start();
	}

}

此处也是写run方法的内容

实现runnable方法和Thread方法本质是有很大的区别的:

通过阅读Thread的源码我们可知,它的源码就是在构造时要传入一个runnable的实例对象,传递后在.start时要检查是否传递了runnable对象,如果没有就不能执行。所以直接new Thread的方法就是需要复写这个方法。

如果在构造Thread时候没有传递Runnable,或者没有复写Run那边了或者没有复写Thread的run方法,该Thread不会调用任何东西。使用Thread方法是要写run方法,实际上是在重写他父类中的run方法。

Runnable不是线程,他只是一个传递的接口,只有Thread是线程。

在这里我们还需要注意在创建Thread时有两个需要传入的参数:ThreadGroup  stacksize

ThreadGroup:如果构造线程对象时,没有传入ThreadGroup,Thread会默认获取父线程中的ThreadGroup作为他自己的ThreadGroup,此时他和父线程在同一ThreadGroup中。

stacksize:构造Thread时传入stacksize表示当该线程占用的stack大小,没有指定stacksize的大小的话,默认为0, 0代表着会忽略该参数,该参数会被虚拟机中的函数调用,该参数在有些平台有效,在有些平台无效。

 

三.线程安全

在使用多线程时,大家一定遇到过这样的情况,当我们开多个线程去同时读取然后修改一个数时,会发现,他会读取到相同的数字,这是为什么?

public void test(){
    public static int i=0;  //1
    System.out.println(i);  //2
    i++;                    //3
}

假设我们想对这段代码实现多线程,我们期望的结果是让i的输出无重复的输出,但是这段代码在没有保护的情况下很容易出现让两个数字读了两次,这里我们用两个线程去演示这个操作,线程A和线程B,现在线程A走到2位置,输出了i=0的值,然后要执行i++;但是,再刚刚输出完后,处理器又让B线程开始工作,A就停在了2的末尾,线程B走到2位置,输出了i=0,这样就输出了两次i=0造成了线程不安全

出现这种情况,我们就要使用synchronized来让线程“安全”了!

有关synchronized,在这个代码块中程序变成了单线程,尽量范围小的去加锁

在这里介绍有关他的锁,常见的加锁方法:

Object LOCK=new Object();
//1
public void test1(){
    public static int i=0; 
    synchronized(LOCK){
        System.out.println(i);  
        i++;
    }             
}

//2
public synchronized void test2(){
    public static int i=0; 
    System.out.println(i);  
    i++;                    
}
//3
public static synchronized void test3(){
    public static int i=0; 
    System.out.println(i);  
    i++;                    
}

这三种方法,第一种是写一个代码块,在这个代码块中执行的代码是单线程执行的,第二种是普通方法加锁,在执行时没有给定锁(例如直接在函数处调用),默认就为this,第三种是对static方法加锁,对static方法给锁时给的是这个类的.class

加锁保证了程序执行的部分安全,但是会出现死锁,这里就是一个死锁的案例:

public class OtherService {

	private Object lock=new Object();
	public DeadLock deadLock;
	
	public void s1() {
		synchronized(lock) {
			System.out.println("s1");
		}
	}
	
	public void s2() {
		synchronized(lock) {
			System.out.println("s2");
			deadLock.m2();
		}
	}
	
	public void setLo(DeadLock deadLock) {
		this.deadLock=deadLock;
	}
}
public class DeadLock {

	private OtherService otherService=new OtherService();
	
	private Object lock=new Object();
	
	public DeadLock(OtherService otherService) {
		this.otherService=otherService;
	}
	
	public void m1() {
		synchronized(lock){
			System.out.println("m1");
			otherService.s1();
		}
	}
	
	public void m2() {
		synchronized(lock){
			System.out.println("m2");
		}
	}
}
public class Test {

	public static void main(String[] args) {
		OtherService o=new OtherService();
		DeadLock d=new DeadLock(o);
		o.setLo(d);
		
        //线程1
		new Thread() {
			public void run() {
				while(true)
					d.m1();
			};
		}.start();
		
        //线程2
		new Thread() {
			public void run() {
				while(true)
					o.s2();
			};
		}.start();
	
	}
}

在这个案例中,我们一开始执行d.m1()时获得了DeadLock中的锁,d.s2()获得了otherservice中的锁,然后然后再m1方法中又要调用s1()方法,在s2()中又要调用m2()的方法,这就造成线程1持有DeadLock的锁,二线程2持有otherservice的锁,他们互相又要进入对方的加锁代码块,互相又都不肯放弃锁,这就造成了死锁。

 

解决死锁问题

关于解决死锁,我们可以定义一个显示锁,听名字就是,我们人工的去操作给它锁和释放锁,线程如果拿到锁,执行一段时间,死锁了,那么,这个线程就不动了,我们就人工的释放它的锁,让其他的线程去运行,这样就避免了死锁。

接口,要实现的功能

import java.util.Collection;

public interface Lock {

    //异常错误类
	class TimeOutException extends Exception{
		public TimeOutException(String message) {
			super(message);
		}
	}
	
    //加锁
	void lock() throws InterruptedException;
	
    //加锁 时间限制
	void lock(long mills) throws InterruptedException,TimeOutException;
	
    //解锁
	void unlock();
	
	Collection<Thread> getBlockedThread();
	
	int getBlockedSize();
}

 

详细的实现

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

public class BooleanLock implements Lock{

	//true 表示锁已被获得 fales 表示锁被释放
	private boolean initValue;
	
	private Collection<Thread> blockedThreadCollection=new ArrayList<>();
	
	private Thread currentThread;// 用来存储当前线程的
	
	@Override
	public synchronized void lock() throws InterruptedException {
		while(initValue) {//当锁被别的线程拿着时
			blockedThreadCollection.add(Thread.currentThread());
			this.wait();
		}
		blockedThreadCollection.remove(Thread.currentThread());
		this.initValue=true;//获得锁
		this.currentThread=Thread.currentThread();//记录当前线程
	}

	@Override
	public synchronized void lock(long mills) throws InterruptedException, TimeOutException {
		 if(mills<0)
			 lock();
		 
		 long hasRemainTime=mills;
		 long endtime=System.currentTimeMillis()+mills;
		 while(initValue) {
			 if(hasRemainTime<0)
				 throw new TimeOutException("超时");
			 blockedThreadCollection.add(Thread.currentThread());
			 this.wait();
			 hasRemainTime=endtime-System.currentTimeMillis();
		 }
		 
//		 blockedThreadCollection.remove(Thread.currentThread());
		 this.initValue=true;
		 this.currentThread=Thread.currentThread();
	}

	//释放锁
	@Override
	public synchronized void unlock() {
		if(Thread.currentThread()==currentThread) {
			this.initValue=false;
			System.out.println("释放锁.....");
			this.notifyAll();//唤醒所有
		}
	}

	@Override
	public Collection<Thread> getBlockedThread() {
		return Collections.unmodifiableCollection(blockedThreadCollection);
	}

	@Override
	public int getBlockedSize() {
		return blockedThreadCollection.size();
	}

}

测试:

import java.util.ArrayList;

import chapter7.Lock.TimeOutException;

public class LockTest {

	public static void main(String[] args) {
		final BooleanLock booleanLock=new BooleanLock();
		ArrayList<String> list=new ArrayList<>();
		list.add("A");list.add("B");list.add("C");list.add("D");
		for(String s:list) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						//获得锁
						booleanLock.lock();
						System.out.println(Thread.currentThread()+"获得了锁!");
						work();
					} catch (Exception e) {
						System.out.println("超时");
					}finally {
						booleanLock.unlock();
					}
				}
			},s).start();;
		}
	}
	
	private static void work() throws InterruptedException {
		System.out.println(Thread.currentThread().getName()+"正在工作");
		Thread.sleep(400);
	}
}

四.常用的API

线程的优先级:getPriority()  setPriority()

默认优先级为5,可以企图改变线程的优先顺序(不推荐)

 

获得Id:在创建线程时,会有一个计数器,每创建一个线程会count++

通过源码可以看出

Jion方法: t1.jion()  等待t1线程执行完后才执行 当前线程(例如在main线程中调用,main就是当前线程) 当两个线程都在启动状态下,对两个线程分别执行join时,两个线程是同步进行

如果同时调用t1.jion() t2.jion() ,当前线程会在t1和t2线程执行完后再执行当前线程,t1和t2时同时进行的,它的等待只是针对当前线程。

如果调用jion(s,ns),那么当先时间就会等待,等待时间一过,当前线程就不再等待。

Thread,currentThread().join();//当前线程等待当前线程执行完(死循环)

案例:当我们要开辟多个线程同时去执行一项任务的多个分支 时,我们需要等待所有线程都执行完了才能一同再执行下一个任务,所以,我们可以让三个线程同时jion进另一个线程。

 

五.停止线程

interrupt:

interrupt()是对该线程进行一个标记,并不能直接关闭线程

interrupted是用来测试当前线程是否已经结束,但是这是个静态方法

我们在使用线程时不能直接关闭,但可以使用其他方法来关闭多线程

方法一:我们可以通过控制它的循环条件去关闭

public class ThreadCloseGraceful {

	private static class Worker extends Thread{
		
		private volatile boolean start=true;
		@Override
		public void run() {
			while(start) {
				System.out.println("A");
			}
		}
		
		public void shutdown() {
			this.start=false;
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		Worker worker=new Worker();
		worker.start();
		
		Thread.sleep(10000);
		
		worker.shutdown();
	}
}

方法二: 如果一个线程在被interrupt标记后,那么在sleep时就是可以被打断的,所以,我们只需要让线程每次睡眠一会,也就是说每次对线程做一个判断,通过捕获异常的方法关闭线程

public class ThreadCloseGraceful2 {

	private static class Worker extends Thread{
		
		@Override
		public void run() {
			while(true) {
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					break;
				}
			}
		}
	}
	
	public static void main(String[] args) {
		Worker worker=new Worker();
		worker.start();
		worker.interrupt();
	}
}

六.线程的优先级

getPriority()  setPriority()

优先级高的线程不一定是一定要先执行,而是执行的记几率高一点

 

七.守护线程

守护线程:setDaemon

将这个线程设置为守护线程,这个函数要在start之前调用。如果在守护线程(t1)上设置守护线程(t2),当第一个守护线程(t1)结束,它的守护线程(t2)也会跟随结束。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值