目录:
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();
}
}