线程
来源:互联网 发布:手机淘宝店铺怎么激活 编辑:程序博客网 时间:2024/06/06 00:39
线程:
本文有5个部分内容:
多线程实现方式
线程同步问题
生产消费者问题
线程池
线程非常用方法
概念: 程序:由多条指令组成
进程:正在运行的程序
线程:进程执行中的执行个体单元
程序和进程的关系可以是一对多,进程和线程可以一对多,但是一个进程至少会有一个线程,如main。
在如在main函数中,一行行读下来,就是单线程
1.多线程实现方式:
1、自定义一个类,来extends继承Thread,重写run方法,并构造当前类的对象,通过调用start方法启动
2、自定义一个类,来实现Runnable接口,重写run方法,并构造当前类对象,再包装秤Thread对象,最后再调用start方法启动
3、匿名内部类Thread类的方式启动:
//匿名内部类调用
new Thread(){
public void run(){
//重写run方法
try {
thread.sleep(100);
} catch (InterruptedExceptione) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
三个方法:
定义一个继承了Thread的A类,定义一个实现了Runnable的B类。匿名内部类直接在Test中使用
Test:
public class Test {
public static void main(Stringargs[]){
Test t=new Test();
long begin =System.currentTimeMillis();
//线程A类调用
A a1=new A();//线程对象定义
a1.start();//start调用
//B类调用
//通过实现Runnable启动线程
B b1=new B();//定义对象
Thread thread =new Thread(b1);
//包装为Thread
thread.start();
//用start调用线程
long end =System.currentTimeMillis();
//匿名内部类调用
new Thread(){
public void run(){
//重写run方法
try {
thread.sleep(100);
} catch (InterruptedExceptione) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
}
}
A:
public class Aextends Thread{
public void run(){
//重写run方法
a();
}
public void a(){
}
}
B:
public class Bimplements Runnable{
public void run(){
//重写run方法
}
public void b(){
}
}
三种线程方法如何选择:
1. 当当前类已经继承了一个类了,但是还需要多线程,则只能选择用Runable
2. 当不需要再继承其他类的时候,毫不犹豫选择继承Thread
3. 线程逻辑很简单的,可以选择用匿名内部类方式做
4. 程序中出现死循环,用多线程
创建Thread线程实例:小球运动
通过创建线程,使小球能够向右运动
首先绘制小球运动的界面
在类中先定义静态的小球与线程对象,便于在其他方法中的调用
用setBall()方法绘制窗体,并在方法中创建线程对象,调用线程
通过paint()方法重绘窗体,并绘制小球
BallFrame 类源码:
package com.today.t2;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
public class BallFrame extends JFrame {
public static BallFrameba=new BallFrame();
public static Bthreadb;
//界面对象ba,线程对象b需要定义为静态
//主函数入口
public static void main(Stringargs[]){
//调用setBall()方法绘制窗体
ba.setBall();
}
public void setBall(){
this.setBackground(Color.white);
this.setTitle("小球运动");
this.setSize(700,700);
this.setDefaultCloseOperation(3);
//new一个新的县城对象,将窗体对象ba作为参数传入
b=new Bthread(ba);
b.start();
this.setVisible(true);
}
public void paint(Graphicsg){
super.paint(g);
g.fillOval(b.x,b.y, 50, 50);
}
}
定义小球的线程类Bthread继承Thread
将小球窗体对象传入构造函数,重写run()方法,并在run()方法中通过调用repaint进行强制界面刷新
小球运动线程Bthread类源码:
public class Bthreadextends Thread{
//定义坐标x,y
public int x=50,y=50;
public BallFrame ba;
//向小球线程类的构造方法中传递小球界面对象
public Bthread(BallFrameba){
this.ba=ba;
}
//重写run方法
public void run(){
while(true){
x+=4;
ba.repaint();
//刷新界面
try {
Thread.sleep(50);
} catch (InterruptedExceptione) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
二.线程同步问题:
案例:
张三和李四:是两夫妻
事情:他们在中国银行开了一个户头,张三拿了银行卡,李四拿了存着,存了10000块进去了
发生:某一条两个人没商量情况下,张拿着银行卡去了ATM机上准备去5000,正在这个时候,李四她也拿着存着,去柜台取钱,准备取7000。
只是单纯使用线程的话,有可能造成数据错误,造成两个人同时取到钱,且与实际金额、存款不符
这个时候需要引入同步锁synchronized
synchronized可以用来锁定方法,方法被调用时,就会锁住,使用完后才能被再次调用
锁定方法:public synchronized int take(int account_take){}
也可以用来锁定代码块//如下,为锁代码块的方法
/*synchronized(this){
if(account_take>account_balance){
return -1;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
具体实现:
账户 Account类源码
创建一个账户类,添加余额属性,在构造方法中传入余额
写take方法(用于取款),当取款金额大于余额时,返回-1,不大于时,进行余额清算,并打印余额。take方法需要用同步锁锁住,使得不会发生两人同时能够取款成功的错误。
创建一个人类继承Thread类,添加取款金额,取款方法,账户对象属性。构造方法传入三种属性。重写的run()方法中调用use()方法,use()方法中调用take()方法并判断取款是否成功,将其打印。
在Account类的主函数中先new一个账户对象,存入金额10000,再定义两个线程对象(即相当于两个人),两个对象调用start()方法从而调用take方法进行取款并判断
public class Account {
//账户类
//添加属性
public int account_balance;//余额
//public int account_take;//取款数目
//给账户传初始金额
public Account(int account_balance){
this.account_balance=account_balance;
}
//取款,在People类的run方法中调用,再将取款金额传递过来
public synchronized int take(int account_take){
//用同步锁synchronized,锁住当前的取款方法
//则有线程进入,会锁住当前方法
//synchronized还可以用来锁住代码块
//如下,为锁代码块的方法
/*synchronized(this){
if(account_take>account_balance){
return -1;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
if(account_take>account_balance){
return -1;
}
try {
Thread.sleep(20);
} catch (Exception e) {
e.printStackTrace();
}
account_balance=account_balance-account_take;
return account_balance;
//返回余额
}
//主函数入口
public static void main(Stringargs[]){
Account ac = new Account(10000);
//定义人的线程,传参
People p1=new People(ac,"ATM自动取款机",5000);
p1.start();
People p2=new People(ac,"人工服务",7000);
p2.start();
}
}
人 People类源码
public class Peopleextends Thread{
private Account ac;
//定义一个账户对象
private String type;
//定义取款类型
private int account_take;
//取款金额
public People(Accountac,String type, int account_take){
this.ac=ac;
this.type=type;
this.account_take=account_take;
}
//定义构造方法,创建people时会出现对应属性
public void run(){
//重写run方法
use();
}
//调用取款功能的方法
public void use(){
int result =ac.take(account_take);
if(result>0){
System.out.println(type+"方式取现成功,取现额度为:"+account_take+";余额为:"+result);
}else{
System.out.println(type+"方式取现失败,取现额度为:"+account_take);
}
}
}
三.生产消费者问题:
要求是,生产者生产手机放入仓库,当库中手机数量大于0时,不会继续生产,仓库中没有手机时,会开始生产手机,即库存中的手机至多一台。
消费者发现仓库中的手机数量不为0时,会消费一台手机。
由题目要求可以得知,这里可以建立两个线程类,一个是生产者线程,ProductorThread,继承线程类;一个是消费者线程CustomerThread.
手机可以单独建一个类,Phone。
再根据手机要放入库存,可以提供一个容器ArrayList,去存放手机。则为ArrayList<Phone> list;
在生产、消费者线程中,由于两者共用list对象(即里面的手机),则可以通过同步锁synchronized锁定List对象,循环判定,避免出现同步问题。
要注意的是,在这里要用到阻塞和唤醒操作,即wait 和 notify。两者必须在同步锁内,对同一个对象使用。wait在挂起时会释放同步锁。
如下的while循环中有wait()和notify()的使用例子
while(true){
//使用同步锁来锁住list
synchronized(list){
//检测容器当中是否还有产品,如果有产品,则不生产
if(list.size()>0){
System.out.println("仓库中还有产品存在");
try {
list.wait();
//还有商品存在,让他进入阻塞状态
} catch (InterruptedExceptione) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Phone phone = new Phone(index+"号手机");
index++;
list.add(phone);
//向容器中添加手机
System.out.println("生产者生产了一个手机,型号是:"+phone.getName());
//赶紧通知消费来取产品,唤醒
try {
Thread.sleep(5000);
} catch (InterruptedExceptione) {
// TODO Auto-generated catch block
e.printStackTrace();
}
list.notify();
}
}
利用同步,达到效果:生产者在库存大于0时等待,=0时生产并提醒消费者;消费者库存=0时等待,>0时消费。
生产者的run()方法中,生产手机后,手机标号+1,并且将新生产的手机存入到容器中。
消费者则是在取出后,用remove方法将容器中此标号的手机移除。
最后再建立一个测试类,主函数中创建线程对象并启动,测试结果与想象是否相同。
生产者源码:
import java.util.ArrayList;
public class ProductorThreadextends Thread{
//定义生产者线程类,继承Thread
public ArrayList<Phone>list;
//定义一个用于装手机的容器
//在构造方法中将手机作为参数传入
public ProductorThread(ArrayList<Phone>list){
this.list=list;
}
//重写run()方法
public void run(){
int index=1;
//设置下标为index,将其设为1
//进行循环判定
while(true){
//使用同步锁来锁住list
synchronized(list){
//检测容器当中是否还有产品,如果有产品,则不生产
if(list.size()>0){
System.out.println("仓库中还有产品存在");
try {
list.wait();
//还有商品存在,让他进入阻塞状态
} catch (InterruptedExceptione) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Phone phone = new Phone(index+"号手机");
index++;
list.add(phone);
//向容器中添加手机
System.out.println("生产者生产了一个手机,型号是:"+phone.getName());
//赶紧通知消费来取产品,唤醒
try {
Thread.sleep(5000);
} catch (InterruptedExceptione) {
// TODO Auto-generated catch block
e.printStackTrace();
}
list.notify();
}
}
}
}
消费者源码:
import java.util.ArrayList;
public class CustomerThreadextends Thread{
//定义消费者线程类,继承Thread
private ArrayList<Phone>list;
public CustomerThread(ArrayList<Phone>list){
this.list =list;
}
public void run(){
while(true){
synchronized(list){
if(list.size()==0){
//进入阻塞状态
try {
list.wait();
} catch (InterruptedExceptione) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//从容器中移除这个手机
Phone phone = list.remove(0);
System.out.println("消费者消费了一个手机,型号是:"+phone.getName());
try {
Thread.sleep(5000);
} catch (InterruptedExceptione) {
// TODO Auto-generated catch block
e.printStackTrace();
}
list.notify();
}
}
}
}
手机类源码:
public class Phone {
private String name;
public Phone(String name){
this.name =name;
}
public String getName(){
return name;
}
}
测试类源码:
import java.util.ArrayList;
public class Test {
public static void main(Stringargs[]){
ArrayList<Phone> list = new ArrayList<Phone>();
//创建生产消费者进程对象
ProductorThread pt=new ProductorThread(list);
CustomerThread ct=new CustomerThread(list);
pt.start();
ct.start();
}
}
四.线程池:
对线程池的使用是因为,我们直接创建线程并使用后,结束时线程会直接死亡、销毁(进入dead状态)。每次想要使用线程都去创建一个新的线程,再让它自然死亡,会造成一种浪费,加大成本。
而这个时候,就需要我们引入线程池的概念
线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程。线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求。然而,增加可用线程数量是可能的。线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中并等待下一次分配任务。
而线程池中准备的那些线程,可以通过我们的设计,让这些线程在使用完之后,进入暂停状态,等待下一次调用。这样可以避免再次创建一个新的线程而造成的浪费
下面建立一个MyThreadPool类(线程池),Worker类,MyTask(任务类)
在建立好几个类后,先看MyThreadPool类。
先在类中定义两个容器,分别装执行者线程、任务:
private Worker[] workers; //装执行线程
private LinkedList<MyTask> taskList = new LinkedList<MyTask>();//装任务
定义MyThreadPool的构造方法,public MyThreadPool(int size){}
在该构造方法中,初始化多个线程对象:workers = new Worker[size];
之后用for循环创建,再用start调用。
线程池类中还需要一个方法去获取任务,getTask(MyTask task);
用同步锁锁住对象,再将获取到的任务添加到容器中,notify唤醒。
现在到MyTask类(实现Runnable):
定义name,传入构造函数。
定义方法work(),执行任务睡眠3秒后,执行完毕
在run()中调用work
Worker类(继承Thread)同样要获取任务队列private LinkedList<MyTask> tasklist;
构造方法的内容、参数与MyTask相同。
重写run()方法,循环判断,锁定任务对象tasklist,用wait和notify让他被阻塞和唤醒,并.removeFirst();取出任务并执行,调用work方法。
最后是测试类Test:
在主函数中创建线程池对象,赋予Size=5
MyThreadPool mtp = new MyThreadPool(5);
给他一个循环
for (int i = 0; i < 20; i++) {
MyTask task = new MyTask(i+"号任务");
}
如下为各类代码
MyThreadPool:
import java.util.LinkedList;
public class MyThreadPool {
//定义两个容器:一个装执行线程,一个装任务
private Worker[] workers;
private LinkedList<MyTask> taskList =new LinkedList<MyTask>();
public MyThreadPool(int size){
workers = new Worker[size];
//往数组中初始化多个执行线程对象
for (int i = 0; i < workers.length; i++) {
//创建对象
Worker worker = new Worker(i+"号执行线程",taskList);
worker.start();
workers[i] = worker;
}
System.out.println("线程池已经初始化");
}
//定义一个函数
public void getTask(MyTask task){
synchronized (taskList) {
taskList.add(task);
taskList.notify();
}
}
}
Worker类:
import java.util.LinkedList;
public class Workerextends Thread{
private String name;
//一定要先获取到任务队列
private LinkedList<MyTask> tasklist;
public Worker(String name,LinkedList<MyTask> tasklist) {
this.name = name;
this.tasklist = tasklist;
}
public void run() {
while(true){
MyTask task;
synchronized (tasklist) {
if(tasklist.size()==0){
try {
tasklist.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//从任务队列中,取出一个任务,并执行这个人
task = tasklist.removeFirst();
}
System.out.println(name+"正在执行:"+task.name);
task.work();
System.out.println(name+"执行完毕:"+task.name);
}
}
}
MyTask类:
public class MyTaskimplements Runnable{
public String name;
public MyTask(String name){
this.name = name;
}
public void work(){
System.out.println(name+"开始执行任务");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"任务执行完毕");
}
public void run() {
work();
}
}
Test类:
public class Test {
public static void main(String[] args) {
MyThreadPool mtp = new MyThreadPool(5);
for (int i = 0; i < 20; i++) {
MyTask task = new MyTask(i+"号任务");
}
}
}
五.线程非常用方法
currentThread() 返回当前运行的Thread对象。
wstop() 使调用它的线程立即停止执行,永远不要调用这个方法。
wsuspend() 使线程挂起,暂停运行。
wresume() 恢复挂起的线程,使其处于可运行状态(Runnable)。
wyield() 将CPU控制权主动移交到下一个可运行线程。
wsetPriority() 设置线程优先级。
wgetPriority() 返回线程优先级。
wsetName() 设置线程的名字。
wgetName() 返回该线程的名字。
wisAlive( ) 如果线程已被启动并且未被终止,那么isAlive( )返回true。如果返回false
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- USB Class Codes
- mybatis中使用Java8的日期LocalDate、LocalDateTime
- CC2640R2 BLE5 Long Range mode
- python 爬取学信网登录页面
- 微软创立全新人工智能实验室,与DeepMind、OpenAI同台竞技
- 线程
- 网络基础之iso,osi七层模型
- sqlServer里面查询一张表里面有没有自增字段
- LeetCode 189 Rotate Array (思维)
- Bootstrap轮播图不兼容IE的问题
- Java注解全解
- 【C#】集合已修改;可能无法执行枚举操作。
- centOS7设置静态ip后无法上网的解决
- DxO OpticsPro 11.4.2.68 for Mac 简体中文全汉化版 送4部教程 完美支持 OS X 10.12 系统