JavaSE:线程

线程

进程和线程

进程:重量级,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。启动进程消耗的内存比启动线程消耗的内存要高得多。
线程:轻量级,线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程,也可以有多个线程。缺点:多个线程是先用进程的共享资源,需要对共享资源进行同步,确保一个共享资源最多只能服务于一个线程。

线程的串行、并行和并发

并行:同时执行。
并发:同一时间执行多个线程,并不是同时执行,而是cpu在多个线程中高速切换。

创建线程的方式:

1)继承Thread类:

class testThead extends Thread {
	public void run(){
		for(int i = 0;i<10;i++){
		//currentThread返回当前执行线程的引用,getName:返回线程的名称
			System.out.println(Thread.currentThread().getName()+" "+i);
		}
	}
}
 public class Test{
	 public static void main(String[] args) {
		 
		 Thread a = new Thread(new testThead(),"a线程");
		 Thread b = new Thread(new testThead(),"b线程");
		 //通过调用start()方法开启线程,线程开启后会自动调用线程的run()方法。
		 a.start();
		 b.start();
	 }
 }

2)实现Runnable接口:

实现Runnable接口,一个类可以实现多个接口,程序弹性和灵活性比继承Thread高。

/**
 * 规范:实现Runnable的类型命名以Task结尾
 * DeskTask多线程中的一个任务
 * run()方法就是任务体
 */
public class DeskTask implements Runnable {
	@Override
	public void run() {
		for(int i=1;i<=10;i++) {
			System.out.println(Thread.currentThread().getName()+"::搬了第"+i+"个桌子");
		}
	}
}

/**
 *在主线程中启动桌子和椅子步骤:
 *1创建Thread类型的对象
 *2将“任务”注入到线程(Thread)中
 *3调用Thread类型的start()方法
 *任务注入到线程
 *此时main(主线程)启动桌子和椅子线程完毕就会出栈,不会守护桌子和椅子线程
 *main线程不是守护线程
 */
public class TestRunnable {
	public static void main(String[] args) {
		//将桌子任务注入到线程
		Thread t1 = new Thread(new DeskTask());
		t1.setName("桌子线程");
		t1.start();
		//将椅子任务注入到线程
		Thread t2 = new Thread(new ChairTask());
		t2.setName("椅子线程");
		t2.start();
	}
}

3)匿名内部类创建线程:

public class TestThreadAddSub {
	//匿名内部类创建线程
	public static void main(String[] args) {
		new Thread(new Runnable() {
			@Override
			public void run() {
			System.out.println(Thread.currentThread().getName()+"这是第一个线程");
			}
		}).start();
		
		new Thread(()-> {
				System.out.println(Thread.currentThread().getName()+"这是第二个线程");
			
		}).start();
	}
}

线程的生命周期:

当线程被创建并启动以后,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)五种不同的状态。

新建状态(New)

用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新建状态。处于新建状态的线程有自己的内存空间,通过调用start方法进入就绪状态(Runnable)。
注意:不能对已经启动的线程再次调用start()方法,否则抛出IllegalThreadStateException异常。

就绪状态(Runnable)

处于就绪状态的线程已经具备了运行条件(也就是具备了在CPU上运行的资格),但还没有分配到CPU的执行权,处于“线程就绪队列”,等待系统为其分配CPU。 就绪状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会进入执行状态。 一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。
运行状态(Running)
处于就绪状态的线程,如果获得了CPU的调度,就会从就绪状态变为运行状态,执行run()方法中的任务。
运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
如果该线程失去了CPU资源,就会又从运行状态变为就绪状态,重新等待系统分配资源。也可以对在运行状态的线程调用yield()方法,它就会让出CPU资源,再次变为就绪状态。

阻塞状态(Blocked)

处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态。
2.同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
3.其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

线程的优先级:

可以为每个线程设置优先级,优先级高的线程任务先执行完毕的概率就会越大,不是优先级最高的线程一定会先抢到CPU。最小的优先级(1)、最大的优先级(10)、普通的优先级(5),线程默认优先级是5,工作中线程优先级只能是1、5、10。

public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

设置线程优先级方法:

线程名.setPriority(优先级常量);

sleep():

如果我们需要让当前正在执行的线程暂停一段时间

并进入阻塞状态,指定时间之后,解除阻塞状态,进入就绪状态,则可以通过调用Thread的sleep方法,sleep方法是静态方法。
语法:

正在运行状态的线程对象.sleep();

1.它只对正在运行状态的线程对象有效。
2.使用sleep方法之后,线程是进入阻塞状态的,只有当睡眠的时间结束,才会重新进入到就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不可能精准的去干涉它,所以如果调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒。

yield():

yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态的方法,它也可以让当前正在执行的线程暂停,让出CPU资源给其它的线程。但是和sleep()方法不同的是,它不会进入到阻塞状态,而是进入到就绪状态。
正在运行状态的线程对象.yield();

join():

线程的合并就是:线程A在运行期间,可以调用线程B的join()方法,这样线程A就必须等待线程B执行完毕后,才能继续执行。在主线程中,将其它线程合入到主线程中,其它线程执行完毕之后最后结束主线程。

线程的停止

如何停止正在运行的线程?

使用定时器去停止正在执行的线程,定时器是一个监听者,监听线程

定时器:设定一个周期,按照指定的周期重复的执行任务

定时器有一个定时任务(实现了Runnable接口)

场景:main线程中启动一个ThreadA,同时开启一个定时任务去监听ThreadA,定时任务每间隔5秒钟扫描当前工程有没有stop文件,如果有将ThreadA线程停止掉

public class TestStop {
	public static void main(String[] args) {
		StopTask stop = new StopTask(true);
		Thread stopThread = new Thread(stop);
		stopThread.start();
		//创建定时器,定时器包含一个定时任务,每个5秒钟检查当前工程下面有没有stop文件,如果有停止线程
		Timer timer = new Timer();
        //schedule()方法调度定时任务的执行
		timer.schedule(new TimerTask() {
			@Override
			public void run() {
				File file = new File("stop");
				//条件成立:表示stop文件存在,停止线程
				if(file.exists()) {
					stop.setFlag(false);
					//StopTask任务停止,立马删除stop文件
					file.delete();
					//取消定时任务
					timer.cancel();
				}
			}
		}, 1000,5000);	
	}
}
class StopTask implements Runnable{
	private boolean flag ;

	public void setFlag(boolean flag) {
		this.flag = flag;
	}
	public StopTask(boolean flag){
		this.flag = flag;
    }
	@Override
	public void run() {
		long index=0;
		for(;flag;) {
			index++;
			if(index%500000000==0) {
				System.out.println(Thread.currentThread().getName()+":::"+index);
			}
		}
	}	
}public class TestTimerr {
	public static void main(String[] args) {
		Timer timer = new Timer();
		//参数1:定时器里面的定时任务,参数2:delay延迟多长时间执行
//		timer.schedule(new TimerTask() {
//			@Override
//			public void run() {
//				System.out.println(new Date());
//			}
//		}, 1000);
		//Java的定时器:指定一个周期重复执行
		//参数1:定时器里面的定时任务
		//参数2:delay延迟多长时间执行(此时表示当前时间之后的1秒钟开始执行定时任务)
		//参数3:每次任务执行的间隔周期(此时表示每个2秒执行一次定时任务)
		timer.schedule(new TimerTask() {
			@Override
			public void run() {
				System.out.println(new Date());
			}
		}, 1000,2000);
	}
}

守护线程:

当正在运行的线程都是守护线程时,Java虚拟机将退出。setDaemon(true)方法必须在启动线程前调用,否则抛出IllegalThreadStateException异常。
如果我们不将一个线程以守护线程方式来运行,即使主线程已经执行完毕,程序也永远不会结束。
注意:如果守护线程守护的是main的主线程,只有当其运行的进程结束,main的主线程才会结束。

线程同步:

线程安全:

非线程安全:多个线程同一时刻访问一个共享资源,造成共享资源的数据出现脏数据,不安全(数组下标越界)。
线程安全:需要对共享资源上一把锁,确保同一时刻最多只能有一个线程访问共享资源,其他线程在外面等待,不会出现脏数据。

synchronized关键字:

同步机制:步骤:A,B,C三个线程同时访问一个共享资源,A线程访问共享资源,判断有没有线程进入,如果没有访问共享资源,B线程访问共享资源,判断有没有线程进入,如果有线程进入了共享资源将在锁池中等待,A线程访问完毕了,会将锁的钥匙放到锁池中,B线程拿到钥匙就可以进入了。
synchronize就是为共享资源上锁,确保同一时刻最多只能有一个线程访问共享资源

例:创建3个线程,同时访问一个ArrayList。

public class TestArrayList {
	public static void main(String[] args) {
		ArrayList<Integer> list =new ArrayList<>();
		new Thread(()->{
			while(true) {
				list.add(1);
			}
		}).start();
		new Thread(()->{
			while(true) {
				list.add(1);
			}
		}).start();
		new Thread(()->{
			while(true) {
				list.add(1);
			}
		}).start();
		new Thread(()->{
			while(true) {
				list.add(1);
			}
		}).start();
	}
}


public class ArrayList<E> {

	private int size;
	
	private Object[] dataElement;
	
	private final Resource resource = new Resource();
	
	public ArrayList(int capacity) {
		dataElement = new Object[capacity];
	}
	
	public ArrayList() {
		this(10);
	}
	
	public int size() {
		return size;
	}
	
	@SuppressWarnings("unchecked")
	public E get(int index) {
		if(index<0 || index>=size) {
			throw new ArrayIndexOutOfBoundsException("index="+index+":size="+size);
		}
		return (E)dataElement[index];
	}
	
	public  void add(E element) {
		//不会改变数组的大小,多个线程可以同时访问
		int length = dataElement.length;
		//被synchronize修饰的代码叫做临界区(临界区的代码块最多只能有一个线程访问)
		synchronized (resource) {
			//条件成立:数组没有空间了,需要扩容
			if(size==length) {
				int newLength = length+(length>>1);
				dataElement = Arrays.copyOf(dataElement, newLength);
			}
			dataElement[size++] = element;
		}
	}
}

小结:

1工作中共享资源必须加锁

2为了提供程序在内存中运行的效率,尽量不要锁住整个方法,而是锁代码块

3 那些代码块需要上锁?

first:某个共享资源的数据经常会发生改变

second:被多个线程使用

4 使用synchronize修饰某个块代码,叫做同步块

5 被synchronize修饰的代码叫做临界区(临界区的代码块最多只能有一个线程访问)

6 synchronize是一个JVM级别的互斥锁(排它锁)

JVM帮你加锁和解锁(不是人为加锁和解锁)

缺点:一旦出现异常可能无法解锁,因为不是你手工加锁

7 同步块括号不能少,括号里面的对象表示你要锁住的共享资源,最好使用final修饰,因为不可改变的

8 synchronize括号里面的对象不支持基本类型

Lock关键字:

比synchronize更加友好,一旦出现异常可以人工解锁,特征:有一个公平机制,等待时间最长的线程优先进入共享资源。

//true:启动公平锁  false:非公平锁  默认false
private Lock lck = new ReentrantLock(true);
//加锁
lck.lock();
try{
   
}finally{
    //解锁
    lck.unlock();
}
//好处:一旦try块出现异常执行finally,将锁解掉
//解锁操作工作中一定要放在finally块中,出现异常立马解锁。
//工作中如果使用Lock,一定先编写整体(try....finally块),再编写局部(try块里面的内容)

例子:创建3个线程,同时访问一个ArrayList。

public class TestArrayList {
	public static void main(String[] args) {
		ArrayList<Integer> list =new ArrayList<>();
		new Thread(()->{
			while(true) {
				//写操作
				list.add(1);
			}
		},"Add线程").start();
		
		new Thread(()->{
			while(true) {
				//读操作
				list.get(0);
			}
		},"Get线程").start();
		
		new Thread(()->{
			while(true) {
				//读操作
				list.size();
			}
		},"Size线程").start();
		
	}
}

public class ArrayList<E> {

	private int size;
	
	private Object[] dataElement;
	
	/**
	 * true:公平的重入锁
	 */
	private Lock lck = new ReentrantLock(true);
	
	public ArrayList(int capacity) {
		dataElement = new Object[capacity];
	}
	
	public ArrayList() {
		this(10);
	}
	
	public int size() {
		lck.lock();
		try {
			return size;
		} finally {
			lck.unlock();
		}
	}

	@SuppressWarnings("unchecked")
	public E get(int index) {
		lck.lock();
		try {
			if(index<0 || index>=size) {
				throw new ArrayIndexOutOfBoundsException("index="+index+":size="+size);
			}
			return (E)dataElement[index];
		} finally {
			lck.unlock();
		}
	}
	
	public  void add(E element) {
		//不会改变数组的大小,多个线程可以同时访问
		int length = dataElement.length;
		lck.lock();
		try {
			//条件成立:数组没有空间了,需要扩容
			if(size==length) {
				int newLength = length+(length>>1);
				dataElement = Arrays.copyOf(dataElement, newLength);
			}
			dataElement[size++] = element;
		} finally {
			lck.unlock();
		}
	}
}

读写锁:

读写锁:由读锁(如果你为多个方法加了读锁,操作多个读方法的线程可以同时进入临界区)和写锁(如果多个方法加上了写锁,最多只能有一个线程进入临界区)组成。

读:并行,一旦操作共享资源多个线程的读方法进入临界区,所有的操作写方法的线程必须在外面等待

写:串行,一旦某个线程进入了贡献在资源的写方法临界区,所有的读方法在外面等待,其他的操作写方法的线程也将在外面等待。

例子:创建3个线程,同时访问一个ArrayList。

public class ArrayList<E> {

	private int size;
	
	private Object[] dataElement;
	
	/**
	 * true:公平的重入读写锁
	 */
	private ReadWriteLock rw = new ReentrantReadWriteLock(true);
	
	public ArrayList(int capacity) {
		dataElement = new Object[capacity];
	}
	
	public ArrayList() {
		this(10);
	}
	
	public int size() {
		//添加读锁
		//rw.readLock() 获取读写锁里面的读锁
		rw.readLock().lock();
		try {
			return size;
		} finally {
			rw.readLock().unlock();
		}
	}

	@SuppressWarnings("unchecked")
	public E get(int index) {
		rw.readLock().lock();
		try {
			if(index<0 || index>=size) {
				throw new ArrayIndexOutOfBoundsException("index="+index+":size="+size);
			}
			return (E)dataElement[index];
		} finally {
			rw.readLock().unlock();
		}
	}
	
	public  void add(E element) {
		//不会改变数组的大小,多个线程可以同时访问
		int length = dataElement.length;
		//上读写锁里面的写锁(串行)
		rw.writeLock().lock();
		try {
			//条件成立:数组没有空间了,需要扩容
			if(size==length) {
				int newLength = length+(length>>1);
				dataElement = Arrays.copyOf(dataElement, newLength);
			}
			dataElement[size++] = element;
		} finally {
			rw.writeLock().unlock();
		}
	}
}

public class TestArrayList {
	public static void main(String[] args) {
		ArrayList<Integer> list =new ArrayList<>();
		new Thread(()->{
			while(true) {
				//写操作
				list.add(1);
			}
		},"Add线程").start();
		
		new Thread(()->{
			while(true) {
				//读操作
				list.get(0);
			}
		},"Get线程").start();
		
		new Thread(()->{
			while(true) {
				//读操作
				list.size();
			}
		},"Size线程").start();
		
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值