Java内容梳理(19)API学习(7)线程

目录:

1、进程和线程

2、线程的创建

3、线程的运行方式和使用场景

4、线程的生命周期

5、线程优先级

6、守护线程

7、线程常用API

8、线程安全

9、锁机制

10、线程同步控制(死锁的介绍)

11、定时器


1、进程和线程

什么是进程?

简单的说:一个独立运行的程序对应一个进程,进程进程之间相对独立,内存数据并不共享

什么是线程?

进程由多个线程来组成,线程共享进程中的资源

线程是进程中的逻辑执行单元,当进程启动时,会立刻启动一个主线程来驱动整个程序逻辑的执行

2、线程的创建

(1)利用Runnable接口创建对象

//创建t1线程

Thread t1 = new Thread( new Runnable() {

           public void run(){

                  //写并行逻辑

            }

});

//启动t1线程,让线程进入可运行状态,等待cpu的选中

t1.start();

(2)利用Thread类来创建对象

//创建t2线程

Thread t2 = new Thread(){

          public void run(){

                   //写并行逻辑

          }

};

//启动t2线程,让线程进入可运行状态,等待cpu的选中

t2.start();

3、线程的运行方式和使用场景

线程的运行方式:

同时运行:并发运行

cpu时间片:cpu运行线程的单位时间

并发原理:

单核cpu会不停的在多个线程中挑选一个线程来运行,运行一个时间片,当时间片到期后,cou核心将挂起这个线程,再去挑

选其它线程占用自己的时间片。时间片的切换频率快到用户无法察觉,从而给用户一种并发的感觉

线程的使用场景:当有多个程序逻辑需要并发运行时,我们需要使用线程来实现。

4、线程的生命周期

在整个生命周期中,线程会有不同的状态,每个状态的特点不一样

生命周期:从new开始,到run方法结束

5、线程优先级

我们不能控制CPU选中具体哪个线程来执行, 我们可以通过设定线程的优先级来告知CPU优先执行哪个线程.CPU在挑选可运行状态

下的线程时优先考虑选中优先级高的线程.

设定线程的优先级,setPriority(int newPriority)   其中newPriority范围只能是1-10之间的整数

当我们没有指定优先级时,默认采用:NORM_PRIORITY ( 5 )

必须要在线程start()调用前执行才有效

package thread;

public class Run {
	public static void main(String[] args) {
		Thread t1 = new Thread( new Runnable() {
				@Override
				public void run() {
					System.out.println("t1线程执行了");
				}
		 });
		
		//创建t2线程
		Thread t2 = new Thread(){
			@Override
			public void run() {
				System.out.println("t2线程执行了");
			}
		};
		t1.setPriority(1);
		t2.setPriority(5);
		
		t1.start();
		t2.start();
		
	}
}

6、守护线程

特点:

当进程中普通的线程全部执行完毕后,整个进程结束

守护线程不影响进程的结束

如:gc线程

setDaemon( boolean on )设置当前线程对象是否为守护线程

必须在线程启动前调用才有效

package thread;

public class Run {
	public static void main(String[] args) {
		Thread t1 = new Thread( new Runnable() {
				@Override
				public void run() {
					System.out.println("t1线程执行了");
				}
		 });
		
		Thread t2 = new Thread(){
			@Override
			public void run() {
				
				System.out.println("t2线程执行了");
			}
		};
		
		t1.setDaemon(true);
		t1.start();
		t2.start();
		
	}
}

7、线程常用API

Thread.currentThread();获取当前线程对象

getId();获取线程ID

getName();获取当前线程的名称

sleep(long mills);让线程休眠指定的毫秒数;线程会进入阻塞状态,指定时间过后线程重新进入可运行状态

join();让当前线程等待调用join方法的线程执行完毕后,再执行当前线程,让本来并行的两条线程变为串行执行

package thread;

public class Run2 {
	public static void main(String[] args) {
		Thread t1 = new Thread() {
				public void run() {
					System.out.println("t1线程运行");
					
					try {
						sleep(3*1000);
						System.out.println("当前线程阻塞3秒");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
		};
		
		try {
			t1.sleep(2*1000);
			System.out.println("t1线程阻塞2秒");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		t1.start();
		
	}
}

package thread;

public class Run3 {
	public static void main(String[] args) {
		 Thread t1 = new Thread() {
			public void run() {
				System.out.println("t1线程运行");
			}
	    };
	    
	    Thread t2 = new Thread() {
			public void run() {
				try {
					t1.join();//保证了t1线程始终会在t2线程之前执行
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("t2线程运行");
			}
	    };
	    
	    t2.start();
	    t1.start();
	}
}

8、线程安全和锁机制

典型的线程安全问题:脏读

原因:多个线程访问同一资源时,有某些线程对该资源进行了修改,其它线程再次读取资源,会与开始读取的不一致

锁机制:避免线程安全问题的一种手段,synchronized关键字,加锁。

只有先获得锁的线程A,才能进入synchronized代码块中去执行代码,其它线程都在等待线程A释放这个锁后,再去竞争锁,然后执

行代码。

线程A释放锁的时机:

1. 线程A顺利执行完synchronized代码块

2. 线程A在执行synchronized代码块时出现异常抛出导致synchronized代码块异常退出时

3. 调用锁对象的wait方法时

注意:

1、若想多个代码块串行执行,必须要让这几个代码块被"同一把锁"锁住

public void method(){

         synchronized( this ){

             //方法体

        }

}

等同于

public synchronized method(){

          //方法体

}

2、对静态方法加synchronized锁的是类

 

package thread;

public class Run4 {
	private static Long count = 1L;
	public static void main(String[] args) {
		
		Thread t1 = new Thread() {
			public void run() {
				/*对count这个临界资源加锁,count也就是"锁对象"*/
				synchronized (count) {
					count += 1;
					System.out.println("t1执行");
					System.out.println(count);
				}
			}
		};
		
		Thread t2 = new Thread() {
			public void run() {
				/*对count这个临界资源加锁*/
				synchronized (count) {
					count += 2;
					System.out.println("t2执行");
					System.out.println(count);
				}
			}
		};
		
		t1.start();
		t2.start();
		
	}
}

举例:锁住当前对象

举例:锁住其它对象

9、线程同步控制(死锁的介绍)

设计Object类中的三个方法:wait / notify / notifyAll

wait:让当前线程等待(进入阻塞状态)并释放其占用锁

notify:通知被同个锁阻塞的线程中的某个线程恢复可运行状态,唤醒对应的锁池中的某个线程,进入可运行状态

notifyAll:唤醒对应锁池中的全部线程

注意:这三个方法必须在synchronized代码块中被使用

死锁:

什么是死锁?

线程A等待线程B释放锁,同时线程B也在等待线程A释放锁,从而形成相互等待的情况,这种情况称为死锁

通常情况下死锁的发生:

多个线程需要按照各自的顺序来同时使用a锁和b锁,但由于这多个线程使用a锁和b锁的顺序不一致,就可能导致死锁

解决死锁的思路:

若多个线程需要同时使用多个锁时,我们应该尽量让这多个线程使用这些锁对象时的顺序是一致的.

举例:生产者与消费者

以生产蛋糕为例应该考虑:

(1)生产速度大于卖出速度

(2)卖出速度大于生产速度

package thread;

import java.util.List;

/**
 * 生产者线程
 */
public class Maker extends Thread{
	/*container表示放蛋糕的货架*/
	private List<String> container;
	private int maxSize;

	/*由外界提供货架和货架的最大装载量*/
	public Maker(List<String> container, int maxSize) {
		this.container = container;
		this.maxSize = maxSize;
	}
	
	public void run() {
		while(true) {
			/*货架没满,就生产蛋糕,往上放*/
			if(container.size() <= maxSize) {
				try {
					System.out.println("生产蛋糕,放入货架");
					this.sleep(2000);
					container.add("蛋糕");
					System.out.println("货架上的蛋糕剩余:"+container.size());
					/*检测货架上的蛋糕数,若大于4个,就要去卖蛋糕了;不能等到生产满了货架才卖*/
					synchronized (container) {
						if(container.size() >= 4 ) {
							container.notifyAll();
						}
					}
					
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}else {
				/*货架满了,等待蛋糕卖出;或者是生产速度大于卖出速度*/
				synchronized (container) {
					try {
						System.out.println("货架满了,等待蛋糕卖出");
						//唤醒消费进程,卖蛋糕
						container.notifyAll();
						//让当前生产蛋糕线程等待,再释放其对container的占用锁
						container.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}	
	}
	
}
package thread;

import java.util.List;

/**
 *消费者线程
 */
public class Saller extends Thread{
	private List<String> container;

	public Saller(List<String> container) {
		super();
		this.container = container;
	}
	
	public void run() {
		while(true) {
			if(container.size() > 0 ) {
				try {
					System.out.println("卖蛋糕,卖一个,货架减一个");
					Thread.sleep(2000);
					container.remove(0);
					System.out.println("货架上的蛋糕剩余:"+container.size());
					
					/*检测货架上的蛋糕数,若小于4个,就要去生产蛋糕了;不能等到卖完了才去生产*/
					synchronized (container) {
						if(container.size() <= 4 ) {
							container.notifyAll();
						}
					}
					
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
			}else {
				/*卖蛋糕卖的快,先卖完的情况*/
				synchronized (container) {
					try {
						System.out.println("蛋糕卖完了,等待生产");
						//唤醒生产蛋糕线程
						container.notifyAll();
						//让卖蛋糕线程等待,释放对container的锁
						container.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
			}
		}
	}
	
}
package thread;

import java.util.ArrayList;
import java.util.List;

public class Run5 {
	public static void main(String[] args) {
		List<String> container = new ArrayList<>();
		int maxSize = 15;
		
		Maker m = new Maker(container, maxSize);
		Saller s = new Saller(container);
		
		m.start();
		s.start();
		
	}
}

10、定时器

它可以周期性的定时执行指定的任务

java.util.Timer类:表示一个计时器(执行者)

schedule(task, 1000):延迟1000毫秒执行task任务(仅执行一次)

schedule(task, 0, 1000):延迟0毫秒执行task任务,每个1000毫秒执行一次这个task任务

cancel():结束计时器

java.util.TimerTask类:表示一个计时器任务(事情)

cancel():结束当前这个任务

package thread;

import java.util.Timer;
import java.util.TimerTask;

public class TimerDemo {
	public static void main(String[] args) {
		/*创建一个计时器*/
		Timer timer = new Timer();
		
		/*创建一个计时器任务:大扫除*/
		TimerTask task1 = new TimerTask() {
			private int day = 1;
			@Override
			public void run() {
				System.out.println("打扫卫生");
			}
		};
		/*延迟2秒执行task1,并且只执行一次,但是计时器不会关闭*/
		//timer.schedule(task1, 2000);
		
		/*每3秒执行一次task1,0表示不会延迟执行*/
		timer.schedule(task1, 0, 3000);
		
		/*取消计时器,计时器立即关闭,不会执行任何任务*/
		timer.cancel();
		
	}
}

猜你喜欢

转载自blog.csdn.net/Carl_changxin/article/details/82908007