线程
进程与线程
1、什么是进程
进程:启动一个QQ.exe就叫一个进程。 接着又启动一个360.exe,这叫两个进程。以此类推,每个独立执行的程序都称为进程。
2、什么是线程
线程:线程是在进程内部同时做的事情,比如在QQ里,有很多事情要同时做,比如发送消息和接受消息,同时上传文件,这就是由多线程
一、创建的四种方式
1、继承Thread方法
public class ThreadextendThreade {
public static void main(String[] args) {
//实现第一种方式
One one = new One();
Thread thread = new Thread(one, "我是第一个");
Thread thread1 = new Thread(one, "我是第二个");
Thread thread2 = new Thread(one, "我是第三个");
thread.start();
thread1.start();
thread2.start();
}
}
//1、第一种方法:继承Thread
class One extends Thread{
//加上同步关键字
@Override
public synchronized void run() {
for (int i = 0; i < 5; i++) {
System.out.println("我是继承Thread创建的线程"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2、实现Runnable接口
public class ThreadextendThreade {
public static void main(String[] args) {
//第二种实现方式
Two two = new Two();
Thread thread = new Thread(two);
thread.start();
}
}
//2、第二种方式:实现Runnable接口
class Two implements Runnable{
@Override
public void run() {
while(true){
System.out.println("我是实现Runnable的线程"+Thread.currentThread().getName());
}
}
}
3、实现Callable接口
public class ThreadextendThreade {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//第三种实现方式
Three three = new Three();
FutureTask<Integer> futureTask=new FutureTask<Integer>(three);
Thread thread=new Thread(futureTask);
thread.start();
//获取返回值
Integer integer = futureTask.get();
System.out.println(integer);
}
}
//3、第三种方式:实现Callable接口
class Three implements Callable<Integer>{
@Override
public Integer call() throws Exception {
while (true){
System.out.println("我是实现Callable接口的线程"+Thread.currentThread().getName());
return 1;
}
}
}
4、线程池
主要有newFixedThreadPool,newCachedThreadPool,newSingleThreadExecutor,newScheduledThreadPool
public class ThreadextendThreade {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//第四种方式实现
Four four = new Four();
ExecutorService service= Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
service.execute(four);
}
service.shutdown();
}
}
//4、第四种方式:使用线程池
class Four implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("run……"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
二、线程的同步
1、synchronized
- 锁方法,类,都是锁定的this对象
- 锁对象中的属性(适用于多线程下会被修改的数据)
- synchronized非函数签名,因此无法被继承,所以无法保证子类调用同步.
###
synchronized:java关键字,具有锁
###
- t1和t2共享线程对象Account对象 Account中有个属性为Object obj (只要是唯一对象。就可以锁住)
- 局部变量就不行
- java三大变量:
- 存在方法区和堆区的对象。只有一个,并且是唯一的
- 常量不会有线程安全问题,因为它不可修改
- 使用局部变量操作字符串的时候是选择StringBuffer还是StringBuilder:答案是:StringBuider,局部变量没有线程安全。
- 让t1线程先执行方式:
- 以上方式叫做排它锁 还有个叫互斥锁
2、Lock
public class 同步 {
public static void main(String[] args) {
TestLock testLock=new TestLock();
new Thread(testLock).start();
new Thread(testLock).start();
new Thread(testLock).start();
}
}
class TestLock implements Runnable{
private int num=10;
private ReentrantLock reentrantLock=new ReentrantLock(); //new
@Override
public void run() {
while (true){
try {
reentrantLock.lock();//加锁
if (num<=0)break;
System.out.println(num--);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
reentrantLock.unlock();//解锁
}
}
}
}
总结:
三、lamda表达式
1、什么是lamda表达式
https://www.runoob.com/java/java8-lambda-expressions.html
3、进程与线程的区别
/*
进程是资源分配最小单位,线程是程序执行的最小单位;
进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段,线程没有独立的地址空间,它使用相同的地址空间共享数据;
线程:堆和方法区地址空间共享,栈内存空间独立,一个线程一个栈
CPU切换一个线程比切换进程花费小;
创建一个线程比进程开销小;
线程占用的资源要⽐进程少很多。
线程之间通信更方便,同一个进程下,线程共享全局变量,静态变量等数据,进程之间的通信需要以通信的方式(IPC)进行;(但多线程程序处理好同步与互斥是个难点)
多进程程序更安全,生命力更强,一个进程死掉不会对另一个进程造成影响(源于有独立的地址空间),多线程程序更不易维护,一个线程死掉,整个进程就死掉了(因为共享地址空间);
进程对资源保护要求高,开销大,效率相对较低,线程资源保护要求不高,但开销小,效率高,可频繁切换;
*/
- 运行mian方法,最少开启两个线程:主线程和gc回收线程
- 所以当主线程结束了,那么主线程栈空了,然后其他栈并没有结束
- start()方法的作用:启动一个分支线程,在JVM中开辟一个新的栈空间。然后该线程自动启动run方法。让线程从新建状态转化为就绪状态 。run方法在新栈的底部。main方法也在主线程栈的底部
- 直接调用类名.run()方法:该线程是单线程状态
- 如果是调用start方法时:如下图
四、线程对象的生命周期
- 特点:
- 线程运行后会在就绪状态和运行状态来回切换。
- 当进程从阻塞状态到就绪状态然后到运行状态时,会接上一次的代码继续运行
- 主线程的名字就叫main
- 线程拥有setName getName Thread.crrurentName().getName() start sleep notify notifyAll wait yeild join
-
Thread中有哪些方法
-
sleep方法
- 作用:执行的时候会转化为静态的方法,会让当前线程睡眠
-
重写run方法,里面的异常不能抛出(原因是:父类没有抛出异常,所以子类重写不能抛出更多异常)
-
在主方法中调用t.interrupt方法会干扰t的睡眠。直接抛出异常,被t的try捕获,然后执行run方法之后的代码
-
stop方法:强行终止(已过时),缺点:容易丢数据,损坏数据。
-
合理的终止线程;看源码
-
yield(静态方法)方法:当前线程让出当前时间片。从运行状态回到就绪状态(仍然有可能再次获得时间片)
-
join方法、t.join()合并t线程。使当前main线程受阻。让t运行完毕后main才接着运行
-
什么时候会产生数据安全性问题
- 多线程
- 有共享数据
- 共享数据有修改的行为
-
-
Object中有哪些方法:
六、Thread中的几个方法的理解
1.start(开启线程,start是通过线程来调用run方法)
2.run 此run非彼run (不是在run方法实现线程的逻辑,而是thread.run(),这个run方法是直接调用了线程中的run)
3.yield:执行状态(运行状态)变为可执行态(就绪状态)
4.sleep(使当前线程由运行状态变成阻塞状态,若睡眠时其他线程调用了interrupt方法,会导致sleep抛出异常InterruptException)
5.join(会抢占资源)(保证当前线程在其他线程开始时会结束)(如下,A线程想运行的话,必须等B线程结束才能运行(将处于阻塞状态))
Thread A{
run{
new ThreadB.join();
}
}.start;
6.interrupt(中断线程)
7.wait/notify(从Object类继承下来的方法)
8.setPriority(设置线程优先级(只能在线程开始前设置)默认为5 【1,10】
9.stop(强制结束线程)
10、interrupt():中断方法
- 当线程使用wait()、join()、yield()等方法时会调用它
- 中断标志位:默认为false。当执行wait()方法后,标志位会变为true
- 阻塞的线程会不断的检查自己的标志位,
七、死锁
- 多个线程对相互资源的抢占。导致都处于等待状态而形成死锁。
八、守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如:后台记录操作日志,监控内存,垃圾回收等待
- 场景:作为定时任务,每天凌晨进行数据备份。
九、生产者和消费者
生产者:仓库,初始化仓库构造方法,一直生产的方法,实现Runnable接口
消费者:仓库,初始化仓库构造方法,一直消费的方法,实现Runnable接口
仓库:存放产品属性,get,set方法
管程法
package com.xd.线程;
import java.util.ArrayList;
import java.util.List;
/**
* ClassName:TwoDomn
* Package:com.xd.线程
* Description:
*
* @Date:2020/07/07 13:31
* @Author:xd
*/
public class TwoDomn {
public static void main(String[] args) {
Contain contain = new Contain();
new Thread(new Producer(contain)).start();
new Thread(new Cosumer(contain)).start();
}
}
//消费者
class Cosumer implements Runnable{
Contain contain;
public Cosumer(Contain contain) {
this.contain = contain;
}
@Override
public void run() {
for (int i = 1; i < 20; i++) {
contain.pop();
}
}
}
//生产者
class Producer implements Runnable{
Contain contain;
public Producer(Contain contain) {
this.contain = contain;
}
@Override
public void run() {
for (int i = 1; i < 20; i++) {
contain.push(new Chicken(i));
}
}
}
//产品
class Chicken{
int id;//产品编号
public Chicken(int id) {
this.id = id;
}
}
//容器
class Contain{
//容器计数器
List<Chicken> chickens=new ArrayList<Chicken>(10);
//放入产品
public synchronized void push(Chicken chicken){
//如果容器满了就需要等待消费者消费
if(chickens.size()==10){
//通知消费者消费
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果容器没满。则生产产品
chickens.add(chicken);
System.out.println("生产了第"+chicken.id+"只鸡");
//唤醒消费者
this.notifyAll();
}
public synchronized void pop(){
//如果容器没有产品就通知生产者生产
if(chickens.size()==0){
//通知生产者生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果容器里面还有则消费
Chicken remove = chickens.remove(chickens.size() - 1);
System.out.println("当前消费的鸡"+remove.id);
//唤醒生产者
this.notifyAll();
}
}
十、解决线程安全,方案选择
十一、定时器
1、java.util.Timer(底层)
2、Spring 中的SpringTask
十二、Callable详解
- 获取返回值得时候会导致当前线程阻塞