什么是线程?
线程是一个程序内部的一条执行流程。 程序如果只有一条执行流程,那这个程序就是单线程。
什么是多线程?
多线程是从软硬件上实现多条执行流程的技术(多线程是由CPU调度执行的)
创建线程有两种方法
创建类,继承Thread 并重写run方法。 main方法是主线程,其他线程是子线程。
不要把主线程任务放在启动子线程之前,这样导致每次都是主线程任务完成之后,才会启动子线程任务。
创建线程的第二种方法,实现一个Runnable接口的类
先创建一个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 = 0; i < n; i++) {
sum = i+sum;
}
return "0到"+n+"之和为"+sum;
}
}
public class ThreadTest01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//先创建一个Callable对象
Callable<String> myCallable = new MyCallable(100);
//把Call对象封装成一个FutureTask对象
//未来任务对象的作用?
//是一个任务对象,实现了Runnable接口
//可以在线程执行完毕后,用外来任务对象调用get方法获取线程执行完毕执行后的结果
FutureTask<String> f1 = new FutureTask<String>(myCallable);
//获取线程执行完毕之后的返回结果
new Thread(f1).start(); //启动线程
//如果线程执行到这,假如上面线程还没有执行完毕,会
//等着上面线程执行完毕之后,才能获取结果
String s = f1.get();
System.out.println(s);
}
}
如何通过构造器为当前线程设置名字
先到Mythead类中,因为Thread有提供名字的构造器,通过super()调用父类的名字
public class MyThread extends Thread{
public MyThread(String name){
super(name);
}
@Override
public void run() {
Thread thread = Thread.currentThread();
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
Thread t1 = new MyThread("线程1");
通过线程.set() 方法为线程起名字
Thread t1 = new MyThread();
//在线程启动之前,为线程起名字
t1.setName("线程1");
t1.start();
System.out.println(t1.getName());
Thread t2 = new MyThread();
//在线程启动之前为线程起名字
t2.setName("线程2");
t2.start();
System.out.println(t2.getName());
Thread t1 = new MyThread();
t1.setName("线程1");
t1.start();
//join方法的作用,让当前调用这个方法的线程先执行完,再去执行其他线程
t1.join();
Thread t2 = new MyThread();
t2.setName("线程2");
t2.start();
t2.join();
线程安全问题
多个线程,同时访问同一个共享资源,且存在修改该资源。
案例 小明和小红同时取共同账户的钱
先创建一个共享类,Account
public class Account {
private int id;
private double money;
public Account(int id, double money) {
this.id = id;
this.money = money;
}
public Account() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public void drawMoney(double money) {
String name = Thread.currentThread().getName();
//细节,一定是this.money() 账户的钱, 如果写if(money >= 10000),
// 这个是传入的参数money,是个固定值
if (this.money >= 10000){
System.out.println(name + "取钱" +money);
this.money = this.money - money;
System.out.println(this.money);
}else{
System.out.println("余额不足");
System.out.println(this.money);
}
}
}
在测试类中,创建两个线程
public static void main(String[] args) {
Account account = new Account(001,10000);
new Thread(new Runnable() {
@Override
public void run() {
account.drawMoney(10000);
}
},"小明").start();
new Thread(() -> {
account.drawMoney(10000);
},"小红").start();
}
解决线程安全问题方法
线程同步,让多个线程先后依次访问共享资源。 加锁:每次只允许一个线程加锁,加锁后才能访问,访问完毕后自动解锁。
加锁的三种方式
1.同步代码块
2.同步方法
3.Lock锁
同步代码块:把访问共享资源的核心代码块上锁,以此保证线程安全。
静态方法用类名.class 作为锁对象
public static void test(){
synchronized (Account.class){
}
}
实例方法用this作为锁对象
public void drawMoney(double money) {
synchronized (this) {
String name = Thread.currentThread().getName();
//细节,一定是this.money() 账户的钱, 如果写if(money >= 10000),
// 这个是传入的参数money,是个固定值
if (this.money > 1000){
System.out.println(name + "取钱" +money);
this.money = this.money - money;
System.out.println(this.money);
}else{
System.out.println("余额不足");
System.out.println(this.money);
}
}
同步方法:把访问共享资源的核心方法上锁,以保证线程的安全
同步代码块性能好一点
第三种,Lock锁
Lock锁是JDK5开始提供的一个新的锁操作,通过它,可以创建出锁对象进行加锁和解锁,更灵活更方便。
Lock是接口,不能直接实例化,可以采用他的实现类ReentrantLock来构建锁对象
public class Account {
private int id;
private double money;
//一个账户有一个锁对象,用final修饰确定锁对象是固定的不能进行修改。
private final Lock lk = new ReentrantLock();
public Account(int id, double money) {
this.id = id;
this.money = money;
}
public Account() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public void drawMoney(double money) {
try {
lk.lock(); //加锁
String name = Thread.currentThread().getName();
if (this.money >= 10000){
System.out.println(name + "取钱" +money);
this.money = this.money - money;
System.out.println(this.money);
}else{
System.out.println("余额不足");
System.out.println(this.money);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lk.unlock(); //解锁 这样比较安全
}
}
线程通信
当多个线程共同操作共享资源时,线程间互相通过某种方式告知自己的状态,避免无效资源的争夺。
线程通信的前提是保证线程安全。
锁是可以跨方法的
public class Desk {
private ArrayList<String> list = new ArrayList<>();
//放包子
public synchronized void put(){
try {
if (list.size() == 0){
list.add("包子");
String name = Thread.currentThread().getName();
System.out.println(name + "生产了包子");
Thread.sleep(2000);
this.notifyAll();
this.wait();
}else {
this.notifyAll();
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//拿包子
public synchronized void get(){
try {
if (list.size() == 1){
String name = Thread.currentThread().getName();
list.clear();
System.out.println(name+"吃包子");
this.notifyAll();
this.wait();
}else {
this.notifyAll();
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Desk desk = new Desk();
//厨师1
new Thread(() -> {
while (true){
desk.put();
}
},"厨师1").start();
//厨师2
new Thread(() -> {
while (true){
desk.put();
}
},"厨师2").start();
//厨师3
new Thread(() -> {
while (true) {
desk.put();
}
},"厨师3").start();
//消费者1
new Thread(() -> {
while (true){
desk.get();
}
},"消费者1").start();
//消费者2
new Thread(() -> {
while (true){
desk.get();
}
},"消费者2").start();
}
线程池
线程池就是复用线程的技术
不使用线程池出现的问题: 用户每发起一个请求,后台就需要一个新线程来处理,下次新任务来了又需要线程处理,创建新线程的开销很大,并且请求过多时,会产生大量的线程出来,严重影响系统性能问题。
谁代表线程池? jdk5.0起提供了代表线程池的接口ExecutorService
任务队列必须是实现Runabble和Cabble接口
什么时候创建临时线程?
新任务提交时发现核心线程都在忙,任务队列已经满了,并且还可以创建临时线程,此时才会创建临时线程。
什么时候开始拒绝新任务?
核心线程和临时线程都在忙,任务队列已经满了,新的任务过来时才会开始拒绝任务。
线程池实现Runnable接口
1.创建Runnable实现类
public class MyRunnable1 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+ "输出666");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.创建线程池测试类
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService pool = new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
Runnable target = new MyRunnable1();
pool.execute(target);//线程池会自动创建一个新线程,自动处理任务,自动执行。
pool.execute(target);//线程池会自动创建一个新线程,自动处理任务,自动执行。
pool.execute(target);//线程池会自动创建一个新线程,自动处理任务,自动执行。
pool.execute(target); //复用前面核心线程
pool.execute(target); //复用前面核心线程
//实际开发中,一般不会关掉线程
// pool.shutdown();//等线程池的任务完毕后,关掉线程
pool.shutdownNow();//立即关掉线程,当核心线程执行完之后关闭,不会给复用线程的机会
}
}
创建临时线程,需要把Thread.sleep(Integer.MAX_VALUE),这样任务会拖住线程
public static void main(String[] args) {
ExecutorService pool = new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
Runnable target = new MyRunnable1();
pool.execute(target);//线程池会自动创建一个新线程,自动处理任务,自动执行。
pool.execute(target);//线程池会自动创建一个新线程,自动处理任务,自动执行。
pool.execute(target);//线程池会自动创建一个新线程,自动处理任务,自动执行。
pool.execute(target); //会进入到任务队列 任务队列设置值为5,所以会有5个任务排队
pool.execute(target); //会进入到任务队列
pool.execute(target); //会进入到任务队列
pool.execute(target); //会进入到任务队列
pool.execute(target); //会进入到任务队列
pool.execute(target); //当任务队列满了,会创建临时线程
pool.execute(target); //创建临时线程
}
线程池实现Callable接口
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
Future<String> submit = pool.submit(new MyCallable(100));
Future<String> submit1 = pool.submit(new MyCallable(200));
Future<String> submit2 = pool.submit(new MyCallable(300));
Future<String> submit3 = pool.submit(new MyCallable(400));
System.out.println(submit.get());
System.out.println(submit1.get());
System.out.println(submit2.get());
System.out.println(submit3.get());
}
并行:在同一时刻上,同时有多个线程被CPU调度执行
并发:进程中的线程是由cpu调度执行的,但cpu能处理线程的数量有限,为了保证全部线程都在执行,cpu会轮询为每个系统的线程服务,由于cpu切换速度极快,给我们感觉线程在同时执行,这就是并发。
线程的生命周期
拓展:悲观锁和乐观锁
悲观锁:一上来就加锁,每次只能一个线程进入访问完毕后再解锁。线程安全,性能较差
乐观锁:一开始不上锁,等出现线程安全问题的时候才开始控制,线程安全,性能较好。
悲观锁就是加synchronized
乐观锁有很多种,后面慢慢补充
public class MyRunnable1 implements Runnable{
//整数修改的乐观锁,原子类实现
private AtomicInteger count = new AtomicInteger();
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("count==" +count.incrementAndGet());
}
}
}
代码题:有100份礼品,小明和小红同时送出,当礼品剩下10份不再送出。使用线程模拟出来,并统计小明和小红各自送出多少礼物。
public class MyRunnable1 implements Runnable{
private List<String> gift;
private String name;
private int count;
public MyRunnable1(){
}
public MyRunnable1(List<String> gift,String name){
this.name = name;
this.gift = gift;
}
@Override
public void run() {
Thread thread = Thread.currentThread();
Random r = new Random();
while (true){
//锁对象要唯一,这里不能用this,因为要创建两个线程对象,this指代当前类对象,不唯一
synchronized (gift){
if (gift.size() <= 10){
break;
}
String remove = gift.remove(r.nextInt(gift.size()));
System.out.println(thread.getName()+remove);
count++;
}
}
}
public List<String> getGift() {
return gift;
}
public void setGift(List<String> gift) {
this.gift = gift;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
public class ThreadPoolTest {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<>();
String[] names = {"口红","包包","computer","phone"};
Random r = new Random();
for (int i = 0; i < 100; i++) {
list.add(names[r.nextInt(names.length)]);
}
MyRunnable1 runnable = new MyRunnable1(list,"小红");
Thread thread = new Thread(runnable);
thread.start();
MyRunnable1 runnable1 = new MyRunnable1(list,"小明");
Thread thread1 = new Thread(runnable1);
thread1.start();
thread.join();
thread1.join();
System.out.println(runnable.getCount());
System.out.println(runnable1.getCount());
}