1.线程的创建
线程(Thread)是一个程序内部的一条执行流程。
多线程的创建方式一:继承Thread类
代码如下:
public class thread extends Thread {
//必须重写run方法
@Override
public void run() {
//描述线程的任务
for (int i = 0; i < 5; i++) {
System.out.println("线程是" + i);
}
}
}
public class main {
public static void main(String[] args) {
thread thread = new thread();
//调用start方法
thread.start();
}
}
多线程的创建方式二:实现Runnable接口
public class myrunnable implements Runnable{
@Override
public void run() {
//线程要执行的任务
for (int i = 0; i < 5; i++) {
System.out.println("线程1执行的i是"+i);
}
}
}
package src;
public class main {
public static void main(String[] args) {
//第一种
myrunnable runnable = new myrunnable();
new Thread(runnable).start();
//第二种
new Thread(new Runnable() {
@Override
public void run() {
//线程要执行的任务
for (int i = 0; i < 5; i++) {
System.out.println("线程2执行的i是"+i);
}
}
}).start();
//第三种
new Thread(()->{
//线程要执行的任务
for (int i = 0; i < 5; i++) {
System.out.println("线程3执行的i是"+i);
}
}).start();
for (int i = 0; i < 5; i++) {
System.out.println("main方法输出" + i);
}
}
}
多线程的创建方式三:实现Callable接口
public class mycallable implements Callable {
private final int n ;
public mycallable(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
int sum=0;
//获取i到n的累加
for (int i = 1; i <= n; i++) {
sum=sum+i;
}
return "获取i到n的累加值为"+sum;
}
}
public class main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
mycallable callable = new mycallable(100);
FutureTask<String> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
String result = futureTask.get();
System.out.println(result);
for (int i = 0; i < 5; i++) {
System.out.println("main方法输出" + i);
}
}
}
2.Thread的常用方法
3.线程安全
线程安全问题出现的原因?
存在多个线程在同时执行
同时访问一个共享资源
4.线程同步
线程同步的思想
加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。
1.同步代码块的方法
同步代码块作用:把访问共享资源的核心代码给上锁,以此保证线程安全。
synchronized(同步锁) {
访问共享资源的核心代码
}
ps:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。
使用规范:
建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象。
对于静态方法建议使用字节码(类名.class)对象作为锁对象。
public static void test(){
synchronized (src.account.class){
}
}
public void quqian() {
String name = Thread.currentThread().getName();
synchronized (this){
if (account>=100000){
System.out.println(name+"取钱了"+account);
this.account=account-100000;
System.out.println(name+"余额剩下"+account);
}else {
System.out.println(name+"取钱但是不够");
}
}
}
2.同步方法
作用:把访问共享资源的核心方法给上锁,以此保证线程安全。
修饰符 synchronized 返回值类型 方法名称(形参列表) {
操作共享资源的代码
}
原理 :每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行
是同步代码块好还是同步方法好一点?
范围上:同步代码块锁的范围更小,同步方法锁的范围更大。
可读性:同步方法更好。
public static synchronized void test(){
}
public synchronized void quqian() {
String name = Thread.currentThread().getName();
if (account>=100000){
System.out.println(name+"取钱了"+account);
this.account=account-100000;
System.out.println(name+"余额剩下"+account);
}else {
System.out.println(name+"取钱但是不够");
}
}
3.Lock锁
Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。
代码如下:
//实例化锁
private final Lock lk=new ReentrantLock();
public synchronized void quqian() {
try{
//加锁
lk.lock();
String name = Thread.currentThread().getName();
if (account>=100000){
System.out.println(name+"取钱了"+account);
this.account=account-100000;
System.out.println(name+"余额剩下"+account);
}else {
System.out.println(name+"取钱但是不够");
}
}
catch (Exception e){
e.printStackTrace();
}
finally {
//解锁
lk.unlock();
}
}
5.线程通信
当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。
线程通信的常见模型(生产者与消费者模型)
生产者线程负责生产数据
消费者线程负责消费生产者生产的数据。
注意:生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,再通知生产者生产!
6.线程池
线程池就是一个可以复用线程的技术。
不使用线程池的问题
用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的, 而创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。
创建线程池
JDK 5.0起提供了代表线程池的接口:ExecutorService。
方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。
方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。
1.方式一使用ExecutorService的实现类:
ThreadPoolExecutor构造器
实例代码如下:
public static void main(String[] args) {
ExecutorService pool = new ThreadPoolExecutor(3,5,5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
}
1、临时线程什么时候创建?
2、什么时候会开始拒绝新任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。
线程池处理Runnable任务
示例代码如下:
public static void main(String[] args) {
ExecutorService pool = new ThreadPoolExecutor(3,5,4, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
myrunnable runnable = new myrunnable();
pool.execute(runnable);
pool.execute(runnable);
pool.execute(runnable);
//线程等待
pool.execute(runnable);
pool.execute(runnable);
pool.execute(runnable);
pool.execute(runnable);
//新建线程
pool.execute(runnable);
pool.execute(runnable);
//拒绝线程
pool.execute(runnable);
//关闭线程
//等线程执行完毕关闭线程
pool.shutdown();
//不等线程执行完毕立即关闭线程
//pool.shutdownNow();
}
线程池处理Callable任务
callable相比runnable多了一个返回结果,调用submit函数即可获取。
public class factory {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = new ThreadPoolExecutor(3,5,4, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
myrunnable runnable = new myrunnable();
callable callable = new callable();
Future<String> future = pool.submit(callable);
System.out.println(future.get());
}
}
2.方式二:使用Executors(线程池的工具类)
是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。
注意 :这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象。
不建议使用Executors里的方法,因为请求队列和创建线程数都是最大,容易OOM内存溢出。
7.并发和并行
8.线程的生命周期
也就是线程从生到死的过程中,经历的各种状态及状态转换。
理解线程这些状态有利于提升并发编程的理解能力。
Java线程的状态
Java总共定义了6种状态
6种状态都定义在Thread类的内部枚举类中。