并行:同一时刻同时处理多个任务。简单理解:一边......一边......
并发:同一时间段内,多个任务交替执行。简单理解:一会......一会......
进程是指正在运行的程序。
线程是进程中执行运算的最小单位,一个进程执行过程中可以产生多个线程,线程必须在某个进程内执行。
一个进程中至少有一个线程,也可以有多个线程,这样的程序就称为多线程程序。
进程:有独立的内存空间,进程中的数据存放空间(堆和栈)是独立的,至少有一个进程。
线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小得多。
资源分配给进程,同一进程的所有线程共享该进程的所有资源(如内存)。
处理器分配给线程,即真正在处理器上运行的是线程。
Java程序中至少包含两个线程:一个main()线程,另外一个是垃圾回收机制线程。
本质区别在于,每个进程拥有一套自己的变量,而线程则是共享数据。
分时调度:所有线程轮流拥有CPU的使用权,平均分配每个线程占用CPU的时间。
抢占式调度:优先让优先级高的线程使用CPU,如果优先级相同,会随机选择一个线程。
每个程序至少自动拥有一个线程,称为主线程,当程序加载到内存中时启动主线程,main()就是主线程的入口。
继承Thread类,重写run(),通过start()启动线程。
public class MyThread extends Thread{
public MyThread(String name){
super(name);
}
@Override
public void run() {
//run方法里面写线程具体的任务
for (int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName());
}
}
}
public class MyThreadTest {
public static void main(String[] args) {
//创建MyThread对象
MyThread myThread = new MyThread("我的线程");
//开启一个新的线程 实际上是去执行重写的run方法
myThread.start();
for (int i = 0; i < 100; i++){
System.out.println("主线程运行了" + i + "次");
}
}
}
实现Runnable接口的方式创建
实现Runnale接口,重写run()方法
public class RunnableDemo {
public static void main(String[] args) {
Thread.currentThread().setName("主线程");
Thread t = new Thread(new MyRunnaable());
t.setName("子线程");
t.start();
//子线程运行后,主线程继续运行
for (int i = 0; i < 10; i ++){
System.out.println(Thread.currentThread().getName() + "运行了" + i + "次");
}
}
}
class MyRunnaable implements Runnable{
@Override
public void run() {
//写子线程要执行的任务
//输出1-100
for (int i = 0; i < 100; i++){
System.out.println(Thread.currentThread().getName() + "运行了" + i + "次");
}
}
}
Runnnable对象仅仅作为Thread对象的target,Runnale实现类里包含的run()仅仅作为线程的执行体,实际的线程对象依然是Thread实例,只是该实例负责执行该其target的run()。
实现Callable接口的方式创建
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class CallableDemo implements Callable {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 10; i++){
sum += i;
}
return sum;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable callable = new CallableDemo();
FutureTask futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());
}
}
Callable和Runnale比较相似,Runnable不返回结果,也不能抛出检查异常,Callable没有返回结果。
FutureTask是一个实现了Future和Runnable的类,它可以表示一个异步计算的结果,FutureTask可以包装一个Callable和Runnale对象,通过get()获取异步计算的结果。
Callable call = new Myacallable();
FutureTask task = new FutureTask(call);
Thread thread = new Thread(task);
thread.start();
public FutureTask(Runnable runnable, V result) :创建一个FutureTask在运行时执行给定的Runnable,并且让get在完成时返回结果。
public Thread():创建一个新线程
public Thread(String name):分配一个指定名字的线程
public Thread(Runnable runnable):分配一个带有指定目标的线程
public Thread(Runnable runnable):分配一个带有指定目标的线程,并指定名字
守护线程类似于一个默默无闻的小跟班,依赖于它所守护的那个线程,它守护的那个线程结束了,它也就结束了。如果所有线程都执行完毕了,没有要执行的了,它也会结束。
守护线程的终止自身无法控制,因此不能把IO、File等重要操作逻辑分配给它。
GC垃圾回收线程:
public class WatchThread extends Thread{
//创建守护线程,检查C盘容量,少于1G发出预警
File file = new File("C:");
@Override
public void run() {
while (true){
long space = file.getFreeSpace();
System.out.println("C盘剩余空间:" + space / 1024 / 1024 / 1024 + "G");
if(space < 1024 * 1024 * 1024){
System.out.println("C盘空间不足");
}
try {
//暂停5秒后继续监视
sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
WatchThread thread = new WatchThread();
//设置为守护线程,在start之前设置
thread.setDaemon(true);
thread.start();
}
}
为了解决线程安全问题,可以使用线程同步的思想,最常见的方案就是加锁。意思还是每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动释放锁,然后其他先才能加锁再进来。
就是把访问共享数据的代码锁起来
synchronized(锁对象){
// ...访问共享数据的代码...
}
锁对象:对象的同步锁只是一个概念,可以想象成在任意对象上标记了一个锁。
如何选择锁对象:
CountDownLatch:一种同步辅助工具,允许一个或多个线程等待,直到其他线程中执行的一组操作完成。
CountDownLatch latch = new CountDownLatch(10);
latch.countDown(); // 每一个线程运行结束 数量减一
latch.await(); // 等待这个计数器中数字清空
使用synchronized修饰的方法就是同步方法,保证线程A执行的时候其他线程只能在方法外等待。就是把一个方法锁住,一个线程调用这个方法,另一个线程就执行不了,只有等上一个线程执行结束,下一个线程调用才能继续执行。
同步方法的锁对象:实例方法的锁对象是类的字节码对象(类名.class),静态方法的锁对象是this,也就是方法的调用者。
同步方法是将方法中的所有代码锁住,同步代码块只是把方法中的部分代码锁住。