start方法的作用:1)启动当前线程;2)调用当前线程的run方法;
此实现方式有一个更简单的形式:使用Thread类的匿名子类实现
/**
* Author:NorthSmile
* Create:2023/3/28 11:45
* 使用两个不同的子线程分别打印偶数、奇数
*/
public class EvenOdd {
public static void main(String[] args) {
System.out.println("主线程:"+Thread.currentThread().getName());
// EvenThread thread1 = new EvenThread();
// OddThread thread2 = new OddThread();
// thread1.start();
// thread2.start();
// 匿名子类的方式
new Thread(){
@Override
public void run() {
// 打印偶数
for (int i = 0; i < 50; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}.start();
new Thread(){
@Override
public void run() {
// 打印偶数
for (int i = 0; i < 50; i++) {
if (i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}.start();
}
}
class EvenThread extends Thread{
@Override
public void run() {
// 打印偶数
for (int i = 0; i < 50; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
class OddThread extends Thread{
@Override
public void run() {
// 打印奇数
for (int i = 0; i < 50; i++) {
if (i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
package com.northsmile.thread;
/**
* Author:NorthSmile
* Create:2023/3/28 14:44
* 多线程创建:
* * 方式二: 通过实现java.lang.Runnable接口实现
* - 创建自定义类,实现java.lang.Runnable接口;
* - 实现其run方法,run方法的方法体用于完成该线程所对应的具体任务;
* - 在主线程中实例化该自定义类对象;
* - 将自定义类对象作为new Thread()的构造器参数,实例化Thread对象;
* - 通过Thread对象启动该线程,调用其run方法:实质是调用Thread类的Runnable型参数target的run方法;
*/
// 1.创建自定义类,实现java.lang.Runnable接口;
class EThread implements Runnable{
// 2.实现其run方法,run方法的方法体用于完成该线程所对应的具体任务;
@Override
public void run() {
// 打印偶数
for (int i = 0; i < 50; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
// 3.在主线程中实例化该自定义类对象;
EThread eThread = new EThread();
// 4.将自定义类对象作为new Thread()的构造器参数,实例化Thread对象;
Thread t1 = new Thread(eThread);
// 5.通过Thread对象启动该线程,调用其run方法:实质是调用Thread类的Runnable型参数target的run方法;
t1.start();
Thread t2 = new Thread(eThread);
// 5.通过Thread对象启动该线程,调用其run方法:实质是调用Thread类的Runnable型参数target的run方法;
t2.start();
}
}
通过实现Runnable接口的方式比通过继承Thread类的方式更常用,原因在于:
二者联系:
Thread类本身实现了Runnable接口;
相同点:
均要在实现类中重写其run()方法,并将具体任务的逻辑生命在其方法体中;
五种状态:新建、就绪、运行、阻塞、死亡
线程状态之间的转换:
出现线程安全的原因:
解决思想:
Java中使用同步机制(隐式锁:同步代码块或同步方法、显式锁:Lock)解决线程安全问题:
使用同步机制处理时具有一定的局限性:操作同步代码时只能有一个线程参与,其他线程等待,相当于一个单线程处理过程,效率较低。
方式一:同步代码块:
synchronized(同步监视器){
// 需要进行同步的代码(即操作共享数据的代码)
}
其中同步监视器称为锁,Java中任意不为null的引用均可作为锁,前提要求是:多个线程必须共用同一把锁。
方式二:同步方法:
权限修饰符 synchronized 返回值类型 (参数列表){
// 需要进行同步的代码(即操作共享数据的代码)
}
当操作共享数据的代码组成完整的方法时,可使用同步方法解决线程安全问题:
class Window implements Runnable{
private int tickets=100;
// 多个线程共用同一把锁
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
// 手动加锁
lock.lock();
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售卖车票,票号为:" + tickets);
tickets--;
} else {
break;
}
}finally {
// 手动释放锁
lock.unlock();
}
}
}
}
使用继承的方式实现多线程问题:
package com.northsmile.thread;
/**
* Author:NorthSmile
* Create:2023/3/29 8:45
* 使用多个窗口卖票:
* 1.涉及多线程;
* 2.涉及共享数据;
* 3.线程对共享数据会进行修改;
*
* 可能存在的问题:线程安全,此处表现为重票、错票问题
* 解决方案:同步机制(同步代码块、同步方法)
* 此方式中通过static修饰共享数据,保证数据的共享性
*/
public class TicketOfThread {
public static void main(String[] args) {
WindowOfThread window1 = new WindowOfThread("1号窗口");
WindowOfThread window2 = new WindowOfThread("2号窗口");
WindowOfThread window3 = new WindowOfThread("3号窗口");
window1.start();
window2.start();
window3.start();
}
}
class WindowOfThread extends Thread{
private static int tickets=100;
public WindowOfThread(String name){
super(name);
}
@Override
public void run() {
while (true){
// 方式一:同步代码块
// synchronized(WindowOfThread.class) {
// if (tickets > 0) {
// System.out.println(getName() + "售卖车票,票号为:" + tickets);
// tickets--;
// } else {
// break;
// }
// }
// 方式二:同步方法
saleTickets();
}
}
// 方式二:同步方法
private static synchronized void saleTickets() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售卖车票,票号为:" + tickets);
tickets--;
}
}
}
使用实现接口的方式实现多线程问题:
package com.northsmile.thread;
/**
* Author:NorthSmile
* Create:2023/3/29 8:45
* 使用多个窗口卖票:
* 1.涉及多线程;
* 2.涉及共享数据;
* 3.线程对共享数据会进行修改;
*
* 可能存在的问题:线程安全,此处表现为重票、错票问题
* 解决方案:同步机制(同步代码块、同步方法)
* 此方式中Runnable接口的实现类始终只有一个,其成员变量自然只有一个,也就是多个线程共享;
*/
public class TicketOfRunnable {
public static void main(String[] args) {
WindowOfRunnable base=new WindowOfRunnable();
Thread window1 = new Thread(base,"1号窗口");
Thread window2 = new Thread(base,"2号窗口");
Thread window3 = new Thread(base,"3号窗口");
window1.start();
window2.start();
window3.start();
}
}
class WindowOfRunnable implements Runnable{
private int tickets=100;
@Override
public void run() {
while (true){
// 方式一:同步代码块
synchronized(WindowOfThread.class) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售卖车票,票号为:" + tickets);
tickets--;
} else {
break;
}
}
// 方式二:同步方法
// saleTickets();
}
}
// 方式二:同步方法
private synchronized void saleTickets() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售卖车票,票号为:" + tickets);
tickets--;
}
}
}
单例模式-懒汉模式:
单线程下,单例模式-懒汉模式示例代码:
package com.northsmile.thread;
/**
* Author:NorthSmile
* Create:2023/3/29 9:42
* 单例模式-懒汉模式
*/
class Phone {
// 1.提供私有无参构造方法
private Phone(){
}
// 2.在类内部实例化一个自身的唯一对象,要求该属性为静态属性
private static Phone instance;
// 3.提供静态方法getInstance用于提供外部访问接口
public static Phone getInstance(){
// 第一次需要该实例对象,对其进行创建
if (instance==null){
instance=new Phone();
}
return instance;
}
}
多线程下,单例模式-懒汉模式示例代码:
class Phone {
// 1.提供私有无参构造方法
private Phone(){
}
// 2.在类内部实例化一个自身的唯一对象,要求该属性为静态属性
private static Phone instance;
// 3.提供静态方法getInstance用于提供外部访问接口
public static Phone getInstance(){
// 第一次需要该实例对象,对其进行创建
// 方式一:效率较低,所有线程都要来抢夺“锁”
// synchronized (Phone.class){
// if (instance==null){
// instance=new Phone();
// }
// }
// 方式二:效率较高,只有开始的部分线程要来抢夺“锁”
if (instance==null) {
synchronized (Phone.class) {
if (instance == null) {
instance = new Phone();
}
}
}
return instance;
}
}
涉及到三个方法:
这三个方法只能使用在同步代码块或同步方法中;
这三个方法只能通过同步代码块或同步方法中的同步监视器进行调用;
这三个方法均继承于Object类;
代码示例:
class WindowOfRunnable implements Runnable{
private int tickets=100;
@Override
public void run() {
while (true){
synchronized(this) {
notify(); // 唤醒一个等待的线程,令其从阻塞变为就绪状态
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售卖车票,票号为:" + tickets);
tickets--;
// 令当前线程进入阻塞状态,等待唤醒
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
break;
}
}
}
}
}
package com.northsmile.thread.ex;
/**
* Author:NorthSmile
* Create:2023/3/29 14:39
* 生产者-消费者问题
* 多线程问题:生产线程、消费线程
* 共享数据:店员或其把握的产品数量
* 线程对共享数据会进行修改;
*
* 存在线程安全问题,解决方案:三种同步机制
* 涉及线程通信问题,使用wait/notify/notifyAll
*
*/
public class ProducerAndCustomer {
public static void main(String[] args) {
Clerk clerk=new Clerk();
Producer producer = new Producer(clerk);
Custom customer = new Custom(clerk);
Thread t1 = new Thread(producer,"生产者");
Thread t2 = new Thread(customer,"消费者");
t1.start();
t2.start();
}
}
// 店员类
class Clerk{
private long products;
public Clerk(){
}
public long getProducts() {
return products;
}
public void setProducts(long products) {
this.products = products;
}
public synchronized void consume() {
if (products>0) {
products--;
System.out.println(Thread.currentThread().getName()+"开始消费,当前产品数量为:"+products);
notify();
}else{ // 没有产品,进行等待,继续生产
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public synchronized void produce() {
if (products<20) {
products++;
System.out.println(Thread.currentThread().getName()+"开始生产,当前产品数量为:"+products);
notify();
}else{ // 没有空位,进行等待,继续消费
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class Producer implements Runnable{
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk=clerk;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
clerk.produce();
}
}
}
class Custom implements Runnable{
private Clerk clerk;
public Custom(Clerk clerk){
this.clerk=clerk;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
clerk.consume();
}
}
}
package com.northsmile.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* Author:NorthSmile
* Create:2023/3/29 16:33
* 实现Callable接口,创建线程
*
* - 自定义类,实现Callable接口,重写其call方法;
* - 创建实现类的对象;
* - 将此对象作为参数,实例化FutureTask对象;
* - 将FutureTask对象作为参数实例化Thread类对象,并启动线程;
* - 有需求可以通过FutureTask对象获取call方法的返回值;
*/
public class CallableDemo {
public static void main(String[] args) {
EvenT evenT = new EvenT();
FutureTask<Integer> task = new FutureTask<Integer>(evenT);
Thread thread = new Thread(task, "子线程");
thread.start();
Integer sum = null;
try {
sum = task.get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
System.out.println(sum);
}
}
class EvenT implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i <=50; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
sum+=i;
}
return sum;
}
}
package com.northsmile.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
* Author:NorthSmile
* Create:2023/3/29 18:22
* 使用线程池创建线程
*/
public class PoolDemo {
public static void main(String[] args) {
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);// 此处nThreads参数实质指定了该线程池的corePoolSize和maximumPoolSize;
ThreadPoolExecutor executor=(ThreadPoolExecutor)executorService;
// 线程管理
executor.setCorePoolSize(2);
executor.setMaximumPoolSize(3);
// 将任务交个线程池执行
WindowPool runnable = new WindowPool();
executor.execute(runnable);
executor.execute(runnable);
executor.execute(runnable);
executor.execute(runnable);
// 关闭线程池
executorService.shutdown();
}
}
class WindowPool implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (true) {
synchronized (this) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售卖车票,票号为:" + tickets);
tickets--;
} else {
break;
}
}
}
}
}
1.创建多线程的方式有哪几种?
四种:1)继承Thread;2)实现Runnable接口;3)实现Callable接口;4)线程池;
2.如何解决线程安全问题,有哪几种方式?
同步机制,有三种方式:1)同步代码块;2)同步方法;3)显式锁Lock;
3.隐式锁和显式锁的异同?
自动、手动。
4.sleep方法和wait方法的异同?
答:
相同点:均可使当前线程从运行态进入阻塞状态;
不同点:1)sleep在经过设定的休眠时长之后自动转为就绪状态,而wait需要等待notify或者notifyAll唤醒;2)sleep方法是java.lang.Thread类的方法,而wait是声明在java.lang.Object类下;3)wait方法只能在同步代码块或者同步方法中使用;4)sleep不会释放同步监视器,而wait会释放。
资料来源:尚硅谷