什么是线程,什么是进程?
进程:进行中的程序,系统中的最小执行单元,有独立的运行空间和内存空间
线程:进程中的最小执行单元,每一个进程至少包含一个主线程。
什么是并发,什么是并行?
并发:多个线程争抢系统资源,交替执行,单核单线程cpu即可执行。
并行:多个线程同时执行,互不影响,必须要在多线程CPU上才能执行。
实现多线程的根本目的:
根本目的是压榨系统资源,可以一定程度上的提高任务执行效率。
常用方法:
- 设置线程的名字:setName(String name)
- 获取线程的名字:Thread类中的getName(); Thread.currentThread.getName()
- 启动线程:start()
如何创建一个线程(4种方式):
- 自定义线程类继承Thread类
/**
* 1. 创建一个自定义的类继承Thread
* 2. 重写run()方法,方法内编写线程任务
* 3. 创建自定义线程的对象,执行start()方法
*/
public class MyThread01 extends Thread {
@Override
public void run(){
long startTime = System.out.currentTimeMillis();
for(int i = 1; i <= 100; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "用时" + (endTime - startTime));
}
}
public static void test1(){
//使用对象去点run方法也可以实现,但是与线程无关了
//public synchronized(同步) void start() 使用线程的开始方法start()会默认去找run方法
MyThread01 thread01 = new MyThread01();
thread01.setName("线程1");
MyThread01 thread02 = new MyThread01();
thread02.setName("线程2");
//这里如果统计时间,发现两个线程的时间比按次序打印的时间还要慢
//这是因为打印方法的out是static修饰的全局共用这一个,还有println方法是加上了 synchronized同步锁使得一次只有
//一个线程访问进来
thread01.start();
thread02.strat();
}
- 自定义线程类实现Runnable接口
/**
* 1. 创建一个自定义的类实现Runnable接口
* 2. 重写run()方法
* 3. 创建自定义线程类的对象
* 4. 创建Thread对象,使用自定义线程类的对象作为有参构造的参数
* 5. 执行Thread对象的start()方法
* 注意:Runnable是函数式的接口,可以使用lambda表达式做实现
*/
//第一种实现方式
public class MyThread02 implements Runnable{
@Override
public void run(){
long startTime = System.currentTimeMillis();
for(int i = 1; i <= 100; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "用时" + (endTime - startTime));
}
}
public static void test2(){
MyThread02 run = new MyThread02();
Thread thread1 = new Thread(run);
thread1.setName("线程1");
thread1.start();
Thrad thread2 = new Thread(run);
thread2.setName("线程2");
thread2.start();
}
//第二种实现方式
Runnable run = () -> {
for (int i = 1; i <= 100; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
};
Thread thread = new Thread(run);
thread.start();
3.自定义线程类实现Callable接口(jdk1.5+,可以抛出异常,可以有返回值)
/**
* 1. 创建一个自定义的类实现callable接口
* 2. 重写call()方法
* 3. 创建自定义线程的对象
* 4. 创建FutureTask类的 对象,把自定义线程对象作为构造方法的参数。
* 5. 创建Thread类对象,把FutureTask类的对象作为构造方法的参数
* 6. 调用Thread对象的start()方法;
*/
public class MyThread03 implements Callable<Long> {
@Override
public Long call() throws Exception {
long startTime = System.currentTimeMillis();
for (int i = 1; i <= 100; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
long endTime = System.currentTimeMillis();
return endTime - startTime;
}
}
private static void test4() {
//三层包装,第一步创建实现了Callable接口的实现类对象
MyThread03 myThread03 = new MyThread03();
//创建FutureTask对象将实现类对象作为参数放入FutureTask对象中
FutureTask<Long> futureTask = new FutureTask<>(myThread03);
//创建线程对象,将FutureTask对象作为参数,FutureTask对象继承了Runnable所以多态可以放入其中
Thread thread = new Thread(futureTask);
thread.start();
//使用该接口创建线程的好处
// 1. 可以获得返回值
// 2. 如果线程中有异常可以抛出
try{
//通过FutureTask对象可以获得call()方法的返回值
Long result = futureTask.get();
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
- 使用线程池创建线程
/**
* 1. 创建一个指定容量的线程池
* 2. 创建一个单例的线程池
* 3. 创建一个缓存型的线程池
*/
//使用固定大小的线程池
//可以理解为我们往线程池里面提交线程,但是线程池固定了大小,一次只会跑3个线程,当线程结束时,别的线程 才会进来
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//往线程池中放入6个线程
for(int i = 1; i <= 6; i++) {
//将线程提交到线程池中
fixedThreadPool.submit(new Runnable() {
@Override
public void run(){
for (int i = 0; i <= 100; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
//创建一个单例的线程池
//无论往线程池中丢入多少的数据,线程池一次只会执行一条线程
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for(int i = 1; i <= 3; i++){
singleThreadExecutor.submit(() -> {
for (int j = 1; j <= 100; j++){
System.out.println(Thread.currentThread().getName() + ":" + j);
});
}
//创建一个缓存型的线程池
//程序根据系统自身来定,能跑多少个线程就跑多少个线程
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for(int i = 1; i <= 10000; i++){
cachedThreadPool.submit(() -> {
for (int j = 1; j <= 100; j++){
System.out.println(Thread.currentThread().getName() + ":" + j);
}
}
}
线程的分类:
- 主线程:一般是主方法的线程
- 子线程:一般情况下都是用户创建的线程
- 守护线程:给主线程和子线程提供底层的技术支持,如果主线程或者子线程死亡,守护线程也没有存活的价值
线程的声明周期:
- 创建状态:创建出Thread的实例
- 就绪状态:线程对象执行start()方法之后,运行状态释放系统资源也会进入
- 运行状态: 线程获取到系统资源
- 等待用户输入、线程休眠都会进入阻塞
- 死亡状态: 线程执行完毕、手动停止线程
//线程的生命周期
private static void test1(){
Thread thread = new Thread(() -> {
for (int i = 1; i <= 100; i++){
System.out.println(i);
}
});
//-------------到此是创建状态-----------------
thread.start();
//-------------线程进入就绪状态---------------
//线程在系统内部获取cpu资源
//-------------进入运行状态-------------------
//任务执行完毕,或者手动停止任务
//-------------进入死亡状态-------------------
}
线程的调度:
- 调度的策略
- 分时调度
- 抢占式调度
- java使用的是抢占式的方式
- 设置线程的优先级 setPriority(int level):
//MIN_PRIORITY = 1;最小是1
//NORM_PRIORITY = 5;默认是5
//MAX_PRIORITY = 10;最大是10
Runnable runnable = () -> {
for(int i = 1; i <= 100; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
//设置线程1和线程2的优先级,线程2先跑完,1才会跑
thread1.setPriority(1);
thread2.setPriority(10);
thread1.start();
thread2.start();
- 线程的休眠 Thread.sleep(long time):
//这里注意的是sleep()方法是静态的方法,可以通过Thread类直接调用,不需要创建对象再调用
Runnbale runnable = () -> {
for(int i = 1; i <= 100; i++){
if(i == 50){
try{
//通过Thread类直接调用sleep方法,睡眠10秒
Thread.sleep(10000);
} catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName + ":" + i);
}
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnbale);
thread1.start();
thread2.strat();
- 线程的礼让 Thread.yield():
//当前线程释放系统资源,进入就绪状态,和其他线程再次争抢资源,这里注意这次礼让只会释放cpu资源并不是直接让另一个线程进来,而是再次争抢。
Thread thread1 = new Thread(() -> {
for (int i = 1; i <= 100; i++) {
if (i == 30) {
//当线程1打印到30的时候会释放cpu资源,然后再次争抢
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + ":" + i);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
});
thread1.start();
thread2.start();
- 线程的加入 join():
//主线程可以实现子线程的加入(使用子线程的对象调用join()方法),子线程加入之后,不再和主线程交替执行,主线程会进入阻塞一直到子线程执行完毕
Thread thread1 = new Thread(() -> {
for(int i = 1; i <= 100; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
});
thread1.start();
for(int i = 1; i <= 100; i ++){
if (i == 30) {
//当主线程执行到等于30时会让线程1加入进来,这里线程会执行完毕才会出来
try{
thread1.join();
} catch (IntertupttedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ":" + i);
}
线程锁的概念
- 对象互斥锁
- 当使用多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常
- 当一个线程拿到锁的时候,其他线程不能获得锁;获得锁的线程可以执行被锁住的代码;当这个线程 执行完毕这块代码后,释放锁,此时其他线程和他一起争抢锁
- 如何实现互斥锁:(在java中采用同步机制来实现互斥锁 )
- 同步方法
- 同步代码块
- 常用的锁:
- 对象锁:任意对象,如:Object lock = new Object();获得this
- 类锁:类的Class对象,如TicketsThread01.class
使用:
- 使用同步方法:
public class TicketsThread02 extends Thread {
//线程操作的对象,一定要设置为静态的,共用的代码
private static int ticketCount = 1000;
private static int count = 0;
//synchronized修饰的静态同步方法
public static synchronized void buyTicket() {
if (ticketCount > 0) {
count++;
System.out.println(Thread.currentThread().getName() + "抢了第"+ count +"张票!");
ticketCount--;
System.out.println("还剩" + ticketCount + "张票!");
}
}
//run方法中调用方法
@Override
public void run() {
while (true) {
buyTicket();
}
}
}
- 同步代码块实现同步:
public class TicketsThread03 extends Thread {
private static int ticketCount = 1000;
private static int count = 0;
//设置共用静态对象
private static Object lock = new Object();
@Override
public void run() {
while (true) {
//同步对象锁
//如果实现两个线程和其他的一个线程锁不同,只需要创建不同的对象放入其中
synchronized(lock) {
if (ticketCount > 0) {
count++;
System.out.println(Thread.currentThread().getName() + "抢了第"+ count +"张票!");
ticketCount--;
System.out.println("还剩" + ticketCount + "张票!");
}
}
}
}
}
这里注意一点:(如果我们使用Runnable这个借口就可以使用this关键词)
public class TicketsThread04 implements Runnable {
private static int ticketCount = 1000;
private static int count = 0;
@Override
public void run() {
while (true) {
synchronized(this) {
if (ticketCount > 0) {
count++;
System.out.println(Thread.currentThread().getName() + "抢了第"+ count +"张票!");
ticketCount--;
System.out.println("还剩" + ticketCount + "张票!");
}
}
}
}
}
//在主线程中创建一个runnable的对象
public static void main(String[] args) {
TicketsThread04 run = new TicketsThread04();
}
死锁:(由于两个线程同时需要对方的锁,导致无法释放自己的锁,所以导致程序一直这样不执行)
public static void main(String[] args) {
Object lock1 = new Object();
Object lock2 = new Object();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock1) {
Sysyem.out.println("我是线程1,我拿到lock1");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
sychronized (lock2) {
System.out.prinyln("我是线程1,我拿到了lock2");
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock2) {
System.out.println("我是线程2,我拿到了lock2");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.pirntln("我是线程2, 我拿到了lock1");
}
}
}
}).start();
}
线程通信:
线程的等待和通知机制
使用的方法:
1.wait():当前使用锁的线程进入阻塞状态
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized(lock) {
for (int i = 0; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
if (i == 50) {
try {
//使用wait()会使当前对象释放锁,并且进入阻塞状态
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized(lock) {
for (int i = 0; i <= 50; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
//使用notify()会通知被该锁阻塞的可以执行了
lock.notify();
//使用notityAll()方法会让多个被阻塞的对象都可以回到就绪状态,但是第一个被锁住的对象会先执行,阻塞相当于是一个队列
}
}
}