多线程
进程和线程的概念:
进程:程序是静止的,运行中的程序就是进程。
进程的特点:
动态性:进程是在运行的,会占用内存资源和CPU资源。
独立性:进程与进程之间各自占用各自的内存资源,互相独立。
并发性:假如计算机中只有一个CPU,CPU是单核的。那么同一个时刻,其实计算机中只能有一个进程在执行, 我们看到了很多进程在一起执行,原因是CPU会分时轮询依次 为每个进程服务,因为轮询的速度非常的块给我们的感觉 好像这些进程在同时运行,这就是进程并发!
并行:是同一个时刻,真的在同时运行。
线程: 一个进程可以包含多个线程。
线程的特点:
多态性,并发性
多线程(Thread):一个进程包含了多个线程就是多线程。
多线程可以提高程序的运行效率,多线程可以设计一些业务模型。多线程的执行会出现随机性,原因是因为他们是并发执行的!
创建线程的步骤:
(1)定义一个线程MyThread类继承Thread.
(2)在线程类MyThread类中重写run方法,申明线程需要完成的任务。
(3)创建MyThread线程类的对象代表了一个新线程
(4)调用线程的start方法启动线程
线程的创建方式一
public class ThreadDemo01 {
// 进程:ThreadDemo01
// 线程1:main方法,main本身就是一个线程,称为最牛逼的主线程!
// 线程2:t线程
public static void main(String[] args) {
// (3)创建MyThread线程类的对象代表了一个新线程
MyThread t = new MyThread();
// (4)调用线程的start方法启动线程。自动调用线程的run()方法
t.start();
for (int i = 0 ; i < 5 ; i++ ){
System.out.println("main输出:"+i);
}
}
}
// MyThread是一个线程类,不是线程,MyThread类是用来创建线程的!
// (1)定义一个线程MyThread类继承Thread.
class MyThread extends Thread{
// (2)在线程类MyThread类中重写run方法,申明线程需要完成的任务。
@Override
public void run() {
for (int i = 0 ; i < 5 ; i++ ){
System.out.println("线程输出:"+i);
}
}
}
线程Thread的常用API介绍。
(1)获取线程的名称 getName();
MyThread t1 = new MyThread(); t1.setName("子线程2号"); System.out.println(t1.getName());
(2)获取当前线程对象的API: Thread.currentThread();
哪个线程中执行这个API就可以得到哪个线程对象。
主线程的默认名称是:main
子线程的默认名称是:Thread_索引
Thread main = Thread.currentThread(); main.setName("最牛逼的线程");
(3)setName(String name)为线程设置名字
// 创建一个子线程 MyThread t = new MyThread();
t.setName("子线程1号");
线程取名字的优化方法_构造器
MyThread t = new MyThread("子线程1号");
t.start();
构造器必须重写父类有参构造方法
class MyThread extends Thread{ public MyThread(String name){ // public Thread(String name) super(name); }}
线程的创建方式二
步骤:
(1)定义一个线程任务类实现Runnable接口。
(2)重写run()方法。
(3)创建线程任务类对象,创建一个Thread线程对象包装线程任务对象。
(4)启动线程对象。
public class ThreadDemo01 {
public static void main(String[] args) {
//(3)创建线程任务类对象,。
RunnableTarget target = new RunnableTarget();
// Thread(Runnable target)
// Thread(Runnable target,String name)
// 创建一个Thread线程对象包装线程任务对象
Thread t = new Thread(target,"子线程1号");
t.start();
Thread t1 = new Thread(target,"子线程2号");
t1.start();
for (int i = 0 ; i < 5 ; i++ ){
System.out.println(Thread.currentThread().getName()+"=>输出:"+i);
}
}
}
// (1)定义一个线程任务类实现Runnable接口。
class RunnableTarget implements Runnable{
// (2)重写run()方法。
@Override
public void run() {
for (int i = 0 ; i < 5 ; i++ ){
System.out.println(Thread.currentThread().getName()+"=>输出:"+i);
}
}
}
Runnable的匿名内部类方式创建线程!!
public class ThreadDemo02 { public static void main(String[] args) { // 线程任务对象! Runnable target = new Runnable() { @Override public void run() { for (int i = 0 ; i < 5 ; i++ ){ System.out.println(Thread.currentThread().getName()+"=>输出:"+i); } } }; // Thread(Runnable target) // Thread(Runnable target,String name) // 创建一个Thread线程对象包装线程任务对象 Thread t = new Thread(target,"子线程1号"); t.start(); Thread t1 = new Thread(target,"子线程2号"); t1.start(); for (int i = 0 ; i < 5 ; i++ ){ System.out.println(Thread.currentThread().getName()+"=>输出:"+i); } } }
创建方式二的优缺点:
缺点:编码相对复杂,不能得到线程执行的结果。
优点:线程任务对象只是实现了Runnable接口,可以继续继承其他类,
可以继续实现其他接口。
线程任务对象的本身的功能可以增强,
非常适合做线程池。
非常适合做共享资源的访问。
线程的创建方式三
步骤: (1)创建一个线程任务对象,实现Callable接口,申明线程执行的结果类型。 重写call方法。 (2)创建一个未来任务对象FutureTask对象,包装Callable实现类对象。 (3)创建一个线程对象Thread来包装FutureTask对象 (4)启动线程。 (5)获取线程执行的结果。
public class ThreadDemo01 {
public static void main(String[] args) {
// (2)创建一个未来任务对象FutureTask对象,包装Callable实现类对象。
CallableTarger targer = new CallableTarger();
// 未来任务对象的功能:可以在线程执行完毕以后得到线程的执行结果。
// 未来任务对象实际上就是一个Runnable对象
FutureTask<String> task = new FutureTask<>(targer);
// public Thread(Runnable task)
// (3)创建一个线程对象Thread来包装FutureTask对象
Thread t = new Thread(task);
// (4)启动线程。
t.start();
for(int i = 1 ; i <= 5 ; i++ ){
System.out.println(Thread.currentThread().getName()+"=>输出:"+i);
}
// (5)获取线程执行的结果。
try {
String rs = task.get();
System.out.println(rs);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// (1)创建一个线程任务对象,实现Callable接口,申明线程执行的结果类型。
// 需求:让线程求和,求 1-5的和返回。
class CallableTarger implements Callable<String>{
@Override
public String call() throws Exception {
int sum = 0 ;
for(int i = 1 ; i <= 5 ; i++ ){
sum+=i;
System.out.println(Thread.currentThread().getName()+"=>输出:"+i);
}
return Thread.currentThread().getName()+"求和返回:"+sum;
}
}
线程安全问题及解决
什么是线程安全问题?
线程安全问题的原因:
多个线程在操作同一个共享资源的时候,可能出现线程安全问题。
目标:模拟线程安全问题的取款案例。
解决方法:
线程同步:为了解决线程安全问题。
线程同步的核心思想是什么呢?
让共享资源的访问实现先后访问。
核心点:把出现线程安全问题的代码给锁起来,每次只能有一个线程访问
其他线程必须等待这个线程执行完毕以后才可以进来访问。
线程同步的方式:
(1)同步代码块
public void draw(int drawMoney) { String name = Thread.currentThread().getName(); synchronized (this){ if(money >= drawMoney){ System.out.println(name+"来取钱,吐出"+drawMoney); // 更新余额 money -= drawMoney; System.out.println(name+"来取钱,取款后剩余"+money); }else{ System.out.println(name+"来取钱,但毛钱没有!!"); } } }
(2)同步方法
public synchronized void draw(int drawMoney) { String name = Thread.currentThread().getName(); if(money >= drawMoney){ System.out.println(name+"来取钱,吐出"+drawMoney); // 更新余额 money -= drawMoney; System.out.println(name+"来取钱,取款后剩余"+money); }else{ System.out.println(name+"来取钱,但毛钱没有!!"); } }
(3)lock锁
public void draw(int drawMoney) { String name = Thread.currentThread().getName(); lock.lock(); try{ if(money >= drawMoney){ System.out.println(name+"来取钱,吐出"+drawMoney); // 更新余额 money -= drawMoney; System.out.println(name+"来取钱,取款后剩余"+money); }else{ System.out.println(name+"来取钱,但毛钱没有!!"); } }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } }
同步代码块:
就是把出现线程安全问题的核心代码给锁起来,每次只允许一个线程访问。
格式:
同步锁:原则上可以是任意唯一对象。
按照规范:建议实例方法用this作为锁。
静态方法用字节码作为锁:类名.class。
线程控制:
线程睡眠(毫秒)
Thread类: public static void sleep(long millis); 让线程暂停一段时间
线程让步
for (int i = 0 ; i < 10 ; i++ ){ System.out.println(main.getName()+"=>"+i); if(i == 3){ // 当前线程礼让以下CPU资源 Thread.yield(); } }
线程优先级
默认优先级是:5
级别: 1 5(默认) 10
优先级高的线程可以更多机会得到CPU.
Thread类下:
public static final int MIN_PRIORITY = 1; public static final int NORM_PRIORITY = 5
;public static final int MAX_PRIORITY=10;
MyThread03 t1 = new MyThread03("子线程2号"); t1.start(); t1.setPriority(Thread.MAX_PRIORITY);
让出资源
for (int i = 0 ; i < 10 ; i++ ){ System.out.println(main.getName()+"=>"+i); if(i == 3){ // 当前线程让出CPU资源,必须t1线程完全跑完再执行自己! try { //t1是线程 t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }
public void wait()` : 让当前线程进入到等待状态 此方法必须同步锁对象调用.
public void notify()` : 唤醒当前锁对象上等待状态的一个线程 此方法必须锁对象调用.
public void notifyAll()` : 唤醒当前锁对象上等待状态的全部线程 此方法必须锁对象调用.
this wait(); this.notify(); this.notifyAll();