创建线程的方式
- 继承Thread类,重写run方法
public class MyThread extends Thread{
int i=0;
@Override
public void run() {
for (int j = 0; j < 100; j++) {
i++;
System.out.println(getName()+" "+i);
}
}
}
- 实现Runnable接口,重写run方法,再创建实例,以该类的实例为Thread的参数来创建线程
public class MyRunnable implements Runnable{
int i=0;
@Override
public void run() {
for (int j = 0; j < 100; j++) {
i++;
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
if (i==20){
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable,"线程1").start();
new Thread(myRunnable,"线程2").start();
}
}
}
}
- 实现Callable接口,重写call方法,再创建该对象的实例,以该对象的实例为FutureTask的参数来创建FutureTask对象,再以FutureTask对象作为Thread的参数创建并启动线程。通过FutureTask对象.get()获取线程的返回值
public class MyCallable implements Callable<Integer> {
int i=0;
@Override
public Integer call() throws Exception {
for (int j = 0; j < 100; j++) {
i++;
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> integerFutureTask = new FutureTask<>(myCallable);
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
if (i==20){
new Thread(integerFutureTask,"有返回值的线程1").start();
}
}
try {
System.out.println("返回值:"+integerFutureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
- 通过线程池创建线程
public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
MyRunnable myRunnable = new MyRunnable();
for (int i = 0; i < 10; i++) {
executorService.execute(myRunnable);
}
executorService.shutdown();
}
}
不同线程创建方法的优劣
使用Runnable和Callable实现多线程和基础Thread类实现多线程的区别
- 实现接口实现多线程的方式还可以再继承其他的类;
- 实现接口的方式多个线程可以共享同一个target对象,非常适合多个线程处理同一个资源的情况;
- 实现接口实现多线程的方程获取当前线程时,比继承Thread类稍微复杂一些,需要使用Thread.currentThread()获取当前线程,而继承Thread类的只需要使用this即可。
线程同步的方式
- 使用同步代码块的方式(给代码添加synchronized关键字)
public class MyRunnable implements Runnable{
int i=0;
@Override
public void run() {
for (int j = 0; j < 100; j++) {
try {
Thread.currentThread().sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this){
i++;
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
if (i==20){
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable,"线程1").start();
new Thread(myRunnable,"线程2").start();
}
}
}
}
- 同步方法方式
将run方法需要执行的代码提取到一个方法中,在方法前添加synchronized关键字。
其中方法分为静态方法和普通方法
静态方法锁住的是整个类,而普通方法只能锁住当前的对象
public class MyThread extends Thread{
static int i=0;
@Override
public void run() {
method();
}
public static synchronized void method(){
for (int j = 0; j < 100; j++) {
try {
sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
if (i==50){
new MyThread().start();
new MyThread().start();
}
}
}
}
- Lock方式实现线程同步
对操作不安全的代码进行加锁,操作完成后进行解锁,从而实现线程同步
public class MyCallable implements Callable<Integer> {
static int i=0;
Lock l=new ReentrantLock();
@Override
public Integer call() throws Exception {
for (int j = 0; j < 100; j++) {
try {
Thread.currentThread().sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
l.lock();
i++;
l.unlock();
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
if (i==20){
FutureTask<Integer> integerFutureTask1 = new FutureTask<>(myCallable);
new Thread(integerFutureTask1,"有返回值的线程1").start();
FutureTask<Integer> integerFutureTask2 = new FutureTask<>(myCallable);
new Thread(integerFutureTask2,"有返回值的线程2").start();
}
}
}
}
进程与线程的区别
- 线程是“进程代码段”的一次执行流程。一个进程由一个或者多个线程组成,一个进程至少拥有一个线程;
- 线程是CPU调度的最小单位,进程是操作系统分配资源的最小单位,线程的尺度划分小于进程,使得多线程程序的并发性高;
- 线程是出于高并发的调度诉求从进程内部演进而来。线程的出现既充分发挥了CPU的计算性能,又弥补了进程调度过于笨重的问题;
- 进程之间是相互独立的,但是进程内部的各个线程之间并不完全独立。各个线程之间共享进程的方法区内存、堆内存、系统资源。
- 切换速度不同:线程上下文切换比进程上下文切换要快。所以有时候线程也称为轻量级进程。
守护线程
- 守护线程必须在启动前将其守护状态设置为true,启动之后不能再将用户线程设置为守护线程,否则会抛出异常;具体操作就是线程在start之前,调用线程实例的setDaemon(true),设置其daemon属性为true;
- 守护线程存在被JVM强行终止的风险,所以在守护线程中尽量不要去访问系统资源,JVM运行结束时守护线程自动终止;
- 守护线程创建的线程也是守护线程,所以想要在守护线程中创建用户线程,想要在新线程启动前,调用setDaemon(false)方法,将新线程是守护状态改为false。
为什么使用线程池?
- 降低资源消耗,提高线程的利用率,降低线程创建和销毁线程的消耗;
- 降低响应时间,任务来了,直接就有线程可以执行,而不是先创建再执行;
- 提高线程的可管理性,线程是稀缺资源,使用线程池可以统一分配调优监控。