进程与线程
进程:是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程——生命周期 比如运行中的QQ。
线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。
并行与并发
并发:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并行:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
继承Thread类
- 创建一个新的执行线程有两种方法。 一个是将一个类声明为
Thread
的子类。 这个子类应该重写run
类的方法Thread
。
1.声明一个去继承Thread类
2.重写run()、实现逻辑
3.创建线程对象
4.开启线程,执行run()
public class Demo {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
//输出100以内的偶数
for (int i = 0; i < 100; i++) {
if(i%2 == 0){
System.out.println(i);
}
}
}
}
Thread类的一些方法
//获取当前线程
public static native Thread currentThread();
//获取当前线程名字
public final String getName()
//设置当前线程名字
public final synchronized void setName(String name) {
//源码:
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
演示上述方法:
public class Demo2 {
public static void main(String[] args) {
MyThread mt = new MyThread();
//自定义当前线程名的方式:通过调用Thread类的setName()进行设置
//mt.setName("自定义线程");
//执行start() 1.开启当前线程 2.执行run()
mt.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
//输出100以内的偶数
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
//实现类的无参可以省略
public MyThread(){
//隐式的初始化Thread的name属性
super();
}
}
打印结果:
static void sleep(long millis)
正在执行的线程以指定的毫秒数暂停(暂时停止执行),
举例:
public class Demo3 {
public static void main(String[] args) {
MyThread2 mt = new MyThread2();
mt.setName("子线程");
mt.start();
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//一秒打印一个偶数
if (i%2 == 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
void setPriority(int newPriority)
更改此线程的优先级。
-- Thread类定义了三种常量类型的优先级
//最小优先级
public final static int MIN_PRIORITY = 1;
//默认优先级
public final static int NORM_PRIORITY = 5;
//最大优先级
public final static int MAX_PRIORITY = 10;
public class Demo4 {
public static void main(String[] args) {
MyThread2 mt = new MyThread2();
mt.setName("子线程");
mt.setPriority(Thread.MAX_PRIORITY);
mt.start();
for (int i = 0; i < 100; i++) {
//一秒打印一个偶数
if (i%2 == 0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
class MyThread3 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//一秒打印一个偶数
if (i%2 == 0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
实现Runnable接口
- 另一种方法来创建一个线程是声明实现类
Runnable
接口。 那个类然后实现了run
方法。 然后可以分配类的实例,在创建Thread
时作为参数传递,并启动。
1.声明Runnable接口的子类
2.重写run(),实现逻辑
3.创建子类对象
4、创建Thread类对象,将子类对象通过构造器传入
public class Demo {
public static void main(String[] args) {
MyTread mt = new MyTread();
Thread t = new Thread(mt);
t.setName("子线程");
t.start();
}
}
class MyTread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2 == 0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
Thread类和Runnable接口的区别?
- 实现的方式没有类的单继承性的局限性
- 实现的方式更适合来处理多个线程共享数据的情况
实际上Thread类继承了 Runnable接口,本质上没有太大区别,知识一般来处理多个线程共享数据优先选择实现的方式。
public class Thread implements Runnable
守护线程
- 守护线程,专门用于服务其他的线程,如果其他的线程(即用户自定义线程)都执行完毕,连main线程也执行完毕,那么jvm就会退出(即停止运行)——此时,连jvm都停止运行了,守护线程当然也就停止执行了。
@param on if {@code true}, marks this thread as a daemon thread
//这句话的意思是如果条件是true 标记这个线程为守护线程
验证:
public class Demo {
public static void main(String[] args) {
MyTread mt = new MyTread();
Thread t = new Thread(mt);
//设置为守护线程
t.setDaemon(true);
t.setName("子线程");
t.start();
for (int i = 0; i < 100; i++) {
if (i%2 == 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
class MyTread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2 == 0){
try {
//通过睡眠提高线程抢到CPU执行权的概率
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
结果:
从图中可以看出主线程结束后程序就结束了,这是因为子线程被设置为守护线程。
可以理解为守护线程随着主线程的消亡而消亡。
线程中断
一个线程是一个独立的执行路径,它是否应该结束,应该由自身决定。
public class Demo2 {
public static void main(String[] args) {
MyTread2 mt = new MyTread2();
Thread t = new Thread(mt);
t.setName("子线程");
t.start();
for (int i = 0; i < 100; i++) {
if (i%2 == 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
//给线程t添加中断标记
t.interrupt();
}
}
class MyTread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2 == 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("发现中断标记,结束该线程");
//结束该线程
return;
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
打印结果:
线程安全问题
什么是线程安全问题?
出现问题原因:当某个线程操作车票的过程中,尚未完成,另一个线程也参与进来,对车票进行操作。
public class Demo {
public static void main(String[] args) {
Ticket t = new Ticket();
//创建三个窗口线程进行卖票
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.setName("窗口-1");
t2.setName("窗口-2");
t3.setName("窗口-3");
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable {
//总票数
private int ticket = 10;
@Override
public void run() {
while (true) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 卖票,票号为" + ticket);
ticket--;
}else {
break;
}
}
}
}
执行结果:
一共10张票三个窗口进行售出,出现重票和错票的问题。这就是线程安全问题
解决方式:当一个线程a在操作ticket的时候,其他线程不能参与进来。 直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能改变
同步方法
语法: synchronized修饰操作共享数据的方法
public class Demo {
public static void main(String[] args) {
Ticket t = new Ticket();
//创建三个窗口线程进行卖票
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.setName("窗口-1");
t2.setName("窗口-2");
t3.setName("窗口-3");
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable {
//总票数
private int ticket = 10;
@Override
public void run() {
while (true) {
sale();
}
}
public synchronized void sale() {
if (ticket > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 卖票,票号为" + ticket);
ticket--;
}
}
}
同步代码块
语法:
//锁对象可以是任何的对象 必须是所有线程共享同一把锁对象
synchronized(锁对象){
//操作共享数据的代码
}
因为实现Runnable接口只创建了一个子类对象,其他三个线程共用。
public class Demo2 {
public static void main(String[] args) {
Windows w = new Windows();
//创建三个窗口线程进行卖票
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口-1");
t2.setName("窗口-2");
t3.setName("窗口-3");
t1.start();
t2.start();
t3.start();
}
}
class Windows implements Runnable {
//总票数
private int ticket = 10;
@Override
public void run() {
while (true) {
synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 卖票,票号为" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
Lock锁
//使用ReentrantLock创建锁对象
Lock lock = new ReentrantLock();
//使用多态形式,是因为实现了Lock
public class ReentrantLock implements Lock
public class Demo3 {
public static void main(String[] args) {
Windows2 w = new Windows2();
//创建三个窗口线程进行卖票
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口-1");
t2.setName("窗口-2");
t3.setName("窗口-3");
t1.start();
t2.start();
t3.start();
}
}
class Windows2 implements Runnable {
//总票数
private int ticket = 10;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//加锁
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 卖票,票号为" + ticket);
ticket--;
} else {
break;
}
//释放锁
lock.unlock();
}
}
}
公平锁与非公平锁
//将参数设置为true,表示为公平锁就会执行公平锁策略
Lock lock=new ReentrantLock(true);//公平锁
Lock lock=new ReentrantLock(false);//非公平锁
//也就是线程1首先抢到CPU执行权后,会等到其他两个线程执行完后再操作共享数据。
执行结果应该是下面这样的
死锁
何为死锁,就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放
死锁的例子:
public class DeadLockTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
//假设有线程a和线程b 线程一启动,如果
//线程a拿到了s1锁,这时候睡了一秒 ,阻塞的过程中,很有可能线程b拿到了s2锁,
//这时候线程b也睡了一秒,想要继续执行下去,必须拿到s1锁 发现s1锁已经被线程a拿到 无法执行下去
//这时候就出现了 双方都占用这对方需要的共同资源,互相僵持这,出现 死锁。
synchronized (s1) {
s1.append("a");
s2.append("1");
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2) {
s1.append("b");
s2.append("2");
}
System.out.println(s1);
System.out.println(s2);
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2) {
s1.append("c");
s2.append("3");
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1) {
s1.append("d");
s2.append("4");
}
System.out.println(s1);
System.out.println(s2);
}
}
}).start();
}
}
解决方式:同步代码块不要嵌套。
线程通信
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来去走产品。
//店员
class Clerk {
//产品数量
private int productCount;
//生产产品的方法
public synchronized void producerProduct() {
if (productCount < 20) {
productCount++;
System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");
notify();
}else {
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
消费产品的方法
public synchronized void consumerProduct() {
if (productCount > 0) {
System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
productCount--;
notify();
}else {
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class Consumer extends Thread {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("消费者开始消费产品....");
while (true) {
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumerProduct();
}
}
}
//生产者
class Producer extends Thread {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("生产者开始生产产品....");
while (true) {
try {
sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.producerProduct();
}
}
}
public class ProducerTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
Consumer consumer2 = new Consumer(clerk);
producer.setName("生产者1");
consumer.setName("消费者1");
consumer2.setName("消费者2");
producer.start();
consumer.start();
consumer2.start();
}
}
实现Callable接口
1.创建实现Callable接口的实现类
2.完成Callable接口的call()重写
3.创建接口的实现类对象
4.该对象作为参数传递给FutureTask类的构造器, 创建该类对象
5.此对象作为参数传递给Thread类的构造器,创建一个线程
6.调用futureTask的get()返回重写call()的返回值
//1. 创建实现Callable接口的实现类
class MyThread implements Callable<Integer>{
//2. 完成Callable接口的call()重写
@Override
public Integer call() throws Exception {
//完成100以内的偶数之和
int sum = 0;
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
sum+=i;
}
}
return sum;
}
}
public class ThreadTest {
public static void main(String[] args) {
//3. 创建接口的实现类对象
MyThread m1 = new MyThread();
//4. 该对象作为参数传递给FutureTask类的构造器, 创建该类对象
FutureTask<Integer> futureTask = new FutureTask<Integer>(m1);
//5. 此对象作为参数传递给Thread类的构造器,创建一个线程
Thread t1 = new Thread(futureTask);
t1.start();
try {
//6. 调用futureTask的get()返回重写call()的返回值
Integer sum = futureTask.get();
System.out.println("偶数总和: " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程
就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容
器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
-
使用线程池的好处:
-
降低资源消耗。
-
提高响应速度。
-
提高线程的可管理性
四种线程池
1.缓存线程池
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行
很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造
的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并
从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资
源
/***
* 缓存线程池.(长度无限制)
* 执行流程:
* 1. 判断线程池是否存在空闲线程 *
* 2. 存在则使用
* 3. 不存在,则创建线程 并放入线程池, 然后使用
*/
public class Test3 {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
//向线程池中 加入 新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
}
}
2.定长线程池
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大
多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,
则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何
线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之
前,池中的线程将一直存在。
public class Test2 {
public static void main(String[] args) {
// 创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while(true) {
threadPool.execute(new Runnable() { // 提交多个线程任务,并执行
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running ..");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
3.单线程线程池
Executors.newSingleThreadExecutor()返回一个线程池(这个线程池只有一个线程),这个线程
池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去
public class Test4 {
public static void main(String[] args) {
/* 效果与定长线程池 创建时传入数值1 效果一致.
单线程线程池.
执行流程:
1. 判断线程池 的那个线程 是否空闲
2. 空闲则使用
3. 不空闲,则等待 池中的单个线程空闲后 使用 */
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
}
}
4.周期性任务定长线程池
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
public class Test {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(3);
/**
* 定时执行
* 参数1. runnable类型的任务
* 参数2. 时长数字 *
* 参数3. 时长数字的单位
*/
scheduledThreadPool.schedule(new Runnable(){
@Override
public void run() {
System.out.println("延迟三秒");
}
}, 3, TimeUnit.SECONDS);
/**
* 周期执行
* 参数1. runnable类型的任务
* 参数2. 时长数字(延迟执行的时长)
* 参数3. 周期时长(每次执行的间隔时间)
* 参数4. 时长数字的单位
*/
scheduledThreadPool.scheduleAtFixedRate(new Runnable(){
@Override
public void run() {
System.out.println("延迟 1 秒后每三秒执行一次");
}
},1,3,TimeUnit.SECONDS);
}
}
Lambda表达式
jdk1.8中Lambda新特性极大的提高编程效率和程序可读性。
比如使用匿名内部类方式创建线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2 == 0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}).start();
使用Lambda表达式的方式实现:
Lambda表达式 | 语法 |
---|---|
左边 | 右边 |
方法名里面的参数列表 | 方法体内的逻辑 |
new Thread(() -> System.out.println("Lambda方式创建线程")).start();
##### Lambda表达式
**jdk1.8中Lambda新特性极大的提高编程效率和程序可读性。**
比如使用匿名内部类方式创建线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2 == 0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}).start();