线程和进程:
什么是线程,什么是进程?
- 作业(JOB)计算机的一次执行任务,对于单核CPU来说,作业就是线程为了完成某一个任务所占用一段时间的震荡时钟。
- 线程(Thread)是计算机执行计划任务的最小单元(携带了很多的作业(JOB)),线程中的作业基本上都是同步的,如果是异步,那么就会大大降低应用程序的效率。
- 进程(Process)很多线程的集合,进程在操作系统级别是管理线程的工具,也就是说,一个进程里面有很多个线程。线程的名字,我们可以自己给出,但是进程必须是由操作系统,给出一个独有的进程号,这个有进程号的进程就是一系列线程任务的容器。
一个进程就是一个应用程序,其实这种说法也对也不对,因为很多人就是这样认知的,有的应用程序就是一个进程独立完成所有任务,但是有的应用程序会启动多个进程(将不同的任务点分配给不同的进程)。
当我们在java中启动一个main方法,其实就是启动了一个进程,而这个进程中只有一个叫main的主线程,我们真正启动的时候,就是在黑框启动了一个java.exe的程序,这个程序中有且只有一个名叫main的线程。
一个线程能控制其他线程的启动和结束,就叫做主线线程。
线程的创建和启动:
1、继承Thread
启动方法:start()方法
作业方法:run()方法
- 执行strat()和调用run()的区别:执行strat()方法就是启动子线程,让子线程自己执行自己的run()方法,这就是多线程启动,但是直接执行run()方法却是用main方法调用了子线程的run()方法,只有一个线程(主线程main)在工作。
2、实现Runnable接口
- Runnable接口只有一个方法(run()),所以你可以理解为它就是个任务,他并不是个线程,最终启动还是要用Thread的start()方法来启动子线程,就是和你要在Thread里面重写的run()方法一样。
- 启动该线程的时候的写法:在Thread的构造方法中将实现Runnable的类对象传进去,最后调用start()方法启动线程。
- 这种创建线程的好处就是将继承的能力留给更重要的类去使用,因为java是单继承多实现的,所以用接口创建线程还是很有好处的。
3、实现Callable接口,有返回值的线程:
- 重写接口里面的call()方法,其中Callable接口中只有一个call()方法,call()方法只是提供了一个返回值,此时需要一个任务来包裹这个返回值,java中提供了这个实现类就是FutureTask,FutureTask在源码中实现了RunnableFuture接口,这个接口它继承了Runnable接口和Future接口,当我们在执行Runnable接口中的run()方法的时候,将Callable中的返回值设置成为一个属性,然后可以通过调用get()方法取出这个属性,最后在main()方法中执行和Runnnable接口创建线程一样的过程来启动线程,从而实现了这种有返回值的创建线程的过程。
4、线程池
Java通过Executors提供四种线程池,分别为:
1、newSingleThreadExecutor
- 创建一个单线程的线程池,它只会用一个工作线程来执行任务,保证所有任务按照指定顺序执行。
2、newFixedThreadPool
- 创建一个固定线程数的线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3、newScheduledThreadPool
- 创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。
4、newCachedThreadPoo
- 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
线程常用的方法:
currentThread():静态方法,返回当前正在执行的线程对象的引用
sleep():静态方法,当前线程睡眠,参数是毫秒级别
wait():Object的方法,设置当前线程等待,调用哪个对象的wait()方法,就要给哪个对象加锁,调用同一个对象的notify()方法,可以随机唤醒调用同一个对象wait()方法的线程,注意是随机的,调用notifyAll()方法,唤醒所有调用wait()的线程。
java中线程的6中状态:
- 创建(NEW):新创建了一个线程对象,但还没有调用start()方法。
- 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态的称为“运行”,线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
- 阻塞(BLOCKED):比如当前线程可能没有抢到同步锁,或者线程需要IO动作所以被阻塞,调用sleep()方法进入阻塞状态。
- 等待(WAITING):调用wait()方法,没有参数此时只能用notify()方法唤醒。
- 超时等待(TIMED_WAITING):调用wait()方法设置参数,在参数规定的时间中等待。
- 终止(TERMINATED):表示该线程已经执行完毕。
sleep()和wait()方法的区别:
1、wait()方法是Object的方法。
sleep()方法是Thread的方法。
2、wait()方法是只能在同步块中使用。
sleep()方法是可以在任何场景下使用。
3、wait()方法可以随时唤醒。
sleep()方法只能睡够才可以继续运行。
4、wait()方法会释放锁资源
sleep()方法不会释放锁资源。
线程加锁后为什么就会同步安全了?
当一个进程在cpu下的时候,该进程下的所有线程就会在这个CPU下执行。此时对于这个进程下所有线程来说,就是单一时钟在震荡,就算两个线程同时到达,CPU也只会选择其中一个线程去执行,所以给线程加锁是安全的,不用担心两个线程同时到达同时执行,因为单一震荡时钟的CPU在同一时刻下,只能执行一个线程,所以带锁的线程抢占后立刻加锁,后面的线程要操作加锁的对象的时候,只能被阻塞。
线程的锁机制:
synchronized:重量锁,内容以一个对象或一组类型。当某一个线程率先抢占到锁的对象的时候,以后别的线程就不可以修改这个对象的内容。
synchronized可以锁住的东西(共用的资源)
- 共用的对象:多个线程操作同一对象的时候
- 共用的类型:当多个线程操作统一种类型的对象的时候
- 共用的方法:synchronized可以锁住多个线程将要共用的方法
synchronized解析:
- 同步锁,当多个线程要操作同一对象(同一数据)的时候,锁住的其实是对象,其他没抢到锁的线程也就操作不了共享对象了,然后没抢到锁的线程被阻塞,所以多线程下才能实现同步执行。代码层面理解是必须要有共享对象,不然synchronized不可能实现多线程的同步。
- 当有多个共享对象的时候,synchronized是锁不住的,因为它只能锁住单一对象,所以并不灵活。
共享数据类:
//共享数据
public class ObjectTest {
public void foo() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
线程:
public class ThreadTest implements Runnable {
// 设置共享数据,保证多线程操作的同一对象
private ObjectTest test;
public ThreadTest(ObjectTest test) {
this.test = test;
}
@Override
public void run() {
// 锁住共享对象
synchronized (test) {
test.foo();
}
}
}
主方法启动:
public class Main {
public static void main(String[] args) {
ObjectTest test = new ObjectTest();
Thread thread0 = new Thread(new ThreadTest(test), "线程1");
Thread thread1 = new Thread(new ThreadTest(test), "线程2");
thread0.start();
thread1.start();
}
}
结果打印:
ReentrantLock常用方法:
- lock():开启锁
- unlock():释放锁
ReentrantLock(重入锁): java.util.concurrent线程并发包下的类,就只为了实现线程同步,再没有共享对象的情况下或者共享对象很多的情况下实现同步过程。
- 重入锁,当要操作多个共享数据的时候,lock锁很好用,它不用锁住共享对象,其他线程只要拿到同一个lock,那么就会实现多线程同步执行。
- 重入锁最大的意义是可以重复加锁,但是要注意释放,要不然当前线程执行完,其他线程就会一直等待。
线程:
import java.util.concurrent.locks.ReentrantLock;
public class LockTest implements Runnable{
// 传入lock锁
private ReentrantLock lock;
public LockTest(ReentrantLock lock) {
this.lock = lock;
}
@Override
public void run() {
// lock加锁,注意这里加锁根本没有共享对象
lock.lock();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " : " + i);
}
// lock释放锁
lock.unlock();
}
}
主方法启动:
import java.util.concurrent.locks.ReentrantLock;
public class Main {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread thread0 = new Thread(new LockTest(lock), "lock");
Thread thread1 = new Thread(new LockTest(lock), "test");
thread0.start();
thread1.start();
}
}
结果打印: