前言
学习线程之前我们先了解一下其中的基本概念:
(1)什么是串行和并行?
串行是指多个任务时,各个任务按顺序执行,完成一个之后才能进行下一个 ;
并行指的是多个任务可以同时执行。异步是多个任务并行的前提条件。
(2)什么是并行和并发?
并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。
(3)什么是进程和线程?
进程是指运行中的应用程序,每个进程都有自己独立的地址空间(内存空间)
线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
一、多线程
线程分为守护线程和用户线程
1.线程的特点
- 线程是轻量级的进程
- 线程没有独立的地址空间(内存空间)
- 线程是由进程创建的(寄生在进程)
- 一个进程可以拥有多个线程–>这就是我们常说的多线程编程
在java语言中,两个线程中的堆内存和方法区共享,但栈内存独立,一个线程一个栈,栈与栈互不干扰。因此局部变量永远不会存在线程安全问题
2.线程的运行状态
3.线程使用的三种方式
(1)实现Runnable接口
package com.Thread;
public class MyHomeWork implements Runnable{
@Override
public void run() { //重写run方法
System.out.println("爱java爱生活");
}
public static void main(String[] args) {
MyHomeWork myHomeWork = new MyHomeWork();//实例化
Thread thread = new Thread(myHomeWork);
thread.start();
}
}
(2)继承Thread类
package com.Thread;
public class MyHomeWork1 extends Thread{
public void run(){ //方法实现
System.out.println("爱生活爱java");
}
public static void main(String[] args) {
MyHomeWork1 myHomeWork1 = new MyHomeWork1();//实例化
myHomeWork1.start();
}
}
(3)实现Callable接口
可以解决无法抛出异常和返回值的问题
package com.Thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyHomeWork implements Callable {
@Override
public Object call() throws Exception {
return "爱生活爱java";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyHomeWork myHomeWork = new MyHomeWork();
FutureTask futureTask = new FutureTask<>(myHomeWork);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println( futureTask.get());
}
}
4.线程的优先级问题
①线程调度的两种模型
- 抢占式调度模型:那个线程的优先级比较高,抢的CPU时间篇的概率就高一些(处于运行状态的时间多一些)java采用
- 均分式调度模型:平分时间片
②优先级设置(通过调用Thread的方法)
thread.setPriority(int newPriority);
方法中的参数为一个int类型(默认值为5)
thread.setPriority(Thread.MAX_PRIORITY); 10
thread.setPriority(Thread.MIN_PRIORITY); 1
thread.setPriority(Thread.NORM_PRIORITY); 5
5.线程中的方法
(1)线程休眠:sleep方法(需要抓取异常)
try {
Thread.sleep(100); //参数为休眠的时间
} catch (InterruptedException e) {
e.printStackTrace();
}
(2)线程礼让:yield方法(礼让不代表放弃,还会争夺资源)
它只是给当前正处于运行状态下的线程一个提醒,告知它可以将资源礼让给其他线程,但这仅仅是一种暗示,没有任何一种机制保证当前线程会将资源礼让。
Thread.yield();
(3)线程联合:join方法(需要抓取异常)
join方法允许一个线程等待另一线程的完成。如果t是Thread正在执行其线程的对象,导致当前线程暂停执行,直到t的线程终止。
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
(4)线程睡眠结束:interrupt()方法
通过java的异常处理机制终止线程睡眠
thread.interrupt();//线程睡眠结束,通过java的异常处理机制,
(5)获取线程的名字 (Thread的静态方法)
静态方法通过类名调用,此方法处于那个线程中,返回的就是那个线程的名字
String thread = Thread.currentThread().getName();
- 主线程的默认名字为 main
- 支线程的默认名字为Thread-0
(6)设置线程的名字(setName方法)
ThreadT1 t1 = new ThreadT1();
t1.setName("heng");
String name = t1.getName();//Thread-0线程默认名字
6.守护线程(后台线程)
守护线程的特点:①守护线程是一个死循环
②用户线程结束,守护线程自动结束
③主线程main是一个用户线程
代码实现:
public class DefendThread {
public static void main(String[] args) {
DefendTask defend = new DefendTask();
defend.setDaemon(true);//在启动线程之前将其设置为守护线程
defend.start();
for (int i = 0; i <10 ; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+i);
}
}
}
class DefendTask extends Thread{
@Override
public void run() {
int i=0;
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
System.out.println(Thread.currentThread().getName()+i);
}
}
}
二、线程安全问题
1.什么时候存在线程安全问题
- 多线程并发
- 有共享数据
- 共享数据有修改的行为
2.线程冲突
当在不同线程中运行作用于相同数据的两个操作时,就会发生干扰。这意味着这两个操作由多个步骤组成,并且步骤顺序重叠。
3.线程同步(可以解决线程冲突的问题)
- 同步语句(synchronized statements )
- 同步方法(synchronized methods )
举例:售票员案例
package com.Thread;
public class MyText implements Runnable {
public static int ticket = 100;
@Override
public void run() {
while (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized ("") {
if (ticket <= 0) {
return;
}
System.out.println(Thread.currentThread().getName()
+ "卖了1张票,剩余" + --ticket + "张票");
}
// didi();
}
}
// public synchronized void didi(){
// if (ticket <= 0) {
// return;
// }
// System.out.println(Thread.currentThread().getName()
// + "卖了1张票,剩余" + --ticket + "张票");
// }
public static void main(String[] args) {
MyText myText = new MyText();
Thread thread1 = new Thread(myText, "售票员1");
thread1.start();
Thread thread2 = new Thread(myText, "售票员2");
thread2.start();
Thread thread3 = new Thread(myText, "售票员3");
thread3.start();
}
}
- 锁对象(java.util.concurrent.locks软件包所提供的锁)
import java.util.concurrent.locks.ReentrantLock;
public class ThreadLock implements Runnable {
public static int ticket = 100;
ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
while (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
reentrantLock.lock();
if (ticket <= 0) {
return;
}
System.out.println(Thread.currentThread().getName() + "卖了1张票,剩余" + --ticket + "张票");
reentrantLock.unlock();
}
}
public static void main(String[] args) {
ThreadLock threadLock = new ThreadLock();
Thread thread1 = new Thread(threadLock, "售票员1");
thread1.start();
Thread thread2 = new Thread(threadLock, "售票员2");
thread2.start();
Thread thread3 = new Thread(threadLock, "售票员3");
thread3.start();
}
}
4.线程死锁
多线程场景中的死锁,指两个或多个线程之间,由于互相持有对方需要的锁,互相等待,而永久处于阻塞状态。
死锁的解决办法:线程协调:wait和notify方法
class CoordinateB implements Runnable {
@Override
public void run() {
synchronized ("B") {
System.out.println(Thread.currentThread().getName()
+ "持有了B锁,等待A锁。。。");
synchronized ("A") {
System.out.println(Thread.currentThread().getName()
+ "持有了B锁和A锁");
"A".notify();
}
}
}
}
class CoordinateA implements Runnable {
@Override
public void run() {
synchronized ("A") {
System.out.println(Thread.currentThread().getName()
+ "持有了A锁,等待B锁。。。");
try {
"A".wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized ("B") {
System.out.println(Thread.currentThread().getName()
+ "持有了A锁和B锁");
}
}
}
}
三、生产者与消费者模式
代码实现:
public class ThreadTexttt {
public static void main(String[] args) {
ArrayList list = new ArrayList();
Thread thread = new Thread(new Producer(list));
Thread thread1 = new Thread(new Consumer(list));
thread.start();
thread1.start();
}
}
class Producer implements Runnable{
private List list;
public Producer(List list) {
this.list = list;
}
@Override
public void run() {
while (true){
synchronized (list){
if (list.size()>0){
System.out.println("仓库已满,不需要生产");
try {
list.wait();//
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
Object o = new Object();
list.add(o);
System.out.println(Thread.currentThread().getName()+"生产了一个"+o);
list.notify();
}
}
}
}
}
class Consumer implements Runnable{
private List list;
public Consumer(List list) {
this.list = list;
}
@Override
public void run() {
while (true){
synchronized (list){
if (list.size()==0){
System.out.println("仓库空了");
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
Object remove = list.remove(0);
System.out.println(Thread.currentThread().getName()+"消费了"+remove);
list.notify();
}
}
}
}
}
四、多线程经典面试题分析
1.阻塞问题:t1线程是否对t2有阻塞
public class ThreadText01 {
public static void main(String[] args) {
MyClass myClass = new MyClass();
// MyClass myClass1 = new MyClass();
MyThread myThread = new MyThread(myClass);
myThread.setName("t1");
MyThread myThread1 = new MyThread(myClass);
myThread1.setName("t2");
myThread.start();
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
myThread1.start();
}
}
class MyThread extends Thread{
MyClass myClass;
public MyThread(MyClass myClass) {
this.myClass = myClass;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
myClass.doSome();
}
if (Thread.currentThread().getName().equals("t2")){
myClass.doOther();
}
}
}
class MyClass {
public synchronized void doSome(){
System.out.println("doSome begin");
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public synchronized void doOther(){
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
会,共享的是同一个对象的方法
如果MyClass myClass1 = new MyClass();将myClass1传t2?
不会阻塞,因为是两个对象,数据不共享
如果使两个方法变成静态方法,会出现阻塞吗?
会,在静态方法中使用synchronized的方式为类锁,类锁只有一把,不管多少个对象
2.sleep() 和 wait() 有什么区别?
- 类的不同:sleep() 来自 Thread,wait() 来自 Object。
- 释放锁:sleep() 不释放锁;wait() 释放锁。
- 用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒
五、定时器(java.util.Timer)
每隔多长时间执行此代码
new Timer().schedule(TimerTask task, Date firstTime, long period);
此方法需要三个参数 第一个参数:获取一个TimerTask对象 此类为抽象类,所以需要通过子类来实现
第二个参数:程序开始的时间
第三个参数:运行多长时间
代码展示:
public class TimerText {
public static void main(String[] args) throws ParseException {
Timer timer = new Timer();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date par = simpleDateFormat.parse("2020-10-02 11:32:00");
System.out.println(par);
timer.schedule(new TimerTx(),par,1000*10);
}
}
class TimerTx extends TimerTask{
@Override
public void run() {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-mm-dd hh:mm:ss");
String format = simpleDateFormat.format(new Date());
System.out.println(format+"完成了一次数据备份");
}
}
- TimerTask为抽象类实现了Runnable接口
- 可以看作是一个线程
- 定时器也可通过SpringTask框架来完成