多线程指从软硬件上实现的多条执行流程的技术(多线程有CPU复制调度执行)
创建方式一:继承Thread类
// 定义线程类
public class MyThread extends Thread{
public void run(){
}
}
// 创建线程对象
MyThread t = new MyThread();
// 启动线程。
t.start();
注意:
- 启动线程必须是调用start方法,不是run方法(用run就是单线程)
- 不要把主线程任务放在启动子线程之前。
创建方式二:实现Runnable接口
// 定义一个可运行的类
public class MyRunnable implements Runnable {
public void run(){
}
}
// 创建线程对象
Thread t = new Thread(new MyRunnable());
// 启动线程
t.start();
package ThreadTest;
public class ThreadTest02 {
public static void main(String[] args) {
//3.创建任务对象
Runnable target = new MyRunnable();
//4.把任务对象交给一个线程对象处理
new Thread(target).start();
for (int i = 1; i < 5; i++) {
System.out.println("主线程main输出~~~~" + i);
}
}
}
创建方式二的匿名内部类写法:
public class ThreadTest04 {
public static void main(String[] args) {
// 创建线程对象,采用匿名内部类方式。
Runable target = new Runable(){
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println("t线程---> " + i);
}
}
new Thread(target).start();
//简写
Thread t = new Thread(new Runnable(){
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println("t线程---> " + i);
}
}
});
// 启动线程
t.start();
for(int i = 0; i < 100; i++){
System.out.println("main线程---> " + i);
}
}
}
创建方式三:利用Callable接口、FutureTask类实现
前两种的问题:线程执行完毕后不能用run方法直接返回结果
未来任务对象的作用?
1.是一个任务对象,实现了Runnable对象
2.可以再线程执行完毕之后,和未来任务对象调用get方法获取线程执行完毕后的结果
ThreadTest.java:
package ThreadTest;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadTest03 {
public static void main(String[] args) {
//3.创建一个callable对象
Callable<String> call = new MyCallable(100);
//4.把callable对象封装成一个FutureTask对象(任务对象)
//未来任务对象的作用?
//1.是一个任务对象,实现了Runnable对象
//2.可以再线程执行完毕之后,和未来任务对象调用get方法获取线程执行完毕后的结果
FutureTask<String> future = new FutureTask<>(call);
//5.把任务对象交给一个Thread对象
Thread thread = new Thread(future);
thread.start();
//new Thread(future).start();
//6.获取线程执行完毕后的结果
try {
System.out.println(future.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
MyCallable.java:
package ThreadTest;
import java.util.concurrent.Callable;
//1.让这个类实现Callable接口
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
//2.重写call方法
@Override
public String call() throws Exception {
//描述线程的任务,返回线程执行返回的结果
//1-n的和
int sum = 0;
for (int i = 1; i < n; i++) {
sum +=i;
}
return "线程求出了1到" + n + "的和是" + sum;
}
}
Thread常用方法
Thread m = Thread.currentThread();//哪个线程执行它,它就会得到哪个线程对象(主线程执行就得到主线程对象,再m.getName()就可以获得主线程名字)
Thread.sleep(1000);参数是毫秒
作用: 让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。
这行代码出现在A线程中,A线程就会进入休眠。
这行代码出现在B线程中,B线程就会进入休眠。
Thread.sleep()方法:间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。t
Thread.join();
void join() 将一个线程合并到当前线程中,当前线程受阻塞,加入的线程执行直到结束
void join(long millis) 接上条,等待该线程终止的时间最长为 millis 毫秒
void join(long millis, int nanos) 接第一条,等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒
一个线程.join(),当前线程会进入”阻塞状态
“。等待加入线程执行完
public class ThreadTest13 {
public static void main(String[] args) {
System.out.println("main begin");
Thread t = new Thread(new MyRunnable7());
t.setName("t");
t.start();
//合并线程
try {
t.join(); // t合并到当前线程中,当前线程受阻塞,t线程执行直到结束。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main over");
}
}
class MyRunnable7 implements Runnable {
@Override
public void run() {
for(int i = 0; i < 10000; i++){
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
线程安全
多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题
- 条件1:多线程并发。
- 条件2:有共享数据。
- 条件3:共享数据有修改的行为。
线程同步
线程同步常见方案:加锁
方式一:同步代码块
把访问共享资源的核心代码上锁,每次只允许一个线程加锁进入,执行完毕后自动解锁,其他线程才可以进来执行。
synchronized(同步锁){
// 线程同步代码块。
}
同步锁必须是多线程共享的数据!!
synchronized ("同步锁") {
if (this.balance >= money) {
//假设取钱成功,就从余额中减去
this.balance -= money;
System.out.println(name + "取出" + money + "元,余额" + this.balance + "元");
} else {
System.out.println(name + "取出" + money + "元,余额不足");
}
}
方式二:同步方法
把访问资源的核心方法上锁,一次保证线程安全
修饰符 synchronized 返回值类型 方法名称(形参列表){
操作共享资源的代码
}
public synchronized void drawMoney(double money) {
//搞清楚谁来取钱
String name = Thread.currentThread().getName();
//判断余额是否足够
if (this.balance >= money) {
//假设取钱成功,就从余额中减去
this.balance -= money;
System.out.println(name + "取出" + money + "元,余额" + this.balance + "元");
} else {
System.out.println(name + "取出" + money + "元,余额不足");
}
}
方式三:Lock锁
Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活,更方便,更强大
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。
//创建一个锁对象
private final Lock lk = new ReentrantLock();
...
public void drawMoney(double money) {{
//搞清楚谁来取钱
String name = Thread.currentThread().getName();
try {
lk.lock();//加锁
//判断余额是否足够
if (this.balance >= money) {
//假设取钱成功,就从余额中减去
this.balance -= money;
System.out.println(name + "取出" + money + "元,余额" + this.balance + "元");
} else {
System.out.println(name + "取出" + money + "元,余额不足");
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lk.unlock();//解锁
}
}
线程池
可以复用线程的技术
ThreadPoolExecutor:
注意事项:
临时线程的创建时间:新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
开始拒绝新任务的时间:核心线程和临时线程都在忙,任务队列也满了,新的任务过来是才会拒绝任务。
//1.通过ThreadPoolExecutor创建一个线程池对象
//new LinkedBlockingDeque<>(4)四个任务列
//new ThreadPoolExecutor.AbortPolicy():拒绝策略,新任务来了无法执行则抛出异常
ExecutorService pool = new ThreadPoolExecutor(3,5,8,
TimeUnit.SECONDS,new LinkedBlockingDeque<>(4),Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
Runnable target = new MyRunnable();
pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行;
pool.execute(target);
pool.execute(target);
pool.execute(target);//复用前面的核心线程
pool.execute(target);//复用前面的核心线程
//pool-1-thread-2=====> 输出6666666~
//pool-1-thread-3=====> 输出6666666~
//pool-1-thread-1=====> 输出6666666~
//pool-1-thread-3=====> 输出6666666~
//pool-1-thread-2=====> 输出6666666~
pool.shutdown();//等线程执行完,再关闭线程池
pool.shutdownNow();//不等线程执行完,直接关闭线程池
ExecutorService常用方法![](https://i-blog.csdnimg.cn/direct/c56336808ba945e39e782809f6c7f05c.png)
pool.execute():
//1.通过ThreadPoolExecutor创建一个线程池对象
//new LinkedBlockingDeque<>(4)四个任务列
//new ThreadPoolExecutor.AbortPolicy():拒绝策略,新任务来了无法执行则抛出异常
ExecutorService pool = new ThreadPoolExecutor(3,5,8,
TimeUnit.SECONDS,new LinkedBlockingDeque<>(4),Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
Runnable target = new MyRunnable();
pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行;
pool.execute(target);
pool.execute(target);
//开始任务列
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
//开始临时线程
pool.execute(target);
pool.execute(target);
//新任务的拒绝时机(抛异常)
pool.execute(target);
pool.shutdown();//等线程执行完,再关闭线程池
pool.shutdownNow();//不等线程执行完,直接关闭线程池
package thread_pool;
import java.sql.SQLOutput;
public class MyRunnable implements Runnable{
@Override
public void run() {
//任务做什么
System.out.println(Thread.currentThread().getName() + "=====> 输出6666666~");
try {
Thread.sleep(Integer.MAX_VALUE);//使核心线程一直被占用
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
新任务拒绝策略
pool.submit():
//1.通过ThreadPoolExecutor创建一个线程池对象
ExecutorService pool = new ThreadPoolExecutor(3,5,8,
TimeUnit.SECONDS,new LinkedBlockingDeque<>(4),Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//2.使用线程池处理Callable任务
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
Future<String> f3 = pool.submit(new MyCallable(300));
Future<String> f4 = pool.submit(new MyCallable(400));
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
package thread_pool;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n)
{
this.n = n;
}
@Override
public String call() throws Exception {
int sum = 0;
for(int i = 1;i <= n;i++)
{
sum += i;
}
return Thread.currentThread().getName() + "线程求出了1到" + n + "的和是" + sum;
}
}
pool.shutdown();
等线程执行完,再关闭线程池
pool.shutdownNow();
不等线程执行完,直接关闭线程池
Executors工具类实现线程池
Executors.newFixedThreadPool(3);
- 计算密集型任务:核心线程数量 = CPU的核数 + 1;
- IO密集型任务:核心线程数量 = CPU的核数 * 2;
这里的CPU核数为4;
大型并发系统环境种使用Executors如果不注意可能会出现系统风险
并发和并行
线程的通信
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了死锁。
出现死锁后,并不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
使用同步时应避免出现死锁。
死锁的解决方法:
1. 专门的算法、原则。
2. 尽量减少同步资源的定义。
3. 尽量避免嵌套同步。
原理:
当一个线程执行完成其所应该执行的代码后,手动让这个线程进入阻塞状态,这样一来,接下来的操作只能由其他线程来操作。当其他线程执行的开始阶段,再手动让已经阻塞的线程停止阻塞,进入就绪状态,虽说这时候阻塞的线程停止了阻塞,但是由于现在正在运行的线程拿着同步锁,所以停止阻塞的线程也无法立马执行。如此操作就可以完成线程间的通信。
所用的到方法:
wait():一旦执行此方法,当前线程就会进入阻塞,一旦执行wait()会释放同步监视器。
notify():一旦执行此方法,将会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先度最高的。
notifyAll() :一旦执行此方法,就会唤醒所有被wait的线程
说明:
这三个方法必须在同步代码块或同步方法中使用。
三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
这三个方法并不时定义在Thread类中的,而是定义在Object类当中的。因为所有的对象都可以作为同步监视器,而这三个方法需要由同步监视器调用,所以任何一个类都要满足,那么只能写在Object类中。
sleep()和wait()的异同:
-
相同点:两个方法一旦执行,都可以让线程进入阻塞状态。
-
不同点:1) 两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
2) 调用要求不同:sleep()可以在任何需要的场景下调用。wait()必须在同步代码块中调用。
2) 关于是否释放同步监视器:如果两个方法都使用在同步代码块呵呵同步方法中,sleep不会释放锁,wait会释放锁。