一、基本概念:程序、进程、线程
1、程序:是指为了完成特定的任务,用某种编程语言编写的一组指令集合,即一段静态的代码。
2、进程:指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。进程是资源分配的单位。
3、线程:线程是进程中的一个执行单元,负责当前进程中程序的执行。一个进程中有多个线程。线程作为调度和执行的单位,每个线程拥有独立运行的栈和程序计数器(PC)。若一个进程同时并发执行多个线程,就是支持多线程的。
二、单核CPU和多核CPU
1、单核CPU:单核CPU上运行的多线程程序, 同一时间只能一个线程在跑, 系统帮你切换线程而已, 系统给每个线程分配时间片来执行, 每个时间片大概10ms左右, 看起来像是同时跑, 但实际上是每个线程跑一点点就换到其它线程继续跑。
2、多核CPU:多核CPU指的是CPU有多个核心,多线程是程序有多个线程在同时执行。多核也要用多线程才能发挥优势。
Java应用程序java.exe中有main()主线程,gc()垃圾回收线程,异常处理的线程
三、并行和并发
1、并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
2、并发:一个CPU(采用时间片)“同时”执行多个任务,比如:秒杀,多个人(线程)做同一件事。
四、多线程的创建方式
1、继承Thread类,并重写run方法
2、创建一个类去实现Runable接口,然后将这个类以参数的形式传递给Thread类
3、实现Callable接口,实现call()方法,相比Runnable接口,它可以有返回值、可以抛异常,支持泛型返回值,需要借助FutureTask类,获取返回值。FutureTask类是Future接口的唯一实现类
package cn.wyu.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 使用一个线程计算1-100之间所有偶数之和
* @author linwillen
* @create 2020-04-26-15:46
*/
public class ThreadNew {
public static void main(String[] args) {
//2.创建Callable接口实现类对象
NumThread numThread = new NumThread();
//3.将Callable接口实现类对象作为参数传递到FutureTask构造器中,创建FutureTask对象
FutureTask<Integer> futureTask = new FutureTask<Integer>(numThread);
//4.将FutureTask对象作为参数传递到Thread类的构造器中,并调用start()方法
new Thread(futureTask).start();
try {
//5.获取Callable中call的返回值,调用get()方法
Integer sum = futureTask.get();
System.out.println("总和为:"+sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//1. 实现Callable接口并重写call()方法
class NumThread implements Callable<Integer>{
private int sum = 0;
@Override
public Integer call() throws Exception {
for (int i = 1;i<=100;i++){
if(i%2==0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
4、使用线程池的方法创建线程
package cn.wyu.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 使用线程池创建线程
* @author linwillen
* @create 2020-04-26-16:24
*/
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//service.submit();//适用于Callable接口
//2.执行指定线程的操作
service.execute(new NumberThread());//适用于Runnable接口
service.shutdown();
}
}
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
设置线程池的属性:
五、线程的生命周期
1、JDK中Thread.State枚举类中定义了线程的几种状态
public enum State {
/**
* Thread state for a thread which has not yet started.(线程还没有调用start的时候)
*/
NEW,(新建)
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
* 调用start之后,jvm启动了这个任务,但是可能被其他资源占用线程变成WAITING
*/
RUNNABLE,(准备就绪)
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
* 线程被锁的时候,线程等待进去一个synchronized块方法或者可重入锁的时候
*/
BLOCKED,(阻塞)
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
* 线程调用object.wait thread.join LockSupport.park 的时候变成 WAITING
*/
WAITING,(不见不散)
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
* sleep 或者wait,join带时间参数等方法时
*/
TIMED_WAITING,(过时不候)
/**
* Thread state for a terminated thread.
* The thread has completed execution.
* 线程执行完成或者被中断的时候变成TERMINATED
*/
TERMINATED;(终结)
}
六、线程的同步
1、卖票案例
/**
* @author linwillen
* @create 2020-04-26-0:24
*/
public class WindowTest {
public static void main(String[] args) {
Window window = new Window();
Thread t1 = new Thread(window,"窗口1");
Thread t2 = new Thread(window,"窗口2");
Thread t3 = new Thread(window,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true){
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票,卖出第"+ticket+"张");
ticket--;
}else{
break;
}
}
}
}
其中一次运行结果:出现重票还有错票的情况
对于以上情况可以使用synchronized关键字解决
2、synchronized的三种应用方式:
(1)修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁
synchronized(同步监视器){
//需要同步的代码
}
//1.操作共享数据的代码,被称为同步代码
//2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据
//3.同步监视器:俗称锁,任何一个类的对象都可以充当锁。要求:多个线程必须公用同一把锁
package cn.wyu.thread;
/**
* 使用同步代码块解决实现Runnable接口的线程安全问题
* @author linwillen
* @create 2020-04-26-0:24
*/
public class WindowTest1 {
public static void main(String[] args) {
Window1 window = new Window1();
Thread t1 = new Thread(window,"窗口1");
Thread t2 = new Thread(window,"窗口2");
Thread t3 = new Thread(window,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window1 implements Runnable{
private int ticket = 100;
Object object = new Object();//object作为锁只能只有一个
@Override
public void run() {
while(true){
synchronized (object) {//同步代码块//synchronized (Window.class) Class clazz = Window.class
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,卖出第" + ticket + "张");
ticket--;
} else {
break;
}
}
}
}
}
运行结果:
如果实现Thread类创建线程:则还是会出现同步问题。因为Window2创建了3次,每一个Window2对象都有一个object,因此同步代码块用的是不同的锁。想解决此问题需要将Object设置为static。
package cn.wyu.thread;
/**
* 使用同步代码块解决实现Thread类的线程安全问题
* @author linwillen
* @create 2020-04-26-0:24
*/
public class WindowTest2 {
public static void main(String[] args) {
Window2 t1 = new Window2();
Window2 t2 = new Window2();
Window2 t3 = new Window2();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window2 extends Thread{
private static int ticket = 100;
private static Object object = new Object();
public void run() {
while(true){
synchronized (object) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,卖出第" + ticket + "张");
ticket--;
} else {
break;
}
}
}
}
}
(2)修饰实例方法,作用于实例方法,给实例加锁
package cn.wyu.thread;
/**
* 使用同步方法解决实现Runnable接口的线程安全问题
* @author linwillen
* @create 2020-04-26-0:24
*/
public class WindowTest3 {
public static void main(String[] args) {
Window3 window = new Window3();
Thread t1 = new Thread(window,"窗口1");
Thread t2 = new Thread(window,"窗口2");
Thread t3 = new Thread(window,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window3 implements Runnable{
private static int ticket = 100;
@Override
public void run() {
while(true){
show();
}
}
public synchronized void show(){//同步监视器:this,指当前对象
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,卖出第" + ticket + "张");
ticket--;
}
}
}
(3)修饰静态方法。给当前类加锁,要访问代码前,要获得类对象的锁
package cn.wyu.thread;
/**
* 使用同步方法解决实现Thread类的线程安全问题
* @author linwillen
* @create 2020-04-26-0:24
*/
public class WindowTest4 {
public static void main(String[] args) {
Window4 t1 = new Window4();
Window4 t2 = new Window4();
Window4 t3 = new Window4();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window4 extends Thread{
private static int ticket = 100;
public void run() {
while(true){
show();
}
}
public static synchronized void show(){//同步监视器:当前类本身---Window4.class
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,卖出第" + ticket + "张");
ticket--;
}
}
}
七、线程的死锁
1、死锁:不同的线程分别占用对方需要的同步资源不释放,都在等待对方释放自己需要的资源,这样就形成了死锁。
出现死锁后,不会出现异常,不会提示,只是所有的线程都处于阻塞状态,无法继续执行。
死锁演示:
package cn.wyu.thread;
/**
* @author linwillen
* @create 2020-04-26-2:01
*/
public class DeadLock {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
八、Lock锁
package cn.wyu.thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 解决线程安全问题的方式二;Lock锁 ---JDK5.0后新增
* @author linwillen
* @create 2020-04-26-12:55
*/
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w,"窗口1");
Thread t2 = new Thread(w,"窗口2");
Thread t3 = new Thread(w,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window implements Runnable{
private static int ticket = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
//调用lock()方法锁定
lock.lock();
try {
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,卖出第" + ticket + "张");
ticket--;
}else{
break;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
lock.unlock();
}
}
}
}
面试题:synchronized与Lock的区别?
1. Lock是显式锁(手动开启锁和手动关闭锁),synchronized是隐式锁,出了作用域自动释放锁。
2. Lock只有代码块锁,synchronized有代码块锁和方法锁
建议优先使用顺序:Lock->synchronized代码块->synchronized方法
面试题:解决线程安全问题有几种方式?
Lock锁,同步代码块,同步方法
九、线程的通信
package cn.wyu.thread;
/**
* 线程通信的例子:使用两个线程打印1-100,线程一,线程二交替打印
* @author linwillen
* @create 2020-04-26-13:38
*/
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number, "线程一");
Thread t2 = new Thread(number, "线程二");
t1.start();
t2.start();
}
}
class Number implements Runnable{
private static int number = 1;
//private Object object = new Object();
@Override
public void run() {
while (true){
synchronized (this) {
//synchronized (object) {
this.notify();
//object.notify();
if(number <= 100){
System.out.println(Thread.currentThread().getName()+":"+number);
number++;
try {
this.wait();
//object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
通信涉及到的方法:
1. wait():一旦执行这个方法,当前线程会进入阻塞状态,并释放同步监视器
2. notify():一旦执行这个方法。就会唤醒被wait()的一个线程,如果有多个线程被wait(),就会唤醒优先级高的,如果优先级高的有多个则,随机唤醒其中一个。
3. notifyAll():一旦执行这个方法,就会唤醒所有被wait()的线程。
注意:
1. wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中
2. wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法的同步监视器。否则会出现 java.lang.IllegalMonitorStateException异常
3. wait(),notify(),notifyAll()三个方法都是定义在java.lang.Object类里面的
原因:因为任何类的对象都可以充当同步监视器,而在同步代码块中调用wait(),notify(),notifyAll()必须使用同步监视器来调用,因此,这三个方法都必须在任何对象中。
面试题:wait()方法和sleep()方法的异同?
相同点:两个方法执行都可以使得当前线程进入阻塞状态。
不同点:
1. 两个方法声明的位置不同:wait()方法在Object类中,sleep()方法在Thread类中
2. 调用的要求不同:sleep()方法在任何位置都可以使用,wait()方法必须在同步代码中使用
3. 是否释放锁:两个方法都在同步代码块或同步方法中时,调用sleep()方法的时候不会释放锁,调用wait()方法会释放手中的锁。
生产者消费者问题:
生产者(Producer)将产品交给店员(Clerk),而消费者(Customer)从店员取走产品,店员只能一次持有固定的产品数量(比如:20),如果生产者试图生产更多产品,店员会叫生产品停一下,如果店中有空位再叫生产者生产。如果店中没有产品的话,会叫消费者等一下,等店中有产品了,再叫消费者过来取走产品。
分析:
1. 是否是多线程问题:是,生产者线程,消费者线程。
2. 是否有共享数据:有,店员(或产品)
3. 如何解决线程安全问题:3种
4. 是否涉及线程的通信:是
package cn.wyu.thread;
/**
* @author linwillen
* @create 2020-04-26-15:02
*/
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Customer customer = new Customer(clerk);
new Thread(producer,"生产者").start();
new Thread(customer,"消费者").start();
}
}
class Clerk {
private int productCount = 0;
public synchronized void produceProduct() {
if(productCount<20){
productCount++;
System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consumeProduct() {
if(productCount>0){
System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
productCount--;
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer implements Runnable{
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":开始生产产品。。。");
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
class Customer implements Runnable{
private Clerk clerk;
public Customer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":开始消费产品。。。");
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}