概述
theme: channing-cyan
highlight: androidstudio
文章目录
- theme: channing-cyan highlight: androidstudio
- 前言
- 继承Thread
- 实现Runale接口
- Callable接口
- 线程周期
- CPU调度
- 线程安全问题(并发问题)
- 方案一,直接加synchronized
- 提取Tickets
- 上锁
- 生成者与消费者
- 信号灯
- 管程法
- 线程池
「这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战」
前言
关于java线程的创建这里就不进行过多复述了
继承Thread
这个是直接使用那个Thread继承一下,重写run方法的
public class ThreadDome1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"运行了:"+i+"次");
}
}
public static void main(String[] args) {
new ThreadDome1().start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"运行了:"+i+"次");
}
}
}
实现Runale接口
还有一个是实现这个接口的。
public class RunableDome implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"运行了:"+i+"次");
}
}
public static void main(String[] args) {
RunableDome run = new RunableDome();
new Thread(run, "小明").start();
new Thread(run,"小红").start();
}
}
这个有什么好处就不用多说了(多个线程可以控制一个对象),有什么问题(并发)才谁我们后面的重点之一。
Callable接口
这玩意和runable类似,但是的话有返回值,然后我们用线程池跑了一下。
public class CallabelDome implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"运行了:"+i+"次");
}
return true;
}
public static void main(String[] args) {
CallabelDome t1 = new CallabelDome();
CallabelDome t2 = new CallabelDome();
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<Boolean> submit = executorService.submit(t1);
Future<Boolean> submit1 = executorService.submit(t2);
executorService.shutdownNow();
}
}
线程周期
这个看一张图即可明白
中文图
这里提一下先前那个线程停止的方式,我们一般是使用自己定义的方式(在外部停止的话)
public class RunableDome implements Runnable{
private boolean flag = true;
public void setFlag(boolean flag) {
this.flag = flag;
}
public void stop(){
this.setFlag(false);
System.out.println("主线程调用stop让该线程停止运行");
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
if(!flag)
return;
System.out.println(Thread.currentThread().getName()+"运行了:"+i+"次");
}
}
public static void main(String[] args) {
RunableDome run = new RunableDome();
new Thread(run,"子线程").start();
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName()+"运行了:"+i+"次");
if(i==950){
run.stop();
}
}
}
}
CPU调度
这个主要是关于线程一些,优先级,优先执行,调度转让的内容。
一个是线程转让
Thread.yield()
就可以让 A 线程转让(假设在A线程执行这个代码)让CPU去调度别的线程,当然不一定成功。
Thread.setPriority(2)
这个就是那个优先级的那个,谁先执行,0到10 最大是10,当然这个也是得看cpu心情
还有一个设置守护线程,也就是主线程结束子线程就结束
我就使用了一个setDaemo()
public class RunableDome implements Runnable{
private boolean flag = true;
public void setFlag(boolean flag) {
this.flag = flag;
}
public void stop(){
this.setFlag(false);
System.out.println("主线程调用stop让该线程停止运行");
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
if(!flag)
return;
System.out.println(Thread.currentThread().getName()+"运行了:"+i+"次");
}
}
public static void main(String[] args) {
RunableDome run = new RunableDome();
Thread thread = new Thread(run, "子线程");
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName()+"运行了:"+i+"次");
}
}
}
可以看到我们的子线程要跑10000次的结果只跑了1000多次,因为主线程结束了。
还有最后一个join强制获取CPU调度权,还是刚刚那个例子,我就要让子线程执行玩。
那么我就可以这样
线程安全问题(并发问题)
现在开始进入到我们应该开始关注的问题了。
首先我们举个买票的例子,这个也是很多人都说过的例子,以前我学python写的博客的时候使用的什么工厂的例子,那是是我自己相的,但是有点抽象,java的说那个的时候,我直接上代码,当时就是给自己看到呗,现在还是要考虑一下不仅自己要看得明白,别人也得更好看一点。
先上代码,这个例子很简单。
public class BuyTicket implements Runnable{
private int tickets = 1000;//一开始1000张票
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
tickets --;
System.out.println(Thread.currentThread().getName()+"买了第:"+(i+1)+"张票");
}
}
public static void main(String[] args) {
Runnable tickets = new BuyTicket();
new Thread(tickets,"小明").start();
new Thread(tickets,"小红").start();
new Thread(tickets,"小容").start();
}
}
看输出结果我们发现输出了三次936,也就是产生了错乱,也就是不安全。
原理的话其实很简单。我们可以举个例子模拟代码运行原因。
所以我们现在要解决这个问题。
我们在放大一下问题。用那个sleep放大一下问题。
public class BuyTicket implements Runnable{
private int tickets = 10;//一开始1000张票
@Override
public void run() {
for (int i = 0; i < 10; i++) {
tickets --;
System.out.println(Thread.currentThread().getName()+"买了第:"+(i+1)+"张票还剩下"+tickets+"张票");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Runnable tickets = new BuyTicket();
new Thread(tickets,"小明").start();
new Thread(tickets,"小红").start();
new Thread(tickets,"小容").start();
}
}
方案一,直接加synchronized
我们现在直接看这个代码
public class BuyTicket implements Runnable{
private int tickets = 10;//一开始1000张票
@Override
public synchronized void run() {
for (int i = 0; i < 10; i++){
if(isEmpty(tickets))
return;
System.out.println(Thread.currentThread().getName()+"买了第:"+(i+1)+"张票还剩下"+tickets+"张票");
tickets --;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public int getTickets() {
return tickets;
}
public boolean isEmpty(int tickets){
if(tickets<=0){
return true;
}
return false;
}
public static void main(String[] args) throws InterruptedException {
BuyTicket tickets = new BuyTicket();
Thread t1 = new Thread(tickets, "小明");
Thread t2 = new Thread(tickets, "小红");
Thread t3 = new Thread(tickets, "小容");
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println(tickets.getTickets());
}
}
加上这个synchronized其实就是相当于加了一把锁,但是问题解决了不。
我们可以看看结果
可以看到我们得到了一个想要的值,显然我们的这个方式是OK的,那好我们再看一下执行的时间。
花费了1秒。(这里其实还有个小细节其实我们可以发现这个sleep其实是不会释放掉当前线程的锁的,除非你强行释放了)
但是这里明显有一个问题那就是我们发现,这个是直接锁代码块(默认锁的是当前对象也就是this)那么就有问题了,你锁住了,10张票都被一个人买走了,显然是不行的,所以我们干嘛,得去想办法解决这个问题,那就是我们发现这我们需要上锁的对象其实一直都是只有一个家伙,那就是ticket,所以我们其实是可以直接对ticket进行同步的,也就是把这个家伙提取出来,因为这个synchornized需要接受对象,或者Class。
提取Tickets
class Ticket{
private int tickets = 10;
public int getTickets() {
return tickets;
}
public void setTickets(int tickets) {
this.tickets = tickets;
}
public void sell(int ticket){
System.out.println(Thread.currentThread().getName()+"买了第:"+(ticket)+"张,票还剩下"+getTickets()+"张");
setTickets(getTickets()-1);
}
}
public class BuyTicket implements Runnable{
private Ticket tickets = new Ticket();//一开始1000张票
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (tickets) {
if (tickets.getTickets() <= 0) {
return;
}
tickets.sell((i + 1));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
BuyTicket tickets = new BuyTicket();
Thread t1 = new Thread(tickets, "小明");
Thread t2 = new Thread(tickets, "小红");
Thread t3 = new Thread(tickets, "小容");
long l = System.currentTimeMillis();
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
long l1 = System.currentTimeMillis();
System.out.println("此时的票数为:"+ tickets.tickets.getTickets());
System.out.println("执行时长为"+(l1-l));
}
}
这个执行时长也差不多也是一样的。
上锁
我们使用代码synchronized代码块的话多多少少还是有点不方便的。所以我们和python一样可以上个锁。
我们回到最开始的代码,直接上锁。
public class BuyTicket implements Runnable{
private int tickets = 10;//一开始1000张票
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
lock.lock();
if (tickets <= 0) return;
tickets--;
System.out.println(Thread.currentThread().getName() + "买了第:" + (i + 1) + "张票还剩下" + tickets + "张票");
}finally {
lock.unlock();
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public int getTickets() {
return tickets;
}
public static void main(String[] args) throws InterruptedException {
BuyTicket tickets = new BuyTicket();
Thread t1 = new Thread(tickets, "小明");
Thread t2 = new Thread(tickets, "小红");
Thread t3 = new Thread(tickets, "小容");
long l = System.currentTimeMillis();
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
long l1 = System.currentTimeMillis();
System.out.println("此时的票数为:"+ tickets.getTickets());
System.out.println("执行时长为"+(l1-l));
}
}
接下来是执行时间这里我强行放锁了。
404 ms
如果我不放的话(睡眠后的话)
生成者与消费者
前面两个主要是说了synchronized,这个东西有点“重”所以我们用了锁,但是这个锁是比较耗费资源的,所以我们还有这个模式,这个也是我写那个原来java多线程的内容不仔细的地方,因为一开始我的案例就是用这个的,原因也是在python里面说了很多次,但是其实python里面有个东西我没说到,那就是协程,在python里面是有协程的,不过本质上还是一个单线程,这个和Go语言的协程有别开来,那个go语言的协程是指一个模式,那个其实是多线程,而且用那个管道思想,让Go的线程实现不仅简单而且非常快速的解决了同步通信的问题,很不错!有时间可以聊聊,Go也是个很不错的语言,只是目前我想做的事情Go给的支持还不够。
OK 那么现在回到我们这里,我们说生产者消费者模式,那么其实这里具体的实现大致有两种思路,一种就是和python一样用队列,这个叫做管程法,还有就是用标志,叫做信号灯法,这里我都会有说明,不要着急,毕竟我也不想去翻翻老博客。
当然其实从代码上面说的话,这个和我们的锁是类似的。只是这里使用了另一个玩意,叫做wait()
这里你可能会好奇这个wait和unlock()的区别,我这里直接说一些结论:
- 首先二者都是释放资源。
- 前者用于线程通信,wait()之后配置notify使用。
- 而lock的作用是保护多线程访问的共享资源
- 线程之间的通信方法Condition.await 和 Condition.signal (Object.wait 和 Object.notify) 的调用前提是必须获取到锁,如果没有获取到锁就直接调用,则会抛出java.lang.IllegalMonitorStateException 异常
- A线程调用 Lock.lock() 导致线程阻塞,如果B线程调用 Lock.unlock() 则自动会解除A线程的阻塞状态,该机制是JVM调度器来唤醒,不需要B线程显示的唤醒A
- A线程调用了 Condition.await(Object.wait),如果要唤醒A线程,那么B线程必须做到:
- A 获取到锁(因为A线程wait释放了锁)
- B 调用 Condition.signal(Object.notify),这个时候A还不能执行,只是被唤醒,唤醒后需要重新获取锁
- C 调用unlock释放锁,只有B线程释放了锁,A线程才能真正的被唤醒,然后获取到锁执行
信号灯
先来我们的信号灯法,这个实现比较快。同时也是使用我们这个wait()的。
首先我们要有消费者,生产者,以及产品
在我们这里消费者就是顾客,生产者就是售票处,产品自然就是票
这个直接上代码,比较简单。
class Ticket{
public int tickets=0; //票的数量
private boolean flag = false;//还有票不
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public synchronized void buy() throws InterruptedException {
if(!isFlag()){
wait();
}
tickets --;
notifyAll();
this.flag = !this.flag;
}
public synchronized void make() throws InterruptedException {
if(isFlag()){
wait();
}
tickets ++;
notifyAll();
this.flag = !this.flag;
}
}
class Salesman extends Thread{
Ticket ticket;
public Salesman(Ticket ticket) {
this.ticket = ticket;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
ticket.make();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Customer extends Thread{
Ticket ticket;
public Customer(Ticket ticket) {
this.ticket = ticket;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
if(ticket.tickets<=0){
return;
}
ticket.buy();
System.out.println(Thread.currentThread().getName() + "买了第:" + (i + 1) + "张票还剩下" + ticket.tickets + "张票");
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Test{
public static void main(String[] args) throws InterruptedException {
Ticket ticket = new Ticket();
Salesman salesman = new Salesman(ticket);
Customer customer1 = new Customer(ticket);
Customer customer2 = new Customer(ticket);
salesman.setName("售票处");
customer1.setName("小容");
customer2.setName("小侯");
long start = System.currentTimeMillis();
salesman.start();
customer1.start();
customer2.start();
salesman.join();
customer1.join();
customer2.join();
long end = System.currentTimeMillis();
System.out.println("执行了"+(end-start)+"毫秒还有票数:"+ticket.tickets);
}
}
那么这就是我们的这个信号灯法。
管程法
这个也是,不过要稍微麻烦一点,不过可以直接使用synchronized实现,也不难。
class OneTicket{
public OneTicket(int id) {
this.id = id;
}
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
class TicketContainer{
OneTicket[] tickets = new OneTicket[10];//10个票
int count = 0;
public synchronized void push(OneTicket ticket) throws InterruptedException {
if(count==tickets.length){
wait();
}
tickets[count] = ticket;
count++;
notifyAll();
}
public synchronized OneTicket pop() throws InterruptedException {
if(count==0){
wait();
}
count--;
OneTicket ticket = tickets[count];
notifyAll();
return ticket;
}
}
class Peopel extends Thread{
private TicketContainer ticketContainer;
public Peopel(TicketContainer ticketContainer) {
this.ticketContainer = ticketContainer;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
OneTicket pop = ticketContainer.pop();
System.out.println(Thread.currentThread().getName() + "买了第:" + pop.getId() + "张票还剩下" + ticketContainer.count + "张票");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class SalesPeopel extends Thread{
private TicketContainer ticketContainer;
public SalesPeopel(TicketContainer ticketContainer) {
this.ticketContainer = ticketContainer;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("生成了第"+(i+1)+"张票");
try {
ticketContainer.push(new OneTicket((i+1)));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class TestDome{
public static void main(String[] args) throws InterruptedException {
TicketContainer ticketContainer = new TicketContainer();
SalesPeopel salesman = new SalesPeopel(ticketContainer);
Peopel customer1 = new Peopel(ticketContainer);
Peopel customer2 = new Peopel(ticketContainer);
salesman.setName("售票处");
customer1.setName("小容");
customer2.setName("小侯");
salesman.start();
customer1.start();
customer2.start();
}
}
这里注意的是那个终止条件的问题(我没加),所以的话会一值堵塞。
此外:
这里的话我其实是有一个案例的,不过是使用python的多线程爬虫写的,爬取小姐姐的图片来者。代码是这个,理论都是一样的,只是我们这边使用的queue是人家写好的堵塞的玩意。
import requests
from lxml import etree
import threading
import os
from urllib import request
from queue import Queue
url_frist_save=Queue(1000)
#url_save=Queue(200)
headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'}
url_root='http://pic.netbian.com'
def get_url():
date_frist=requests.get(url_root,headers=headers)
date_frist=date_frist.content.decode('gbk')
date_frist_=etree.HTML(date_frist)
uel_1=date_frist_.xpath('//li//a[@href and @title and @target]/@href')[4:]
for i in uel_1:
url=url_root+i
url_frist_save.put(url)
for i in range(2,101):
urlx='http://pic.netbian.com/index_{name}.html'.format(name=str(i))
date_frist = requests.get(urlx, headers=headers)
date_frist = date_frist.content.decode('gbk')
date_frist_ = etree.HTML(date_frist)
uel_1 = date_frist_.xpath('//li//a[@href and @target]/@href')[1:]
for i in uel_1:
url = url_root + i
url_frist_save.put(url)
def down_load():
if not os.path.exists(r'C:Usersa3139Desktopprojects爬虫domepictures'):
os.makedirs(r'C:Usersa3139Desktopprojects爬虫domepictures')
while True:
date_frist=requests.get(url_frist_save.get(),headers=headers)
date_frist=date_frist.content.decode('gbk')
date_frist_=etree.HTML(date_frist)
url_down=url_root+str(date_frist_.xpath('//div[@class="photo-pic"]/a/img/@src')[0])
name=str(date_frist_.xpath('//div[@class="photo-pic"]/a/img/@title')[0])
path=r'C:Usersa3139Desktopprojects爬虫domepictures'+'\'+name+'.jpg'
print(path)
print(url_down)
try:
request.urlretrieve(url_down,path)
request.urlcleanup()
except:
print('此图片下载失败')
if url_frist_save.empty():
print(url_frist_save.empty())
break
if __name__=="__main__":
for i in range(5):
t1=threading.Thread(target=get_url)
t2=threading.Thread(target=down_load)
t1.start()
t2.start()
应该还能用。
此外的话还有这个线程池,这个就是方便管理,我先前还记得有关一个线程池的面试题,关于线程池的七个参数,好像是,这个就今天早上说一下吧。
线程池
这个其实一开始有我们的演示。
public class Pools {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(new MyThread());
executorService.execute(new MyThread());
executorService.execute(new MyThread());
executorService.shutdownNow();//用完要关
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println("FUcking");
}
}
这个是最简单的固定大小的,实际上我们有动态的,这个是一个考点,也是个小细节,毕竟要压榨资源呗~
早上再补充喽,那么计划的第一部分就OK了。
最后
以上就是悲凉手机为你收集整理的java多线程(分分钟基础秒杀)前言线程安全问题(并发问题)线程池的全部内容,希望文章能够帮你解决java多线程(分分钟基础秒杀)前言线程安全问题(并发问题)线程池所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复